Skip to main content

Vault Implementation Guide

Vault Implementation Guide

deposit/mint guide

  1. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account from which the asset tokens will be pulled). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the deposit owner or the account that was approved to withdraw/redeem). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account taking on the debt). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account from which the asset tokens will be pulled). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the from account or the account that was approved to transfer). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the account that will receive the debt). 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. Use callThroughEVC modifier
  2. Ensure re-entrancy protection
  3. Authorize the appropriate account (the liquidator account). 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 enough time has elapsed since the last successful Account Status Check of the violator
  8. Ensure that the collateral to be liquidated is accepted and trusted
  9. Ensure that the violator is indeed in violation
  10. Take the snapshot of the initial vault state if not taken yet in this context
  11. 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
  12. Require the Account Status Check on the liquidator account
  13. Require the Vault Status Check

checkAccountStatus guide

  1. Use onlyEVCWithChecksInProgress modifier
  2. Calculate the collateral and liability value
  3. Return the magic value if the account is healthy

checkVaultStatus guide

  1. Use onlyEVCWithChecksInProgress modifier
  2. Ensure that the snapshot status is valid
  3. Compare the snapshot with the current vault state (invariant check, supply/borrow cap enforcement, etc.). Ensure your vault is sound as a whole.
  4. Clear the old snapshot
  5. 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 and ERC4626.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 the EVCUtil 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()
}

...

}