Skip to main content

Vault Implementation Guide

Vault Implementation Guide

deposit/mint guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account from which the asset tokens will be pulled) depending on whether the vault is being called directly or through the EVC. Use _msgSender for that
  4. Take the snapshot of the initial vault state if not taken yet in this context
  5. Perform the operation
  6. Require the Vault Status Check

withdraw/redeem guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the deposit owner or the account that was approved to withdraw/redeem) depending on whether the vault is being called directly or through the EVC. Use _msgSender for that
  4. Ensure that the assets receiver is a valid EOA or contract. Use getAccountOwner function of the EVC for that
  5. Take the snapshot of the initial vault state if not taken yet in this context
  6. Perform the operation
  7. Require the Account Status Check on the deposit owner
  8. Require the Vault Status Check

borrow guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account taking on the debt) depending on whether the vault is being called directly or through the EVC. Use _msgSenderForBorrow for that
  4. Check whether the account taking on the debt has enabled the vault as a controller
  5. Ensure that the assets receiver is a valid EOA or contract. Use getAccountOwner function of the EVC for that
  6. Take the snapshot of the initial vault state if not taken yet in this context
  7. Perform the operation
  8. Require the Account Status Check on the account which took on the debt
  9. Require the Vault Status Check

repay guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account from which the asset tokens will be pulled) depending on whether the vault is being called directly or through the EVC. Use _msgSender for that
  4. Take the snapshot of the initial vault state if not taken yet in this context
  5. Perform the operation
  6. Require the Vault Status Check

Shares transfer guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the from account or the account that was approved to transfer) depending on whether the vault is being called directly or through the EVC. Use _msgSender for that
  4. Take the snapshot of the initial vault state if not taken yet in this context
  5. Perform the operation
  6. Require the Account Status Check on the from account
  7. Require the Vault Status Check

Debt transfer guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account that will receive the debt) depending on whether the vault is being called directly or through the EVC. Use _msgSenderForBorrow for that
  4. Check whether the account that will receive the debt has enabled the vault as a controller
  5. Take the snapshot of the initial vault state if not taken yet in this context
  6. Perform the operation
  7. Require the Account Status Check on the to account
  8. Require the Vault Status Check

liquidation guide

  1. Consider using callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the liquidator account) depending on whether the vault is being called directly or through the EVC. Use _msgSenderForBorrow for that
  4. Check whether the liquidator has enabled the vault as a controller
  5. Ensure that the liquidator is not liquidating itself
  6. Ensure that the violator does not have the Account Status Check deferred
  7. Ensure that the collateral to be liquidated is accepted and trusted
  8. Ensure that the violator is indeed in violation
  9. Take the snapshot of the initial vault state if not taken yet in this context
  10. 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
  11. Require the Account Status Check on the liquidator account
  12. Require the Vault Status Check

checkAccountStatus guide

  1. Check whether it's the EVC calling
  2. Check whether checks are in progress
  3. Calculate the collateral and liability value
  4. Return the magic value if the account is healthy

checkVaultStatus guide

  1. Check whether it's the EVC calling
  2. Check whether checks are in progress
  3. Ensure that the snapshot status is valid
  4. Compare the snapshot with the current vault state (invariant check, supply/borrow cap enforcement, etc.). Ensure your vault is sound as a whole.
  5. Clear the old snapshot
  6. 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 actions 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 and ERC4626.convertToAssets functions as they may be relied upon by other vaults in the ecosystem

Typical implementation pattern of the EVC-compliant function

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.requireAccountStatusCheck(account);
EVC.requireVaultStatusCheck();
}