Tayt is a StarkNet smart contract fuzzer.
Installation
We recommend using a Python virtual environment.
git clone https://github.com/crytic/tayt.git && cd tayt python setup.py install
If you don't have cairo-lang already installed and you are on MacOS you may have an error about a missing gmp.h file even if you executed brew install gmp.
The following command can be used to solve it.
CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib pip install ecdsa fastecdsa sympy
If the above command doesn't work you can find more solutions here.
Usage
Run with default options.
When starting you will see the properties to be checked and the external functions used to generate a sequence of transactions.
Fuzzing the following properties:
tayt_flag1
External functions:
set0
set1
Eventually if a property is violated a call sequence will be presented with the order of functions to be called, the respective arguments passed, the caller address, and the events emitted represented by a starting E.
[!] tayt_flag1 violated
Call sequence:
set0[0] from 1
E set_flag0[0]
set1[97066683862585213645535248899637309600] from 0
E set_flag1[97066683862585213645535248899637309600]
The full help menu is:
usage: tayt [-h] [--seq-len SEQ_LEN] [--blacklist-function BLACKLIST_FUNCTION [BLACKLIST_FUNCTION ...]]
[--psender PSENDER] [--sender SENDER [SENDER ...]] [--cairo-path CAIRO_PATH [CAIRO_PATH ...]]
[--coverage] [--no-shrink] [--get-class-hash] [--declare DECLARE [DECLARE ...]]
filename
StarkNet smart contract fuzzer.
positional arguments:
filename Cairo file to analyze.
optional arguments:
-h, --help show this help message and exit
--seq-len SEQ_LEN Number of transactions to generate during testing. (default: 10)
--blacklist-function BLACKLIST_FUNCTION [BLACKLIST_FUNCTION ...]
Function name (space separated) to blacklist from execution.
--psender PSENDER Address of the sender for property transactions. (default: 1)
--sender SENDER [SENDER ...]
Addresses (space separated) to use for the transactions sent during testing.
(default: [0, 1, 2])
--cairo-path CAIRO_PATH [CAIRO_PATH ...]
A list of directories, separated by space to resolve import paths.
--coverage Output a coverage file.
--no-shrink Avoid shrinking failing sequences.
--get-class-hash Get the class hash to use with a deploy function.
--declare DECLARE [DECLARE ...]
A list of contracts that will be declared.
Writing invariants
Invariants are StarkNet view functions with names that begin with tayt_, have no arguments, and return a felt. An invariant is considered failed when it returns 0.
@view func tayt_flag{ range_check_ptr, syscall_ptr: felt*, pedersen_ptr: HashBuiltin* }() -> (res: felt): let (flag_result) = flag.read() if flag_result == 1: return (0) end return (1) end
If the flag storage variable is set to 1 the invariant will fail.
How to test a contract that deploys other contracts
We will use test/deploy.cairo as an example of a contract that deploys other contracts.
First we have to get the class hash of the contracts we want to deploy:
In our case we will deploy test/flags.cairo.
tayt --get-class-hash tests/flags.cairo
We will get the class hash to use in the deploy function.
Class hash for tests/flags.cairo
2024779828085525422431444182955849544076259995530386260630136607064428821244
Finally we can test deploy.cairo, the --declare option takes a list of contracts to declare in the fuzzing state.
tayt tests/deploy.cairo --declare tests/flags.cairo
Coverage
When the --coverage option is enabled, a file named covered.{time}.txt which contains the source code with coverage annotations will be saved. A line starting with * has been executed at least once.
Example with tests/flags.cairo:
@external *func set1{ * syscall_ptr: felt*, * pedersen_ptr: HashBuiltin*, * range_check_ptr, * ecdsa_ptr: SignatureBuiltin* * }(val: felt): * let (res, remainder) = unsigned_div_rem(val, 10) * if remainder == 0: * let (flag_0) = flag0.read() * if flag_0 == 1: * flag1.write(1) * set_flag1.emit(val) * return () end * return () end * return() end