Banked: 2026-06-10, Cycle 235. Source: Across Protocol (across-protocol/contracts @ eabc7375).
Class: cross-chain / bridge. Sibling: C234 LayerZero OFT lens (verifier-set flavor). This is the INTENT/OPTIMISTIC flavor.
Every bridge SHIFTS trust somewhere: to a validator set, a light client, or an optimistic proposer + dispute window. The bug lives wherever that shift is under-constrained. Audit question #1 for ANY bridge: "Where is trust shifted, and what enforces correctness at that point - a cryptographic check, or an economic assumption that someone will dispute?"
Two dominant archetypes: - A. Attestation / verifier-set (OFT, Wormhole, CCIP, Ronin): audit the SIGNATURE / PROOF acceptance. -> C234 lens. - B. Optimistic / intent (Across, optimistic-rollup withdrawals, UMA-settled): audit the DISPUTE WINDOW + proposer bond. -> THIS lens. P1, P2, P6, P7 are SHARED across both. P3 is the distinguishing axis.
P1 - Deposit<->Fill matching integrity. Find the exact field-set hashed into the fill identifier (Across: keccak(abi.encode(relayData, chainId())) covering depositor/recipient/exclusiveRelayer/in+outToken/in+outAmount/originChainId/depositId/fillDeadline/exclusivityDeadline/message). Compare to the deposit identifier's field-set. ANY field on one side missing from the matching hash = forgery door (missing outputAmount -> short-fill; missing originChainId -> cross-origin claim). Then: does the CHAIN prove fill==deposit, or is it deferred off-chain? Across DEFERS it to the proposer - so security collapses into P3.
P2 - Replay / cross-chain message auth. Verify the fill/message commitment binds BOTH source and dest chainId AND a per-source unique nonce/id, and the consumed-marker (nullifier / fillStatus / processedNonce) is keyed by the FULL tuple. Across: relayHash has destination chainId() + relayData.originChainId + depositId; safe deposits use monotonic counter, unsafeDeposit pushes nonce-collision onto the caller (flag any protocol that does this SILENTLY). Replay bugs hide where the nullifier omits chainId (replay on sister deployment) or nonce is caller-suppliable without collision protection.
P3 - Optimistic settlement integrity [HIGHEST YIELD, archetype B]. Merkle proofs only prove leaf-in-root; they do NOT prove the root is HONEST. Root honesty is purely optimistic (bond + dispute window + UMA + >=1 honest watcher). Audit: (a) who proposes? (b) is liveness long enough vs finality + watcher latency? (c) can the proposer grief/escape the bond? (d) is there ANY on-chain content validation beyond merkle inclusion? (e) admin escape hatches (emergencyDeleteProposal)? A chain-halt / censorship / oracle-capture during liveness = full-drain. NOMAD analog: uninitialized/trusted root accepting any proof - check executeX reverts on uninitialized root (Across does).
P4 - Slow-fill / fallback accounting. Fast and fallback paths must share ONE consumed-marker keyed identically (Across: same fillStatuses[relayHash] latch -> recipient paid at most once). The subtle bug is not double-PAY-to-recipient but double-FUND-from-pool (a refund leaf AND a fallback leaf for the same deposit both entering roots). Verify the two leaves are mutually exclusive BY CONSTRUCTION, not by an honest proposer + a claw-back event.
P5 - Liquidity / LP accounting. Model conservation: sum(deposits) - sum(fills/refunds) - sum(fees) == on-chain balance, ALWAYS. Probe: signed-reserve under/overflow driven by attacker-influenced amounts (Across utilizedReserves is int256, deliberately can go negative); exchangeRate rounding / LP-share inflation / first-deposit attack; blind trust of canonical-bridge return path (spoofed L2->L1 inflates reserves); reentrancy on token-release before reserve update (Across: nonReentrant everywhere + _noRevertTransfer).
P6 - Message/handler calldata trust [LiFi/Cycle210 analog, highest yield for steal-from-USER]. Post-transfer hook handleV3AcrossMessage(token, amount, caller, message) -> canonical MulticallHandler executes arbitrary Call[] BLINDLY. Audit: (a) is the handler's caller/msg.sender arg trusted for auth? It must NOT be (relayer is attacker-controllable). (b) arbitrary calls with funds on the handler -> leftovers stealable by next caller (Across documents this). (c) is the message bound into the fill commitment so it can't be tampered en route (Across: yes, in relayHash). (d) reentrancy: confirm the consumed-marker is set BEFORE the external hook (Across sets fillStatuses=Filled before _transferTokensToRecipient - GOOD ordering).
P7 - Token edge cases. Ask "does accounting assume amount-sent == amount-recorded?" If yes: fee-on-transfer breaks deposit==fill==refund (pool drains by fee), rebasing desyncs reserves, weird decimals break fee-pct math (1e18 scaling). Defense is almost always a TOKEN ALLOWLIST -> the real question becomes "can a non-allowlisted/malicious token reach the accounting path?" (Across allows ANY inputToken on deposit but only allowlists refund-backed tokens -> model the permissionless-input-vs-curated-refund mismatch).
P3 + P1 joined = the largest-loss bridge class (Ronin/Wormhole/Nomad were all trust-shift-layer failures). P1 is the door, P3 is whether the forgery survives to payment - audit them together: "can I forge a fill, AND can it survive to reimbursement?" Secondary: P6 for steal-from-user (LiFi class), P5 for slow-bleed/rounding.
When handed any bridge: (1) classify archetype A vs B; (2) run P1->P2 on the matching/replay hashes; (3) hammer P3 (the trust-shift correctness); (4) check P4 fallback exclusivity; (5) model P5 conservation; (6) audit P6 handler caller-trust + marker-before-call ordering; (7) ask P7 amount-sent==recorded + allowlist reachability.