Call
The call
function on the EVC allows users to invoke functions on vaults and other target smart contracts, including the EVC itself. Unless the msg.sender
is the same as the onBehalfOfAccount
, users must go through this function rather than calling the vaults directly. This is because vaults themselves don't understand sub-accounts or operators, and defer their authorization logic to the EVC.
call
also allows users to invoke arbitrary contracts, with arbitrary calldata. These other contracts will see the EVC as msg.sender
. For this reason, it is critical that the EVC itself never be given any special privileges, or hold any token or native currency balances (except for a few corner cases where it is temporarily safe).
If the target contract is the EVC, in order to preserve the msg.sender
, the EVC will be self-called using the delegatecall
.
If the target contract is NOT msg.sender
, the EVC will only allow the target contract to be called if the msg.sender
is the owner or the operator of the onBehalfOfAccount
provided. If that condition is met, the EVC will create a context and call into the target contract with the provided calldata and the onBehalfOfAccount
account set in the context.
Because vaults can be called directly without going through the EVC, checks may not be deferred when they are invoked. In that case, vaults can use the call
function so that they can assume that they are always executing within a checks deferred context. If the calling vault specifies the target contract to be their own address (target contract is msg.sender
), the EVC will create a context and call back into the caller with the provided calldata and the onBehalfOfAccount
account set to whatever was provided by the calling vault. The vault should use msg.sender
as onBehalfOfAccount
. In theory a vault could supply any address, but the only other contract that will see this onBehalfOfAccount
is the vault itself: recall that the onBehalfOfAccount
should only be trusted when msg.sender
is the EVC itself. To use call
in this manner, it is recommended that vaults use a special modifier callThroughEVC
before its re-entrancy guard modifier. This will take care of routing the calls through the EVC, and the vault can operate under the assumption that the checks are always deferred.
The call
function also allows to forward the provided value (or full balance of the EVC if max uint256
was specified). Therefore it can also be used to recover any remaining value in the EVC.
/// @notice Ensures that the caller is the EVC by using the EVC call functionality if necessary.
modifier callThroughEVC() {
if (msg.sender == address(evc)) {
_;
} else {
bytes memory result = evc.call(address(this), msg.sender, 0, msg.data);
assembly {
return(add(32, result), mload(result))
}
}
}