# Mainnet fork testing guidelines for DeFi using Brownie For a DeFi system, in many cases unit testing is insufficient, since mainnet contracts can behave differently from the testing setup of the platform. Brownie allows to easily configure a testing environment that will use real mainnet contracts and allows to test the behavior of the system in the conditions closest to real. [Brownie documentation](https://eth-brownie.readthedocs.io/en/stable/toctree.html) provides the most complete and up-to-date usage information. These guidelines are meant to be used as addition to the docs, as a shortcut on a way to better testing. Good tutorial from Ben Hauser, that covers the power of brownie: https://iamdefinitelyahuman.medium.com/ethereum-mainnet-testing-with-python-and-brownie-82a61dee0222 Curve video tutorials for brownie that covers a lot of aspects: https://github.com/curvefi/brownie-tutorial ## Installing brownie https://eth-brownie.readthedocs.io/en/stable/install.html Brownie uses `ganache-cli`, and it is recommended to use the most recent version from https://github.com/trufflesuite/ganache. Npm ganache-cli package is outdated and does not support certain things. ``` npm install ganache@7.0.0-alpha.1 --global ``` ## Configuring mainnet fork With default configuration, brownie is configured to be used with Infura service. Infura project ID needs to be set first: https://infura.io/register ``` export WEB3_INFURA_PROJECT_ID=YourProjectID ``` https://eth-brownie.readthedocs.io/en/stable/network-management.html#using-a-forked-development-network ### Using other node provider Forking from Infura can be very slow. If you are using this mode extensively, it may be useful to run your own Geth node or other provider. Mainnet provider API url can be edited: * `brownie networks export networks` * Edit the `networks.yaml` file with relevant mainnet API url. * `brownie networks import networks.yaml True` to apply the config ## Starting with mainnet-fork Providing a flag `--network mainnet-fork` will launch brownie with `brownie <command> --network mainnet-fork` ## Compiling project Official docs: ``` https://eth-brownie.readthedocs.io/en/stable/compile.html ``` Brownie has new hardhat support feature: ``` https://eth-brownie.readthedocs.io/en/stable/install.html?highlight=hardhat#using-brownie-with-hardhat ``` Alternative is adjusting the configuration file of the brownie project. Example `brownie-config.yaml`: ``` autofetch_sources: true compiler: solc: version: 0.7.6 remappings: - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@3.2.1-solc-0.7" - "@opengsn=opengsn/gsn@2.2.2/packages" dependencies: - OpenZeppelin/openzeppelin-contracts@3.2.1-solc-0.7 - opengsn/gsn@2.2.2 ``` Command ` brownie compile` compiles all the contracts in the `contracts/` folder of the brownie project. ## Writing tests [Brownie docs "Writing Unit Tests" page](https://eth-brownie.readthedocs.io/en/stable/tests-pytest-intro.html) contain the most complete source of information for test writing. Reading it is essential for testing. Fixtures are suggested to be used for the fixed external contracts that need to be used by multiple unit tests, since the tests runtime can be quite long. Thus reusing the contracts and definitions via use of fixtures can safe a number of queries to ganache and Etherscan. ### Fetching existing contracts from Etherscan Brownie supports integration with Ethersacn API and in the tests it is possible to query exact token via it: ``` python @pytest.fixture def susd(): susd_addr = "0x57ab1ec28d129707052df4df418d58a2d46d5f51" yield Contract.from_explorer(susd_addr) ``` Brownie will then try to fetch, compile and use the ABI of the code automatically. The storage of the contract on forked mainnet will be used. The ABI is only needed to tell brownie, what functions the contract has and how the calldata should be treated. The rate of Etherscan API queries is limited without API key. Registering and running `export ETHERSCAN_TOKEN=<token>` will allow 5 queries per second, which should be enough for tests. Alternatively, any address can be associated with certain compiled contract: ``` python @pytest.fixture def usdt(): usdt_addr = "0xdac17f958d2ee523a2206206994597c13d831ec7" yield ERC20.at(usdt_addr) ``` This is a simpler and more practical approach for most of the tokens. Especially since USDT is not compiled automatically by brownie from Etherscan due to a solc version problem. Using real tokens is essential for good testing. ## Getting funds ```python= from brownie import accounts ``` By default a ganache fork has 10 accounts, associated with certain mnemonics. These accounts have 100 Ether by default, and since a Mainnet fork is used, these funds can be exchanged into needed assets. [Example in the end](https://iamdefinitelyahuman.medium.com/ethereum-mainnet-testing-with-python-and-brownie-82a61dee0222) In dev environment (mainnet-fork), it is also possible to send transactions without having the private key for them. ``` acc = accounts.at('0x79B2f0CbED2a565C925A8b35f2B402710564F8a2', force=True) ``` ## Testing scenarios Since the system is intended to work with many different tokens, scenarios should involve them, e.g., Tokens with missing return values(USDT), Tokens with Low Decimals (USDC has 6) or High Decimals (YAM-V2 has 24). There is a [repo](https://github.com/d-xo/weird-erc20) that has some good examples. Testing scenarios should include all possible actions, that a user can perform, e.g. deposits, withdrawals, swaps, etc. The contracts that are meant to be upgradeable need to be deployed as proxies. The upgradeability needs to be tested as well as `Initialize` specific logic on the smart contracts. Safe Math can also cause problems with unexpected underflows. It is suggested to test the strategies with big number of tokens values inside as well as with big total value amounts. Minting a lot of tokens can be possible with "force" miner account for existing coins. https://eth-brownie.readthedocs.io/en/stable/core-chain.html#time-travel Using the time-travel functionality, the manager upgradeable parameters can be tested. ## Chain Snapshots and Undo/Redo https://eth-brownie.readthedocs.io/en/stable/core-chain.html#snapshots For testing, sometimes the effect of previous tests needs to be undone, before new cases. This can be done with snapshots and undoing/redoing transactions.