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. -
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
. -
Before using
call
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. -
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.
-
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. -
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. -
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. -
It MAY be critical to protect
check(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). -
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 forgiven Account's Status Check hadn't been deferred before the operation and that the call requiring the Account Status Check (i.e., ControlCollateral) does not perform any unexpected/malicious operations down the call path.
-
Vault MUST NOT allow calls to arbitrary contracts in an uncontrolled manner, including the EVC.