BaseVault

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

State Variables

HOOK_CALL_TYPE_SLOT

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;

WHITELIST

The whitelist contract that controls vault permissions

IWhitelist public immutable WHITELIST;

submitHooks

Address of the submit hooks contract for vault-level operations

ISubmitHooks public submitHooks;

guardianRoots

Enumerable map of each guardian address to their merkle root

EnumerableMap.AddressToBytes32Map internal guardianRoots;

Functions

onlyAuthOrGuardian

Ensures caller either has auth authorization requiresAuth (owner or authorized role) or is a guardian

modifier onlyAuthOrGuardian();

constructor

constructor() Pausable() Auth2Step(msg.sender, Authority(address(0)));

receive

Receive function to allow the vault to receive native tokens

receive() external payable;

submit

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

Name
Type

data

bytes

setGuardianRoot

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

Name
Type
Description

guardian

address

Address of the guardian

root

bytes32

Merkle root

removeGuardian

Removes a guardian from the vault

function removeGuardian(address guardian) external virtual requiresAuth;

Parameters

Name
Type
Description

guardian

address

Address of the guardian

checkGuardianWhitelist

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

Name
Type
Description

guardian

address

The guardian address

Returns

Name
Type
Description

isRemoved

bool

Whether the guardian was removed from the whitelist

setSubmitHooks

Set the submit hooks address

function setSubmitHooks(ISubmitHooks newSubmitHooks) external virtual requiresAuth;

Parameters

Name
Type
Description

newSubmitHooks

ISubmitHooks

Address of the new submit hooks contract

pause

Pause the vault, halting the ability for guardians to submit

function pause() external onlyAuthOrGuardian;

unpause

Unpause the vault, allowing guardians to submit operations

function unpause() external requiresAuth;

getActiveGuardians

Get all active guardians

function getActiveGuardians() external view returns (address[] memory);

Returns

Name
Type
Description

<none>

address[]

Array of active guardian addresses

getGuardianRoot

Get the guardian root for a guardian

function getGuardianRoot(address guardian) external view returns (bytes32);

Parameters

Name
Type
Description

guardian

address

The guardian address

Returns

Name
Type
Description

<none>

bytes32

The guardian root

getCurrentHookCallType

Get the current hook call type

function getCurrentHookCallType() external view returns (HookCallType);

Returns

Name
Type
Description

<none>

HookCallType

The current hook call type

onERC721Received

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

_handleCallbackOperations

/// @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

Name
Type
Description

root

bytes32

The merkle root of the callback

cursor

uint256

The cursor to the callback data

Returns

Name
Type
Description

returnValue

bytes

The return value of the callback

_processExpectedCallback

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

Name
Type
Description

reader

CalldataReader

Current position in the calldata

root

bytes32

The merkle root of the active guardian that triggered the callback

Returns

Name
Type
Description

<none>

CalldataReader

Updated cursor position

<none>

uint208

Packed callback data

_beforeSubmitHooks

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

Name
Type
Description

hooks

address

Address of the submit hooks contract

data

bytes

Calldata to pass to the before submit hooks

_afterSubmitHooks

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

Name
Type
Description

hooks

address

Address of the submit hooks contract

data

bytes

Calldata to pass to the after submit hooks

_beforeOperationHooks

Call the before operation hooks if defined

function _beforeOperationHooks(address operationHooks, bytes memory data, uint256 i)
    internal
    returns (bytes memory result);

Parameters

Name
Type
Description

operationHooks

address

Address of the operation-specific hooks

data

bytes

Operation calldata

i

uint256

Operation index

Returns

Name
Type
Description

result

bytes

Result of the hooks call

_afterOperationHooks

Call the after operation hooks if defined

function _afterOperationHooks(address operationHooks, bytes memory data, uint256 i) internal;

Parameters

Name
Type
Description

operationHooks

address

Address of the operation-specific hooks

data

bytes

Operation calldata

i

uint256

Operation index

_executeSubmit

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

Name
Type
Description

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

Name
Type
Description

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

_processBeforeOperationHooks

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

Name
Type
Description

reader

CalldataReader

Current position in the calldata

callData

bytes

Operation calldata

i

uint256

Operation index

Returns

Name
Type
Description

<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

_setSubmitHooks

Set the submit hooks address

function _setSubmitHooks(ISubmitHooks submitHooks_) internal;

Parameters

Name
Type
Description

submitHooks_

ISubmitHooks

Address of the submit hooks contract

_setGuardianRoot

Set the guardian root

function _setGuardianRoot(address guardian, bytes32 root) internal virtual;

Parameters

Name
Type
Description

guardian

address

Address of the guardian

root

bytes32

Merkle root

_setHookCallType

Set the hook call type

function _setHookCallType(HookCallType hookCallType) internal;

Parameters

Name
Type
Description

hookCallType

HookCallType

The hook call type

_noPendingApprovalsInvariant

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

Name
Type
Description

approvals

Approval[]

Array of approvals to check

approvalsLength

uint256

Length of approvals array

_getReturnValue

Get the return value from the operations

function _getReturnValue(CalldataReader reader, bytes[] memory results)
    internal
    pure
    returns (CalldataReader newReader, bytes memory returnValue);

Parameters

Name
Type
Description

reader

CalldataReader

Current position in the calldata

results

bytes[]

Array of results from the operations

Returns

Name
Type
Description

newReader

CalldataReader

Updated reader position

returnValue

bytes

Return value from the operations

_verifyOperation

Verify an operation by validating the merkle proof

function _verifyOperation(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure;

Parameters

Name
Type
Description

proof

bytes32[]

The merkle proof

root

bytes32

The merkle root

leaf

bytes32

The merkle leaf to verify

_createMerkleLeaf

Create a merkle leaf

function _createMerkleLeaf(OperationContext memory ctx, bytes memory extractedData) internal pure returns (bytes32);

Parameters

Name
Type
Description

ctx

OperationContext

The operation context

extractedData

bytes

The extracted data

Returns

Name
Type
Description

<none>

bytes32

leaf The merkle leaf

_extractApprovalSpender

Extract spender address from approval data

Extract spender address from approval data

function _extractApprovalSpender(bytes memory data) internal pure returns (address spender);

Parameters

Name
Type
Description

data

bytes

Approval calldata

Returns

Name
Type
Description

spender

address

Address of the spender

_hasBeforeHooks

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

Name
Type
Description

hooks

address

Hooks address to check

Returns

Name
Type
Description

<none>

bool

True if hooks needs to be called before the submit/operation

_hasAfterHooks

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

Name
Type
Description

hooks

address

Submit hooks address to check

Returns

Name
Type
Description

<none>

bool

True if submit hooks needs to be called after the submit/operation

_isAllowanceSelector

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

Name
Type
Description

selector

bytes4

Selector to check

Returns

Name
Type
Description

<none>

bool

True if the selector is an allowance handling selector

Last updated