Tayt is a StarkNet smart contract fuzzer.
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
.
Run with default options.
tayt tests/flags.cairo
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.
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.
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
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