CallbackHandler
Last updated
Last updated
Inherits: ICallbackHandler
Handles callback validation and execution for vault operations. This contract is designed to be used as a mixin in BaseVault, providing the ability to register logic for safely handling callbacks during guardian submissions. A common use case for handlers is receiving a flash loan. To receive a flashloan, the vault has to cede control when requesting a flashloan and then atomically handle the callback to repay the flashloan This requires two capabilities: the ability to register new handlers and the ability to initiate additional operations in the handle while being restricted by the merkle tree constraints. The callback handler contract achieves this by allowing guardians to "prepare" for a callback when they construct a given operation. If the operation "has a callback" then the fallback function in this contract will handle it. It will use transient storage to preserve information such as the expected callback caller, function selector of the callback and any approvals that are created during the callback
Uses transient storage to manage callback state and approvals
ERC7201-compliant transient storage slot for storing the next authorized selector + caller
Equal to keccak256(abi.encode(uint256(keccak256("aera.callbackHandler.call")) - 1)) & ~bytes32(uint256(0xff));
Note: security: Critical for callback validation
bytes32 internal constant CALLBACK_CALL_SLOT = 0xa48fd101fc9f41f09dc754b3b14722487070ffbd61259b49558564a3296a3f00;
ERC7201-compliant transient storage slot for storing the callback merkle root
Equal to keccak256(abi.encode(uint256(keccak256("aera.callbackHandler.merkleRoot")) - 1) & ~bytes32(uint256(0xff));
Note: security: Critical for callback validation
bytes32 internal constant CALLBACK_MERKLE_ROOT_SLOT = 0x30fb041442610fd0a22e4654f60ea1c715088ef7320b5ec0c4e75cbdd99dbe00;
ERC7201-compliant transient storage slot for storing the approval tracking
Equal to keccak256(abi.encode(uint256(keccak256("aera.callbackHandler.approvals")) - 1)) & ~bytes32(uint256(0xff));
Note: security: Critical for tracking token approvals during callbacks
bytes32 internal constant APPROVALS_SLOT = 0xba2cfcc1b17a97110b1fb218b61c42c0e510c6913e669a69d4ade619ace66c00;
Handle incoming callbacks and validates their authorization
Extracts callback data and forwards to _handleCallbackOperations if valid
fallback(bytes calldata) external returns (bytes memory returnValue);
Internal handler for validated callbacks
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 bytes Static return data │ └───────────────────────────────────────────────────────────────────────────────────────────────────────┘
function _handleCallbackOperations(bytes32 root, uint256 cursor) internal virtual 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
Whitelist a function selector and caller as a valid callback
Uses transient storage to store the callback data
function _allowCallback(bytes32 root, uint256 packedCallbackData) internal;
Parameters
root
bytes32
The merkle root of the callback
packedCallbackData
uint256
Packed data containing caller, selector, and offsets
Store approvals for the current callback context
Uses transient storage to track approvals during callback execution
Length of the array, packed with the token address will be stored in the first slot
All other elements are laid out sequentially after the first slot, taking 2 slots per approval
If there are existing approvals, we will update length in the slot zero and append new approvals
function _storeCallbackApprovals(Approval[] memory approvals, uint256 length) internal;
Parameters
approvals
Approval[]
Array of token approvals to store
length
uint256
Length of the array
Retrieve the currently allowed callback data
Store packed token and length in the zero slot, and spender in the second
Update the length and preserve the token in the zero slot Minus one to compensate for pre-increment in upcoming storage loop
Unpacks data from transient storage
Note: security: Critical for callback validation
function _getAllowedCallback() internal returns (address caller, bytes4 selector, uint16 userDataOffset);
Returns
caller
address
The authorized caller address
selector
bytes4
The authorized function selector
userDataOffset
uint16
The offset in calldata where user data begins
Retrieves the currently allowed merkle root
Unpacks data from transient storage
Note: security: Critical for callback validation
function _getAllowedMerkleRoot() internal returns (bytes32 root);
Returns
root
bytes32
The authorized merkle root
Retrieves the current callback approvals
Decodes approvals from transient storage
The first slot contains the length of the array, packed with the token address
All other elements are laid out sequentially after the first slot, taking 2 slots per approval
Only length slot is cleared, the rest of the approvals are left in the transient storage
This is safe because even if new approvals are added, old ones will be overwritten for length slots
function _getCallbackApprovals() internal returns (Approval[] memory approvals);
Returns
approvals
Approval[]
Array of current token approvals
Checks if an expected callback has been called
If callback was expected but not received, CALLBACK_CALL_SLOT will not be reset to 0
function _hasCallbackBeenCalled() internal view returns (bool);
Returns
<none>
bool
True if an expected callback has been called, false otherwise
Unpacks callback data from a packed uint256
function _unpackCallbackData(uint256 packed)
private
pure
returns (address target, bytes4 selector, uint16 dataOffset);
Parameters
packed
uint256
The packed uint256 containing callback data
Returns
target
address
The target address
selector
bytes4
The function selector
dataOffset
uint16
The offset in calldata where user data begins
Packs a token address and length into a uint256
Used in transient storage slot zero
length
is required to be less than type(uint96).max + 1
function _packLengthAndToken(uint256 length, address token) private pure returns (uint256);
Parameters
length
uint256
The length of the approvals array
token
address
The token address
Returns
<none>
uint256
packed The packed uint256