BaseVault
Last updated
Last updated
Inherits: IBaseVault, Pausable, CallbackHandler, ReentrancyGuardTransient, Auth2Step, IERC721Receiver
This contract embeds core Aera platform functionality: the ability to enlist off-chain guardians to take guarded actions on a vault. It is meant to either be extended with deposit/withdraw capabilities for users or used directly. When used directly, a depositor can simply transfer assets to the vault and a guardian can transfer them out when needed Registered guardians call the submit function and trigger vault operations. The vault may run before and after submit hooks and revert if a guardian is using an unauthorized operation. Authorized operations are configured in an off-chain merkle tree and guardians need to provide a merkle proof for each operation. In addition to validating operation targets (the contract and function being called), the merkle tree can maintain custom per-operation hooks that extract specific parts of the calldata for validation or even perform (possibly stateful) validation during the submit call
ERC7201-compliant transient storage slot for the current hook call type flag
Equal to keccak256(abi.encode(uint256(keccak256("aera.basevault.hookCallType")) - 1)) & ~bytes32(uint256(0xff));
bytes32 internal constant HOOK_CALL_TYPE_SLOT = 0xb8706f504833578f7e830b12e31c3cfba31669a85b02596177f00c6a7faf6e00;
The whitelist contract that controls vault permissions
IWhitelist public immutable WHITELIST;
Address of the submit hooks contract for vault-level operations
ISubmitHooks public submitHooks;
Enumerable map of each guardian address to their merkle root
EnumerableMap.AddressToBytes32Map internal guardianRoots;
Ensures caller either has auth authorization requiresAuth (owner or authorized role) or is a guardian
modifier onlyAuthOrGuardian();
constructor() Pausable() Auth2Step(msg.sender, Authority(address(0)));
Receive function to allow the vault to receive native tokens
receive() external payable;
Submit a series of operations to the vault
/// @notice Submit a series of operations to the vault
/// @param data Encoded array of operations to submit
/// ┌─────────────────────────────┬─────────────────────────┬───────────────────────────────────────────────┐
/// │ FIELDS │ SIZE │ DESCRIPTION │
/// ├─────────────────────────────┴─────────────────────────┴───────────────────────────────────────────────┤
/// │ operationsLength 1 byte Number of operations in the array │
/// │ │
/// │ [for each operation]: │
/// │ │
/// │ SIGNATURE │
/// │ target 20 bytes Target contract address │
/// │ calldataLength 2 bytes Length of calldata │
/// │ calldata <calldataLength> bytes Calldata (before pipelining) │
/// │ │
/// │ CLIPBOARD │
/// │ clipboardsLength 1 byte Number of clipboards │
/// │ [for each clipboard entry]: │
/// │ resultIndex 1 byte Which operation to take from │
/// │ copyWord 1 byte Which word to copy │
/// │ pasteOffset 2 bytes What offset to paste it at │
/// │ │
/// │ CALL TYPE │
/// │ isStaticCall 1 byte 1 if static, 0 if a regular call │
/// │ [if isStaticCall == 0]: │
/// │ │
/// │ CALLBACK HANDLING │
/// │ hasCallback 1 byte Whether to allow callbacks during operation │
/// │ [if hasCallback == 1]: │
/// │ callbackData = 26 bytes Expected callback info │
/// │ ┌────────────────────┬──────────────────────────┬───────────────────┐ │
/// │ │ selector (4 bytes) │ calldataOffset (2 bytes) │ caller (20 bytes) │ │
/// │ └────────────────────┴──────────────────────────┴───────────────────┘ │
/// │ │
/// │ HOOKS │
/// │ hookConfig = 1 byte Hook configuration │
/// │ ┌─────────────────┬────────────────────────────────────────┐ │
/// │ │ hasHook (1 bit) │ configurableHookOffsetsLength (7 bits) │ │
/// │ └─────────────────┴────────────────────────────────────────┘ │
/// │ if configurableHookOffsetsLength > 0: │
/// │ configurableHookOffsets 32 bytes Packed configurable hook offsets │
/// │ if hasHook == 1: │
/// │ hook 20 bytes Hook contract address │
/// │ │
/// │ MERKLE PROOF │
/// │ proofLength 1 byte Merkle proof length │
/// │ proof <proofLength> * 32 bytes Merkle proof data │
/// │ │
/// │ PAYABILITY │
/// │ hasValue 1 byte Whether to send native token with the call │
/// │ [if hasValue == 1]: │
/// │ value 32 bytes Amount of native token to send │
/// └───────────────────────────────────────────────────────────────────────────────────────────────────────┘
function submit(bytes calldata data) external whenNotPaused nonReentrant;
Parameters
data
bytes
Set the merkle root for a guardian Used to add guardians and update their permissions
function setGuardianRoot(address guardian, bytes32 root) external virtual requiresAuth;
Parameters
guardian
address
Address of the guardian
root
bytes32
Merkle root
Removes a guardian from the vault
function removeGuardian(address guardian) external virtual requiresAuth;
Parameters
guardian
address
Address of the guardian
Check if the guardian is whitelisted and set the root to zero if not Used to disable guardians who were removed from the whitelist after being selected as guardians
function checkGuardianWhitelist(address guardian) external returns (bool isRemoved);
Parameters
guardian
address
The guardian address
Returns
isRemoved
bool
Whether the guardian was removed from the whitelist
Set the submit hooks address
function setSubmitHooks(ISubmitHooks newSubmitHooks) external virtual requiresAuth;
Parameters
newSubmitHooks
ISubmitHooks
Address of the new submit hooks contract
Pause the vault, halting the ability for guardians to submit
function pause() external onlyAuthOrGuardian;
Unpause the vault, allowing guardians to submit operations
function unpause() external requiresAuth;
Get all active guardians
function getActiveGuardians() external view returns (address[] memory);
Returns
<none>
address[]
Array of active guardian addresses
Get the guardian root for a guardian
function getGuardianRoot(address guardian) external view returns (bytes32);
Parameters
guardian
address
The guardian address
Returns
<none>
bytes32
The guardian root
Get the current hook call type
function getCurrentHookCallType() external view returns (HookCallType);
Returns
<none>
HookCallType
The current hook call type
Whenever an {IERC721} tokenId
token is transferred to this contract via {IERC721-safeTransferFrom} by operator
from from
, this function is called. It must return its Solidity selector to confirm the token transfer. If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. The selector can be obtained in Solidity with IERC721Receiver.onERC721Received.selector
.
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4);
/// @notice Internal handler for validated callbacks
/// @dev Callback operations are like regular operations, but with a return value, which are encoded after
/// operations array
/// ┌─────────────────────────────┬─────────────────────────┬───────────────────────────────────────────────┐
/// │ FIELDS │ SIZE │ DESCRIPTION │
/// ├─────────────────────────────┴─────────────────────────┴───────────────────────────────────────────────┤
/// │ returnTypeFlag 1 byte 0 = no return, 1 = static, 2 = dynamic │
/// │ [if returnTypeFlag == 1]: │
/// │ returnDataLength 2 bytes Length of return data │
/// │ returnData <returnDataLength> bytes Static return data │
/// └───────────────────────────────────────────────────────────────────────────────────────────────────────┘
/// @param root The merkle root of the callback
/// @param cursor The cursor to the callback data
/// @return returnValue The return value of the callback
function _handleCallbackOperations(bytes32 root, uint256 cursor)
internal
virtual
override
returns (bytes memory returnValue);
Parameters
root
bytes32
The merkle root of the callback
cursor
uint256
The cursor to the callback data
Returns
returnValue
bytes
The return value of the callback
Prepare for a callback if the guardian expects one
Writes to transient storage to encode callback expectations
function _processExpectedCallback(CalldataReader reader, bytes32 root) internal returns (CalldataReader, uint208);
Parameters
reader
CalldataReader
Current position in the calldata
root
bytes32
The merkle root of the active guardian that triggered the callback
Returns
<none>
CalldataReader
Updated cursor position
<none>
uint208
Packed callback data
Call the before submit hooks if defined
Submit hooks passed as an argument to reduce storage loading
function _beforeSubmitHooks(address hooks, bytes calldata data) internal;
Parameters
hooks
address
Address of the submit hooks contract
data
bytes
Calldata to pass to the before submit hooks
Call the after submit hooks if defined
Submit hooks passed as an argument to reduce storage loading
function _afterSubmitHooks(address hooks, bytes calldata data) internal;
Parameters
hooks
address
Address of the submit hooks contract
data
bytes
Calldata to pass to the after submit hooks
Call the before operation hooks if defined
function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i)
internal
returns (bytes memory result);
Parameters
operationHooks
address
Address of the operation-specific hooks
data
bytes
Operation calldata
i
uint256
Operation index
Returns
result
bytes
Result of the hooks call
Call the after operation hooks if defined
function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal;
Parameters
operationHooks
address
Address of the operation-specific hooks
data
bytes
Operation calldata
i
uint256
Operation index
Executes a series of operations
Approvals are tracked so we can verify if they have been zeroed out at the end of submit
function _executeSubmit(bytes32 root, CalldataReader reader, bool isCalledFromCallback)
internal
returns (Approval[] memory approvals, uint256 approvalsLength, bytes[] memory results, CalldataReader newReader);
Parameters
root
bytes32
The merkle root of the active guardian that triggered the callback
reader
CalldataReader
Current position in the calldata
isCalledFromCallback
bool
Whether the submit is called from a callback
Returns
approvals
Approval[]
Array of outgoing approvals created during execution
approvalsLength
uint256
Length of approvals array
results
bytes[]
Array of results from the operations
newReader
CalldataReader
Updated cursor position
Processes all hooks for operation
Returns extracted data if configurable or contract before operation hooks is defined
Custom hooks (with contracts) can run before and after each operation but a configurable hooks can only run before an operation. This function processes all of the possible configurations of hooks which doesn't allow using both a custom before hook and a configurable before hook
function _processBeforeOperationHooks(CalldataReader reader, bytes memory callData, uint256 i)
internal
returns (CalldataReader, bytes memory, uint256, address);
Parameters
reader
CalldataReader
Current position in the calldata
callData
bytes
Operation calldata
i
uint256
Operation index
Returns
<none>
CalldataReader
reader Updated reader position
<none>
bytes
extractedData Extracted chunks of calldata
<none>
uint256
hooksConfigBytes hooks configuration bytes
<none>
address
operationHooks Operation hooks address
Set the submit hooks address
function _setSubmitHooks(ISubmitHooks submitHooks_) internal;
Parameters
submitHooks_
ISubmitHooks
Address of the submit hooks contract
Set the guardian root
function _setGuardianRoot(address guardian, bytes32 root) internal virtual;
Parameters
guardian
address
Address of the guardian
root
bytes32
Merkle root
Set the hook call type
function _setHookCallType(HookCallType hookCallType) internal;
Parameters
hookCallType
HookCallType
The hook call type
Verify no pending approvals remain at the end of a submit
We iterate backwards to avoid extra i variable
While loop is preferred over for(;approvalsLength != 0;)
Iterator variable is not used because it's not needed and decrement needs to be unchecked
function _noPendingApprovalsInvariant(Approval[] memory approvals, uint256 approvalsLength) internal view;
Parameters
approvals
Approval[]
Array of approvals to check
approvalsLength
uint256
Length of approvals array
Get the return value from the operations
function _getReturnValue(CalldataReader reader, bytes[] memory results)
internal
pure
returns (CalldataReader newReader, bytes memory returnValue);
Parameters
reader
CalldataReader
Current position in the calldata
results
bytes[]
Array of results from the operations
Returns
newReader
CalldataReader
Updated reader position
returnValue
bytes
Return value from the operations
Verify an operation by validating the merkle proof
function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure;
Parameters
proof
bytes32[]
The merkle proof
root
bytes32
The merkle root
leaf
bytes32
The merkle leaf to verify
Create a merkle leaf
function _createMerkleLeaf(OperationContext memory ctx, bytes memory extractedData) internal pure returns (bytes32);
Parameters
ctx
OperationContext
The operation context
extractedData
bytes
The extracted data
Returns
<none>
bytes32
leaf The merkle leaf
Extract spender address from approval data
Extract spender address from approval data
function _extractApprovalSpender(bytes memory data) internal pure returns (address spender);
Parameters
data
bytes
Approval calldata
Returns
spender
address
Address of the spender
Check if hooks needs to be called before the submit/operation
Check if hooks needs to be called before the submit/operation
function _hasBeforeHooks(address hooks) internal pure returns (bool);
Parameters
hooks
address
Hooks address to check
Returns
<none>
bool
True if hooks needs to be called before the submit/operation
least significant bit is 1 indicating it's a before hooks
Check if submit hooks needs to be called after the submit/operation
Check if submit hooks needs to be called after the submit/operation
function _hasAfterHooks(address hooks) internal pure returns (bool);
Parameters
hooks
address
Submit hooks address to check
Returns
<none>
bool
True if submit hooks needs to be called after the submit/operation
second least significant bit is 1 indicating it's a after hooks
Check if the selector is an allowance handling selector
Check if the selector is an allowance handling selector
function _isAllowanceSelector(bytes4 selector) internal pure returns (bool);
Parameters
selector
bytes4
Selector to check
Returns
<none>
bool
True if the selector is an allowance handling selector