Creating an initial state for the fuzzer to target
The seed deployment is what you provide as a starting point to the fuzzing process. It's essentially a snapshot of your system, where all the relevant components are set up in a realistic configuration.
You'll commonly write one or more deployment scripts to set up this seed deployment (we also call it a seed state). Most of the code for these scripts will mirror your existing deployment scripts and test fixtures. However, there are some unique additions to make the seed primed for fuzzing.
In our fuzzing engine, we simulate transactions coming from a set of predefined user accounts. There include the following:
The base fuzzing account
An account with 0 ETH
An account with a lot of ETH
The creator account
In most cases, you'll need to do some setup as these are the only actors that we'll simulate. If the fuzzing accounts do not hold any tokens or permissions in the system under test, then it's unlikely that they'll be able to cover much of its code.
Here are some common priming steps:
Transfer ownership of (some) contracts to a fuzzer account.
Mint tokens and assign them to a fuzzer account.
Assign an allowance which allows a fuzzer account to send tokens for another user.
A good excerise is to ask yourself the following question: Could the fuzzer accounts call the contracts under test to cover all functionality that you want to test?
You might notice that the fuzzer still couldn't cover some code once you've run your first fuzzing campaign. There is a high chance that a small tweak to your seed deployment can do miracles!
Inspect coverage results and see if the fuzzer gets stuck anywhere.
Inspect the requirements for covering the condition.
Can you come up with a test case that covers the part where the fuzzer gets stuck?
If not, then why?
Can we give more tokens or privileges to the fuzzer that would make it possible?
Sometimes the seed is fine, and you need to adapt the fuzzing configuration instead! Check out the fuzzing configuration page for more info.
The topics above cover most of what you need to know to write great seed deployment scripts. In this section, we cover some of the deeper details.
Sometimes you don't want to analyze the whole system at once. This is easy! Just create a seed deployment with the components you want to test!
However, keep in mind that the seed must still be functional. If the contracts revert on each transaction, then fuzzing won't be effective.
Upgradable contracts and proxies
Some systems use upgradable proxy contracts. Keep in mind that a fuzzer can perform such upgrades if the fuzzer accounts have sufficient permissions. Such upgrades may not be very interesting since the fuzzer can perform non-realistic upgrades. For example, it can make your proxy lending pool point to an ERC20 token that happens to be part of the system.
To avoid this, you can consider disabling the upgrade functionality of the proxy during fuzzing.
Many projects depend on external components, which you might not want to include in your fuzzing seed. In most cases, you'll be able to use mock implementations that you already built for your unit tests.
Be aware! Mocks can act differently than their real world counterparts. This can leave bugs unnoticed!
Working with Ganache
There are some CLI options that make life easier when using ganache:
Add --deterministic to make contracts be deployed at the same address each time you run your deployment scripts. This way you won't have to go in and adapt the addresses in your configuration all the time.
$ ganache-cli --deterministic
Sometimes Scribble instrumentation makes contracts grow beyond the regularly allowed size. Add `--allowUnlimetedContractSize` and you won't have any problems!
$ ganache-cli --allowUnlimitedContractSize
Sometimes it can be necessary to also increase the gasLimit using --gasLimit <new-superhigh-limit>