CCTPHookExecutor
Inherits: ICCTPHookExecutor
Title: CCTPHookExecutor
Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Modified from Circle CCTP v2 (https://github.com/circlefin/evm-cctp-contracts)
The role-less, fund-less contract every inbound CCTP hook is executed through — NOT a facet, NOT cut
into the diamond. It exists so that the ONLY thing that ever calls an attacker-named hook target is a
contract with no funds, no roles and no delegatecall reach; attacker bytes in a CCTP burn's hookData
therefore gain nothing beyond a plain EOA's authority. Deployed once per diamond by the adapter's init
(new CCTPHookExecutor(address(this)), delegatecalled so address(this) is the diamond) and stored
immutably — there is deliberately NO setter (a swappable executor would be a forgeable trust anchor).
CCTP does NOT execute hooks; the destination recipient does. This executor is that recipient's
indirection: executeHook is gated to the immutable relay diamond and calls the target with the FIXED
ICCTPHookReceiver.onCCTPHook selector via assembly, forwarding no value, dropping ALL returndata
(return-bomb safe) and NEVER bubbling the target's revert — it returns a plain bool success. The one
exception is a target that exhausts the gas of its OWN frame (CCTPHookOutOfGas): that reverts the whole
relay, so a relayer cannot starve the target's own frame while the mint stands. An out-of-gas inside a
sub-call the target makes is NOT visible here — the target reverts normally and this returns false —
unless the target re-raises it as ICCTPHookReceiver requires. Every context argument the relay passes
is read from the Circle-ATTESTED message, never from hookData.
Constants
relay
The diamond that deployed this executor — the ONLY address permitted to call executeHook.
address public immutable relay;Returns
| Name | Type | Description |
|---|---|---|
| <none> | address |
Functions
constructor
constructor(address relay_);Parameters
| Name | Type | Description |
|---|---|---|
| relay_ | address | The diamond deploying this executor (its only permitted caller). Non-zero by construction — the init passes address(this) (the diamond), which is never zero. |
executeHook
Calls target.onCCTPHook(...) with Circle-attested context, swallowing any revert/returndata.
MUST revert CCTPHookExecutorUnauthorized unless msg.sender == relay. The call uses a FIXED
selector so payload can never choose the invoked function, forwards no value, and returns
success = false if the target reverts or return-bombs. It MUST revert CCTPHookOutOfGas instead
when the target fails with at most 1/63 of the pre-call gas left (its own frame starved or out of
gas). This detects exhaustion in the target's OWN frame only: a sub-call's out-of-gas leaves the
target its 1/64 reserve, so it reads as an ordinary revert (false) unless the target burns all its
remaining gas to re-raise it.
function executeHook(
uint32 sourceDomain,
bytes32 sender,
bytes32 mintRecipient,
uint256 amount,
bytes32, /* nonce */
address target,
bytes calldata payload
) external returns (bool success);Parameters
| Name | Type | Description |
|---|---|---|
| sourceDomain | uint32 | Attested CCTP source domain of the burn. |
| sender | bytes32 | Attested bytes32 burner on the source domain. |
| mintRecipient | bytes32 | Attested bytes32 mint recipient on this chain. |
| amount | uint256 | The USDC amount actually minted to mintRecipient (attested burn amount minusattested feeExecuted). |
| _ | bytes32 | Attested CCTP nonce (passed through for the relay's HookExecuted bookkeeping). |
| target | address | The hook target decoded from the Lattice hookData envelope (attacker-chosen). |
| payload | bytes | The attacker-controlled hook payload tail. |
Returns
| Name | Type | Description |
|---|---|---|
| success | bool | Whether target.onCCTPHook(...) returned without reverting. |