conceptualv0.1 · skeleton docs
EVM gateway
What WormHookGateway checks before a hook runs, and how it builds the receipt.
Order of operations
WormHookGateway.solconceptual
function receiveHookCall(bytes calldata vaa, bytes calldata payload) external nonReentrant whenNotPaused {
// 1. Verify the VAA via Wormhole Core and the emitter against the registered router
(HookCall memory c, bytes32 callId) = _verifyAndDecode(vaa); // WH-301
// 2. Payload must match the committed hash
require(keccak256(payload) == c.payloadHash, PayloadMismatch());
// 3. Replay: each call ID executes at most once
if (consumed[callId]) revert Replay(callId); // WH-302
consumed[callId] = true;
// 4. Expiry: refuse, never execute late
if (block.timestamp > c.expiry) return _reject(callId, CALL_EXPIRED); // WH-201
// 5. Registry: hook exists, is active, app is permitted
Hook memory h = registry.get(c.hookId); // WH-303/304/305
// 6. Spend: per-call cap and app daily cap
_checkSpend(h, c); // WH-306
// 7. Execute through the adapter; reverts become failed receipts
try IWormHookAdapter(h.adapter).execute{gas: h.gasLimit}(callId, payload) returns (bytes memory out) {
_emitReceipt(callId, SUCCESS, keccak256(out), _spent(callId));
} catch (bytes memory reason) {
_emitReceipt(callId, FAILED, bytes32(0), 0); // WH-401
emit HookReverted(callId, reason);
}
}Notes on the design
- Replay is marked before execution. A re-entrant or duplicate delivery can't slip through between check and effect.
- Expired calls still produce a receipt. The gateway consumes the call ID and sends a failed receipt so Solana can close the pending call.
- Duplicates produce no receipt. The first delivery already produced it; see HookReceipt.
- Gas is bounded per hook. A hook can't consume the executor's whole gas budget.
- Pause is global and per-hook. The global pause stops all execution; per-hook pause lives in the registry.
Events
| Event | When |
|---|---|
HookCallReceived(callId, hookId, sourceApp) | After verification |
HookExecuted(callId, status, outputHash, amountSpent) | After adapter returns or reverts |
HookReverted(callId, reason) | On adapter revert |
ReceiptEmitted(callId, receiptHash, sequence) | When the return message is posted |
The indexer joins these to the Solana and Wormhole sides to build each execution page.