Vault Implementation Considerations
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Vault Specification
A vault, in order to be compatible with the EVC, MUST implement IVault
interface. Additionally, the implementation of the following specifications MUST be considered:
-
A Vault MAY either be allowed to be called through the EVC or MAY be allowed to be called both through the EVC and directly. When the Vault is called through the EVC, it MUST rely on the
getCurrentOnBehalfOfAccount
function for the currently authenticated Account on behalf of which the operation is being performed. The Vault MUST consider this the true value ofmsg.sender
for authorization purposes. If the Vault is called directly, the Vault MAY perform the authentication itself, and OPTIONALLY use thecall
function on the EVC in the callback manner.NOTE: You can use the
_msgSender
and_msgSenderForBorrow
functions from theEVCUtil
contract to retrieve the correct sender address depending on the operation. -
In more complex cases, to avoid status-check-related issues, it is advised to use the
call
function in the callback manner (meaning a Vault can call back itself usingcall
) when the Vault is called directly. It will ensure the Account and Vault Status Checks are always deferred, at least until the end of thecall
.NOTE: You can use the
callThroughEVC
modifier from theEVCUtil
contract to ensure that the Account and Vault Status Checks are always deferred. -
If not using the
callThroughEVC
modifier from theEVCUtil
contract, before usingcall
in the callback manner, the Vault is REQUIRED to authenticate themsg.sender
. It is strongly advised to pass themsg.sender
address asonBehalfOfAccount
input of thecall
function unless there's a legitimate reason not to do so.NOTE: There is a subtle complication that vault implementations should consider if they use re-entrancy guards (which is recommended). When a vault is invoked without Status Checks being deferred (i.e. vault called directly, not via the EVC), if it calls
require(Account|Vault)StatusCheck
on the EVC, the EVC will immediately call back into the vault'scheck(Account|Vault)Status
function. A normal re-entrancy guard would fail upon re-entering at this point. To avoid this, vaults may wish to use thecall
EVC function or the vault implementation should relax the re-entrancy modifier to allowcheck(Account|Vault)Status
call while invokingrequire(Account|Vault)StatusCheck
. -
In order to support sub-accounts, operators, collateral control, and permits, a Vault MUST be invoked via the EVC.
WARNING: Care MUST be taken not to transfer any assets to the Accounts other than the Account Owner (ID 0). Otherwise, the assets may be lost. If unsure, a vault SHOULD call
getAccountOwner()
function that returns an address of the Account Owner if it's already registered on the EVC. -
When a user requests to perform an operation such as borrow, a Vault MUST call into the EVC to ensure that the Account has enabled this Vault as a Controller. For that purpose
getCurrentOnBehalfOfAccount
orisControllerEnabled
functions MUST be used (this is done by the_msgSenderForBorrow
function from theEVCUtil
contract). -
Due to the fact that only the Controller can disable itself for the Account, a Vault MUST implement a standard
disableController
function that can be called by a user to disable the Controller if vault-specific conditions are met (typically, the Vault SHOULD check whether the Account has repaid its debt in full). If the vault-specific conditions are not met, the function MUST revert.WARNING: If a vault attempted to read the collateral or controller sets of an account in order to enforce some policy of its own, then it is possible that a user could defer checks in a batch to be able to violate them to satisfy the vault's policy transiently. For this reason, vaults SHOULD NOT rely on an account's controller or collateral sets if checks are deferred for this account. This also applies to the amount of deposits and borrows the account has.
-
After each operation potentially affecting the Account's solvency (also on a non-borrowable Vault), a Vault MUST invoke the EVC in order to require the Account Status Check. The EVC determines whether the check should be deferred or performed immediately.
-
Vault MUST implement
checkAccountStatus
function, which gets called for the accounts that have enabled this Vault as Controller. The function receives the Account address and the list of Collateral Vaults enabled for the Account. The function MUST determine whether or not the Account is in an acceptable state by returning the magic value or throwing an exception. If the Vault is deposit-only, the function SHOULD always return the appropriate magic value unless there's a need to implement custom logic.NOTE: It is recommended to decorate the
checkAccountStatus
function with theonlyEVCWithChecksInProgress
modifier from theEVCUtil
contract to ensure that the function is only called in the expected context. -
checkAccountStatus
is not called if there is no Controller enabled for the Account at the time of the checks or when the only Controller was disabled before the checks were performed.WARNING: Allowing a large set of collateral assets for a vault may be tempting. However, vault creators MUST be careful about which assets they accept. A malicious vault could lie about the amount of assets it holds or reject liquidations when a user is in violation. For this reason, vaults should restrict allowed collaterals to a known-good set of audited addresses or look up the addresses in a registry or factory contract to ensure they were created by known-good, audited contracts.
-
Vault MAY implement
checkVaultStatus
function, which gets called if the Vault requires that via the EVC. The EVC determines whether the check should be deferred or performed immediately. This OPTIONAL functionality allows the Vault to enforce its constraints (like supply/borrow caps, invariants checks, etc.). If implemented, the Vault Status Check MUST always be required by the Vault after each operation affecting the Vault's state. ThecheckVaultStatus
function MUST determine whether or not the Vault is in an acceptable state by returning the magic value or throwing an exception. If the Vault doesn't implement this function, the Vault MUST NOT require the Vault Status Check.NOTE: It is recommended to decorate the
checkVaultStatus
function with theonlyEVCWithChecksInProgress
modifier from theEVCUtil
contract to ensure that the function is only called in the expected context. -
To evaluate the Vault Status,
checkVaultStatus
MAY need access to a snapshot of the initial Vault state, which SHOULD be taken at the beginning of the action that requires the check. -
If the
onlyEVCWithChecksInProgress
modifier from theEVCUtil
contract is not used, it MAY be critical to protectcheck(Account|Vault)Status
functions against re-entrancy (depends on individual implementation). Additionally, the following check MUST be included as well:require(msg.sender == address(evc) && evc.areChecksInProgress());
-
Both Account and Vault Status Check MUST be required at the very end of the operation unless the Vault enforces that Checks are always deferred when required (i.e. by using
callThroughEVC
modifier). This is in order to ensure that the checks are performed after the operation is complete. -
The Controller MAY need to forgive the Account Status Check in order for the operation to succeed, i.e. during liquidations. However, the Account Status Check forgiveness MUST be done with care by ensuring that the account for which the Account Status Check will be forgiven hadn't been unexpectedly deferred before the forgiving operation. This is to ensure that the account did not perform any unexpected/malicious operations before the forgiveness.
-
To ensure only the standard authentication path, if the call is made through the EVC, all the access-controlled functions SHOULD enforce that the caller is the EVC account owner.
NOTE: It is recommended to decorate the access-controlled functions with the
onlyEVCAccountOwner
modifier from theEVCUtil
contract to ensure that the function is only called by the account owner in the EVC context. -
Vault MUST NOT allow calls to arbitrary contracts in an uncontrolled manner, including the EVC.