To use these properties to test a given vault implementation, see the readme in the project root.
Some properties of the ERC4626 spec cannot be tested externally because testing them requires interactions between the test suite & functionality that is not defined in the spec.
To compensate for this limitation, a vault under test may optionally implement a set of methods that allow such properties to be tested. See IERC4626Internal for the list of methods.
These methods should be added to the Vault by a derived, test-environment-only contract to minimize changes to the production contract. When a vault under test implements IERC4626Internal, pass true
to the test harness's initialize()
function to enable the properties that require the internal interface:
contract Vault is IERC4626 { ... }
contract VaultTestable is Vault, IERC4626Internal { ... }
contract TestHarness is CryticERC4626PropertyTests{
constructor(...) {
[...]
initialize(address(_vault), address(_asset), true);
}
}
Check out the rounding property verification contracts if you'd like to see how such an implementation would look.
Before doing any development, run forge install
to get dependencies sorted out. forge build
will not work without the Echidna remappings.
Running tests(used to validate the properties are working correctly):
echidna ./contracts/ERC4626/test/rounding/BadConvertToAssetsRounding.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_previewRedeemRoundingDirection
- verify_redeemRoundingDirection
- verify_convertToAssetsRoundingDirection
echidna ./contracts/ERC4626/test/rounding/BadConvertToSharesRounding.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_convertToSharesRoundingDirection
- verify_previewDepositRoundingDirection
- verify_depositRoundingDirection
echidna ./contracts/ERC4626/test/rounding/BadPreviewMintRounding.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_previewMintRoundingDirection
- verify_mintRoundingDirection
echidna ./contracts/ERC4626/test/rounding/BadPreviewWithdrawRounding.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_previewWithdrawRoundingDirection
- verify_withdrawRoundingDirection
echidna ./contracts/ERC4626/test/security/BadShareInflation.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_sharePriceInflationAttack
echidna ./contracts/ERC4626/test/usingApproval/BadAllowanceUpdate.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
Should cause these properties to fail:
- verify_redeemViaApprovalProxy
- verify_withdrawViaApprovalProxy
- verify_redeemRequiresTokenApproval
- verify_withdrawRequiresTokenApproval
Run property tests against vanilla solmate:
echidna ./contracts/ERC4626/test/Solmate4626.sol --contract TestHarness --config ./contracts/ERC4626/test/echidna.config.yaml
convertToAssets()
must not revert for reasonable valuesconvertToShares()
must not revert for reasonable valuesasset()
must not reverttotalAssets()
must not revertmaxDeposit()
must not revertmaxMint()
must not revertmaxRedeem()
must not revertmaxWithdraw()
must not revert
deposit()
must deduct assets from the ownerdeposit()
must credit shares to the receiverdeposit()
must mint greater than or equal to the number of shares predicted bypreviewDeposit()
mint()
must deduct assets from the ownermint()
must credit shares to the receivermint()
must consume less than or equal to the number of assets predicted bypreviewMint()
withdraw()
must deduct shares from the ownerwithdraw()
must credit assets to the receiverwithdraw()
must deduct less than or equal to the number of shares predicted bypreviewWithdraw()
redeem()
must deduct shares from the ownerredeem()
must credit assets to the receiverredeem()
must credit greater than or equal to the number of assets predicted bypreviewRedeem()
withdraw()
must allow proxies to withdraw tokens on behalf of the owner using share token approvalsredeem()
must allow proxies to redeem shares on behalf of the owner using share token approvals- Third party
withdraw()
calls must update the msg.sender's allowance - Third party
redeem()
calls must update the msg.sender's allowance - Third parties must not be able to
withdraw()
tokens on an owner's behalf without a token approval - Third parties must not be able to
redeem()
shares on an owner's behalf without a token approval
maxDeposit()
must assume the receiver/sender has infinite assetsmaxMint()
must assume the receiver/sender has infinite assetspreviewMint()
must not account for msg.sender asset balancepreviewDeposit()
must not account for msg.sender asset balancepreviewWithdraw()
must not account for msg.sender share balancepreviewRedeem()
must not account for msg.sender share balance
- Shares may never be minted for free using:
previewDeposit()
previewMint()
convertToShares()
- Tokens may never be withdrawn for free using:
previewWithdraw()
previewRedeem()
convertToAssets()
- Shares may never be minted for free using:
deposit()
mint()
- Tokens may never be withdrawn for free using:
withdraw()
redeem()
decimals()
should be larger than or equal toasset.decimals()
- Accounting system must not be vulnerable to share price inflation attacks
- deposit/mint must increase totalSupply/totalAssets
- withdraw/redeem must decrease totalSupply/totalAssets
previewDeposit()
must not account for vault specific/user/global limitspreviewMint()
must not account for vault specific/user/global limitspreviewWithdraw()
must not account for vault specific/user/global limitspreviewRedeem()
must not account for vault specific/user/global limits
- Note that any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing.
- Whether a given method is inclusive of withdraw/deposit fees