← back to lessons

Cross-Chain OFT / OApp Audit Primitives (LayerZero V2)

Class: cross-chain messaging / omnichain fungible token (OFT). Banked Cycle 234 from a static lens-mine of canonical LayerZero-Labs/LayerZero-v2 @ 9c741e7f (OFTCore/OFT/OFTAdapter, the codecs, OAppReceiver/OAppCore, MessagingComposer, MessagingChannel). Closes a real coverage gap: we had NO banked OFT lens. Applies to any LayerZero OFT trio (native OFT + OFTAdapter + composer).

The one question that opens an OFT audit

"The canonical core is audited and saturated - so where did the INTEGRATOR override _debit / _credit / lzReceive / lzCompose, and does that override preserve supply conservation and actor-binding?" The protocol enforces almost NO supply conservation itself; it is emergent from (a) replay protection and (b) integrator-correct overrides + a comment-only "one adapter per mesh" rule. Yield lives in the overrides, not in re-auditing core.

The 6 primitives (mechanism -> guard -> rule)

  1. Supply conservation (burn/mint vs lock/unlock). Native OFT burns on src / mints on dst; Adapter locks via safeTransferFrom / unlocks via safeTransfer. Conservation is NOT checked on-chain - it is emergent. RULE: map every mint/unlock site, prove each is gated by a consume-once message; confirm exactly ONE adapter per mesh and no second mint authority on the token.
  2. Decimal / dust (THE classic OFT bug). Wire amount is shared-decimals uint64; rate = 10**(localDec - sharedDec); _removeDust truncates on the DEBIT side before accounting, so default sends/receives are equal and no dust is stranded. RULE: verify dust is removed before mint/lock; break-tests = fee-after-dust override (inflation/strand), sharedDecimals < 6 with supply > uint64.max*rate (uint64-cast truncation), token decimals == or < sharedDecimals.
  3. Message auth / replay. lzReceive requires msg.sender==endpoint (OnlyEndpoint) + peer[srcEid]==origin.sender (OnlyPeer); replay stopped by per-path inboundPayloadHash nonce consume-once in MessagingChannel. RULE: confirm no override drops endpoint/peer checks, no public credit path, peers set correctly per live srcEid (a WRONG peer = inflation door).
  4. Compose actor-binding (HIGHEST YIELD). lzCompose is PERMISSIONLESS by protocol design; guarded only by content-hash + consume-once. The composer MUST itself assert msg.sender==endpoint, _from==trusted OFT, and bind its action to the message-decoded composeFrom/amountLD (treat executor msg.sender and extraData as untrusted). RULE: in any OVaultComposer check all of: endpoint-caller, trusted-_from, action bound to message (not tx.origin/stored state), composed-action minOut from the message, front-run safety (tokens are pre-credited), and revert/strand handling.
  5. Rate-limit / fee / slippage. RateLimiter is an OPTIONAL mixin; only always-on guard is the debit-side slippage require(received>=minAmountLD). Default quoteOFT enforces NO min on receive. RULE: check WHICH side the rate-limit is enforced (debit vs credit); src-only limit is useless vs a bad-peer inbound mint; note _setRateLimits does NOT reset amountInFlight on lower.
  6. Adapter approval / non-lossless token. Default OFTAdapter assumes 1-in-1-out. A fee-on-transfer / rebasing inner token makes the lock receive less than the wire amount -> over-unlock on dst -> mesh-wide supply inflation. No default guard; fix is integrator pre/post balanceOf. RULE: classify the inner token FIRST (fee-on-transfer? rebasing? hooks? non-std decimals?); default _debit + non-lossless token = high-severity lock-accounting drift.

Highest-yield class

P4 compose actor/value binding (permissionless lzCompose, pre-credited tokens, all safety pushed to the integrator's composer) tied with P6 adapter lock-accounting drift on a non-lossless inner token.

Cross-references

Where this transfers next

Immediate consumer: dreUSD (Sherlock contest 1259) OFT trio dreShareOFT / Adapter / OVaultComposer - see the dreUSD pre-flight (PF0-PF5) in ~/bounty/cycles/cycle-234-oft-lens-mine/00-verdict.md. Any future LayerZero OFT, Stargate, or omnichain-stablecoin target.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min