/// 1. Create the hook natspec
/// @title ApproveAndDeposit4626VaultHook Tutorial
/// @dev data has the following structure
/// @notice bytes32 yieldSourceOracleId = bytes32(BytesLib.slice(data, 0, 32), 0);
/// @notice address yieldSource = BytesLib.toAddress(data, 32);
/// @notice address token = BytesLib.toAddress(data, 52);
/// @notice uint256 amount = BytesLib.toUint256(data, 72);
/// @notice bool usePrevHookAmount = _decodeBool(data, 104);
contract ApproveAndDeposit4626VaultHook is
BaseHook, // 2. Implement inheritance
ISuperHookInflowOutflow,
ISuperHookContextAware
{
using HookDataDecoder for bytes; // 3. Import and setup decoder libraries
uint256 private constant AMOUNT_POS = 72;
uint256 private constant USE_PREV_POS = 104;
// 4. Set categories in constructor
constructor() BaseHook(HookType.INFLOW, HookSubTypes.ERC4626) { }
/* ---------- build() internals ---------- */
// 5. Create the build executions
function _buildHookExecutions(
address prev,
address account,
bytes calldata data
) internal view override returns (Execution[] memory ex) {
// 5.1 Decode data
address yieldSource = data.extractYieldSource();
address token = BytesLib.toAddress(data, 52);
uint256 amount = _decodeAmount(data);
bool usePrevHookAmount = _decodeBool(data, USE_PREV_HOOK_AMOUNT_POSITION);
// 5.2 Hook chaining functionality
if (usePrevHookAmount) {
amount = ISuperHookResult(prevHook).getOutAmount(account);
} // Note: re-use prevHook's output when usePrevHookAmount = true
// 5.3 Data validation
if (amount == 0) revert AMOUNT_NOT_VALID();
if (yieldSource == address(0) || token == address(0)) revert ADDRESS_NOT_VALID();
// 5.4.1 Define Executions
// 4 calls = 4 executions: reset approval → approve → deposit → reset approval
executions = new Execution[](4);
// 5.4.2 Populate execution elements
executions[0] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, 0))
});
executions[1] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, amount))
});
executions[2] =
Execution({
target: yieldSource,
value: 0,
callData: abi.encodeCall(IERC4626.deposit, (amount, account))
});
executions[3] =
Execution({
target: token,
value: 0,
callData: abi.encodeCall(IERC20.approve, (yieldSource, 0))
}); // Security note: zero allowances before/after deposit to avoid "variable-approval" exploit
}
/* ---------- pre / post ---------- */
// 6. Validate & prepare any state-changing amounts required
function _preExecute(
address, address account, bytes calldata data
) internal override {
_setOutAmount(_vaultBal(account, data), account); // snapshot
spToken = data.extractYieldSource();
}
// 7. Finalise, set outAmount/usedShares
function _postExecute(
address, address account, bytes calldata data
) internal override {
uint256 afterBal = _vaultBal(account, data);
_setOutAmount(afterBal - getOutAmount(account), account);
}
/* ---------- helpers ---------- */
function _decodeAmount(bytes memory data) private pure returns (uint256) {
return BytesLib.toUint256(data, AMOUNT_POS);
}
function _vaultBal(address acct, bytes memory d)
private view returns (uint256)
{
return IERC4626(d.extractYieldSource()).balanceOf(acct);
}
}