Skip to content

Latest commit

 

History

History
146 lines (108 loc) · 6.27 KB

README.md

File metadata and controls

146 lines (108 loc) · 6.27 KB

snek 🐍

snek is small tool for writing vyper contracts. It does two basic things: build your contracts, and test them.

You can write tests as vyper contracts, which run on an Anvil node for very quick test runs. Besides simple assertions, test contracts can also assert events are logged correctly and test invariants with fast fuzzing. You can use a javascript for situations where in-EVM tests are not sufficient.

installation

snek runs Vyper and Anvil as subprocesses and both need to be installed and added to your path for snek to run.

For now snek can be run from source by running index.js directly with node.js, or run from binaries. To build, first clone this repo, then run npm run initialize and npm run build-general.

The snek executable generated should then be placed somewhere on your path. If using linux you can use npm run build-linux to install the executable in /usr/bin/local.

usage

commands

snek make compiles all Vyper contracts in the source and subdirectories. Paths can be configured with command line arguments and will otherwise default to searching for contracts in ./src and will save the output to ./out.

snek test performs the above, and in addition also compiles all test contracts and runs all methods in every test contract which start with test. Methods starting with 'test_throw' will pass in a snek test iff an exception is thrown. The path for test files can be configured and is ./test by default.

To view help for snek or any subcommand run the command with the --help option.

Usage: snek [options] [command]

vyper helper command

Options:
  -o,--output-dir <string>               directory to output compiled contracts to (default: "./out")
  -V, --version                          output the version number
  -h, --help                             display help for command

Commands:
  make [src_path]                        compile all vyper contracts in source folder
  test [options] [src_path] [test_path]  compile and test all vyper contracts in source and test folders
  help [command]                         display help for command

Examples:

snek make
  -> compiles all vyper files in ./src and stores the json output at ./out)

snek test
  -> compiles all vyper files in ./src and ./test, stores the json output at ./out,
     runs all tests in ./test with seed = 0 and reps = 1

snek make ./test/src --output-dir ./test/out
  -> compiles all vyper files in ./test/src and ./test/test, stores the json output at ./test/out

snek test ./test/src ./test/test --output-dir ./test/out --seed 123 --reps 1000
  -> compiles all vyper files in ./test/src and ./test/test,
     stores the json output at ./test/out,
     runs all tests in ./test/test with seed = 123 and reps = 1000

networks

When running snek you do not have to consider networks, snek will start up a test network for you. If you prefer to run another network yourself you can by starting it up before invoking snek. If http://127.0.0.1:8545 is already bound then snek will continue with tests and use the existing network. This allows using something like hardhat node to view Vyper print() statements which work the same as hardhats console.log(), or using ganache if you want to inspect via GUI. Benchmarking has shown snek tests with anvil are hundreds of times faster.

example

Here we explain the most important parts of a snek test. There is also a full working example.

First include an interface to snek. (TODO: replace with single import statement, file inserted at build time)

interface Snek:
    def make(typename: String[32], objectname: String[32], args: Bytes[3200]) -> address: nonpayable
    def echo(target: address): nonpayable
    def rand(set: uint256) -> uint256: nonpayable
  • make is used to deploy instances of any other source contracts you want tests to interact with
  • echo is used to set the events to expect from the target contract, and can be repeated to retarget
  • rand gets the next pseudo random number to enable fuzz tests with random actions and values

A snek instance is passed into each test contracts init method and should be stored as a test member.

def __init__(_snek: Snek):
    self.snek = _snek
    name: String[32] = 'ali'
    year: uint256 = 10
    args: Bytes[224] = _abi_encode(name, year)
    self.prs1 = Person(self.snek.make('Person', 'person1', args))

snek contains a multifab instance which snek.vy uses in make to deploy any other source contracts. The first arg specifies which contract to create, and the arguments for that contracts constructor are passed as the third parameter.

Methods beginning with 'test' will be run by the test harness.

@external
def test_set_year():
    self.snek.echo(self.prs1.address)
    log Birth(20)
    self.prs1.set_year(20)
    assert self.prs1.year() == 20

This test sets values in a Person and uses vypers assert function to test the behaviour is correct. snek.echo(target) tests that the target contract echos back the same events as the test contract logs. After echo() the test runner records everything logged by the test for the current target and asserts that the target later emits the same events in the same order. Extra events from the target do not cause a failure and the target can be changed to queue up multiple sets of required events.

snek can also be used to fuzz and test invariants.

@external
def test_fuzz(reps: uint256):
    for i in range(MAX_REPS):
        if i > reps:
            break
        opt: uint256 = self.snek.rand(3)
        if opt == 0:
            self.prs1.draw(self.snek.rand(MAX_DRAW))
        elif opt == 1:
            self.prs1.wipe(self.snek.rand(self.prs1.cash() + 1))
        elif opt == 2:
            self.prs1.shop(self.snek.rand(self.prs1.cash() + 1))

        assert self.prs1.debt() == self.prs1.cash() + self.prs1.toys()

Test methods can optionally require on parameter, reps, intended to help with fuzz tests. The value passed to the test is the value of the snek command line option -r or --reps. Fuzz tests can use the snek method rand() which returns a random uint256, and the PRNG is seeded with the command line option -s or --seed which defaults to zero.