Vault Implementation Guide
Vault Implementation Guide
deposit
/mint
guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the account from which the asset tokens will be pulled). Use
_msgSender
for that - Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Vault Status Check
withdraw
/redeem
guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the deposit owner or the account that was approved to withdraw/redeem). Use
_msgSender
for that - Ensure that the assets receiver is a valid EOA or contract. Use
getAccountOwner
function of the EVC for that - Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Account Status Check on the deposit owner
- Require the Vault Status Check
borrow
guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the account taking on the debt). Use
_msgSenderForBorrow
for that - Check whether the account taking on the debt has enabled the vault as a controller
- Ensure that the assets receiver is a valid EOA or contract. Use
getAccountOwner
function of the EVC for that - Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Account Status Check on the account which took on the debt
- Require the Vault Status Check
repay
guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the account from which the asset tokens will be pulled). Use
_msgSender
for that - Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Vault Status Check
Shares transfer guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the
from
account or the account that was approved to transfer). Use_msgSender
for that - Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Account Status Check on the
from
account - Require the Vault Status Check
Debt transfer guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the account that will receive the debt). Use
_msgSenderForBorrow
for that - Check whether the account that will receive the debt has enabled the vault as a controller
- Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Require the Account Status Check on the
to
account - Require the Vault Status Check
liquidation
guide
- Use
callThroughEVC
modifier - Ensure re-entrancy protection
- Authorize the appropriate account (the liquidator account). Use
_msgSenderForBorrow
for that - Check whether the liquidator has enabled the vault as a controller
- Ensure that the liquidator is not liquidating itself
- Ensure that the violator does not have the Account Status Check deferred
- Ensure that enough time has elapsed since the last successful Account Status Check of the violator
- Ensure that the collateral to be liquidated is accepted and trusted
- Ensure that the violator is indeed in violation
- Take the snapshot of the initial vault state if not taken yet in this context
- Perform the operation
- Perform all the necessary calculations
- Decrease the violator's debt, increase the liquidator's debt
- Seize the collateral:
- If the collateral is the controller vault: ensure violator enabled this vault as collateral, decrease the violator's balance and increase the liquidator's balance
- If the collateral vault is an external vault: use the
EVC.controlCollateral
functionality to transfer the shares from the violator to the liquidator. If you allow the violator to stay in violation after the liquidation, forgive the Account Status Check on the violator to enable that
- Require the Account Status Check on the liquidator account
- Require the Vault Status Check
checkAccountStatus
guide
- Use
onlyEVCWithChecksInProgress
modifier - Calculate the collateral and liability value
- Return the magic value if the account is healthy
checkVaultStatus
guide
- Use
onlyEVCWithChecksInProgress
modifier - Ensure that the snapshot status is valid
- Compare the snapshot with the current vault state (invariant check, supply/borrow cap enforcement, etc.). Ensure your vault is sound as a whole.
- Clear the old snapshot
- Return the magic value if the vault is healthy
Other considerations
- The vault MUST only be released from being a controller if the account has the debt fully repaid. Implement
IVault.disableController
for that - The vault has the freedom to implement the Account Status Check. It MAY price all the assets according to its preference without depending on potentially untrustworthy oracles. Implement
IVault.checkAccountStatus
for that - The vault has the freedom to implement the Vault Status Check. Depending on the implementation, the initial state snapshotting MAY not be needed. Depending on the implementation, not all the operations MAY require the Vault Status Check (i.e. vault share transfers). If the snapshot is needed, note that it MAY require a prior vault state update (i.e. interest rate accrual). Implement
IVault.checkVaultStatus
for that - One SHOULD be careful when forgiving the Account Status Check after using
EVC.controlCollateral
. A malicious target collateral could manipulate the process leading to the bad debt accrual. To prevent that, one MUST ensure that only trusted collaterals that behave in the expected way are being called using the control collateral functionality - When sending regular ERC20 tokens from a vault to an address, one SHOULD ensure that the address is not a sub-account but a valid EOA/contract address by getting an account owner from the EVC
- If implementing ERC-4626 vault, one should consider implementing manipulation resistant mechanisms for
ERC4626.convertToShares
andERC4626.convertToAssets
functions as they may be relied upon by other vaults in the ecosystem - If the vault requires any access-controlled functions to be implemented (i.e. for governance purposes), one SHOULD decorate them with the
onlyEVCAccountOwner
modifier from theEVCUtil
contract to ensure that the function is only called by the account owner in the EVC context
Typical implementation pattern of the EVC-compliant function
contract EVCAwareContract is EVCUtil {
...
function func() external callThroughEVC nonReentrant {
// retrieve the "true" message sender from the EVC. whether _msgSender or _msgSenderForBorrow should be called,
// depends on whether it's a debt-related functionality
address msgSender = _msgSender(); // or _msgSenderForBorrow()
// accrue the interest before the snapshot if it relies on it. otherwise it can be accrued after or never if
// the vault does not implement the interest accrual
_accrueInterest();
// take the snapshot if the given functionality requires it
takeVaultSnapshot();
// CUSTOM FUNCTION LOGIC HERE
// after all the custom logic has been executed, trigger the account status check and vault status check, if
// the latter one is needed. the account for which the account status check is required may differ and depends on
// the function logic. i.e.:
// - for shares transfer the `from` account should be checked
// - for debt transfer the `to` account should be checked
// - for borrow the account taking on the debt should be checked
// hence, the account checked is not always the `msgSender`
evc.requireAccountAndVaultStatusCheck(account); // or evc.requireAccountStatusCheck(account) and evc.requireVaultStatusCheck()
}
...
}