Skip to main content

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))
}
}
}