Skip to content
Mainnet Live. All executions, addresses and hashes are live.
WORMHOOK
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

EventWhen
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.