This repository contains tests to ensure that an ERC4626 token is compatible with Balancer V3 buffers. For compatibility, an ERC4626 token must meet the following requirements:
The token must implement all functions defined by the ERC-4626: Tokenized Vaults standard. The tests validate the correct implementation of the following functions:
assetconvertToAssetsconvertToSharespreviewDepositpreviewMintpreviewWithdrawpreviewRedeemdepositmintwithdrawredeem
There are some common errors that occur when testing a token. The step-by-step guide below helps to cover most of them.
- Navigate to the wrapped token address on Etherscan;
- Check whether the Vault's buffer at the current block has liquidity for that token (Use the Vault Explorer's
getBufferBalancefunction with the wrapped token address as the argument.)- If there is, it's probably reverting because the buffer was already initialized. Choose a block number prior to the buffer initialization and try again.
- If the wrapper was recently created, use a block number right after the creation of the wrapper and try again.
- If not, try to put an old block number, closer to the Balancer V3 launch (e.g., 21332121 on Mainnet), where the Vault is already there but the buffer was not yet initialized.
- If there is, it's probably reverting because the buffer was already initialized. Choose a block number prior to the buffer initialization and try again.
- Check the asset of the wrapped token (underlying token) and get the holders.
- Check whether the top holder is the one used in the test.
- If it's not, change the holder to the top one. It may be a bit tricky if you're using an old block number. In that case, you would need to iterate over the holders list until you find a holder with sufficient balance at that specific block.
- Check whether the holder we're using has enough balance to cover 3 * defaultAmount (i.e., 3e6 * 1e18 if default amount is 1e6 * 1e18).
- Check whether the top holder is the one used in the test.
- Finally, if the error persists, try testing it in the balancer-v3-monorepo.
- In your copy of that repo, go to
pkg/vault/test/foundry/fork, then copy and modify theERC4626MainnetAaveUsdc.t.soltest for your token. - Run the tests specific to this file (something like
yarn test:forge --match-contract ERC4626MainnetAaveUsdcTestinside the pkg/vault folder) and check for errors. - If the tests pass, it means that the buffer is initialized at the chosen block number. The test in the monorepo uses a newly deployed Vault: this is the only reason for a test to pass in the monorepo that doesn't pass in the ERC4626 tests repo.
- In your copy of that repo, go to
If the step-by-step instructions above do not help, the token is likely incompatible with the vault, and needs further investigation.
The ERC4626 wrapper should not impose deposit or withdrawal fees. For example, a deposit of amount tokens should
allow the user to withdraw at least amount - 1 tokens.
The convert and preview functions must produce results that are close, with a maximum allowable difference of 1 wei.
The results of preview functions must match the outcomes of their corresponding operations precisely.
The deposit and redeem functions must behave as EXACT_IN functions, consuming the exact amount of tokens specified in their input arguments.
The mint and withdraw functions must behave as EXACT_OUT functions, returning the exact number of tokens specified in their output values.
In terms of all ERC4626 vaults, there is an inherent incompatibility related to flash loans. When an ERC4626 is deposited into a contract that can be flashloaned, an issue arises where a user can frontrun bad debt socialization by flashloaning the shares and then withdrawing them, which amplifies the bad debt. This is limited by the amount available to flashloan AND withdrawable liquidity in the pool.
- Install Yarn
- Install Foundry
- Install dependencies:
yarn install
- Create a
.envfile in the root of the project and add the RPC URL for the chain you want to test. The supported environment variables are listed infoundry.tomlunder[rpc_endpoints]. For example, to test on Mainnet:MAINNET_RPC_URL=https://your-mainnet-rpc-url
-
Export the required environment variables in your terminal:
source .envOr export them directly:
export MAINNET_RPC_URL=https://your-mainnet-rpc-url -
Run the test with
yarn test:forge. Use--match-contractor--mcif you need to target a specific test contract, locally. Also, use-vvvfor debugging, if needed:yarn test:forge --mc ERC4626MainnetMorphoKpkUsdcYield
Replace
ERC4626MainnetMorphoKpkUsdcYieldwith the name of the test contract you want to run (i.e., thecontractname defined in the.t.solfile).
To add a test for a new ERC4626 token, duplicate an existing test file from the corresponding chain folder inside test/ (e.g., test/mainnet/, test/arbitrum/) and update the following:
- Contract name — Rename the contract to match your new file (e.g.,
ERC4626MainnetMyTokenTest). forkState.network— Set this to the chain where the token is deployed (e.g.,"mainnet","arbitrum","base"). The supported networks are listed inERC4626WrapperBase.t.sol.erc4626State.wrapper— Set this to the address of the ERC4626 wrapper (vault) contract you want to test.erc4626State.underlyingDonor— Set this to the address of a whale that holds a large balance of the underlying token (i.e., the asset returned bywrapper.asset()). The donor must hold at least3 * amountToDonateof the underlying token at the fork block number. You can find suitable donors by checking the top holders of the underlying token on a block explorer (e.g., Etherscan).erc4626State.amountToDonate— Adjust if the underlying token has non-standard decimals or if the default1e6 * 1e18is not appropriate.