private audit field notes / evm / cross-chain defi
Between Two Chains: How I Audit Cross-Chain DeFi Protocols
A bridge is not one transaction stretched across two networks. It is a distributed state machine with money moving through the gaps. This is the approach I used during a recent private EVM engagement with Three Sigma.
This article does not name the client or disclose findings, affected code, severities, proof-of-concepts, or remediation details. It shares the reusable reasoning process and bug classes behind the review.
I recently reviewed a cross-chain DeFi system during a private engagement with Three Sigma. The architecture belonged to a category that is becoming increasingly common: a hub chain holds canonical accounting and backing, outpost chains expose faster user flows, and asynchronous messages keep rates, supplies, vaults, and liquidity aligned.
At first glance, each contract looked familiar. There were gateways, vaults, receipt tokens, message codecs, role checks, nonces, and ERC-4626-style accounting. The interesting attack surface did not live inside any one of them. It lived in the time and state between them.
The key mental shift was simple: stop reading the bridge as a sequence of Solidity calls and start reading it as a distributed protocol that can pause after any step.
Why Cross-Chain Auditing Is Different
On one chain, a revert usually restores the entire transaction. In a cross-chain flow, the source transaction may already be final before the destination even sees the message. A later revert cannot travel backward in time and undo the source-chain state.
The dangerous states are often not the starting state or the intended final state. They are the intermediate states:
- backing moved, but the destination supply has not changed;
- tokens burned, but the release message has not executed;
- a rate snapshot is authentic, but older than the state around it;
- a failed message still owns the next expected nonce;
- the queue is repaired, but the associated assets are not;
- two individually ordered message streams cross in an unsafe order.
This is why bridge audits cannot stop at access control and message authentication. Authentic bytes can still carry stale truth. Correct functions can still compose into an incorrect protocol.
The Five-Ledger Model
Before hunting for bugs, I reduced the system to five ledgers. Every relevant state transition had to update one or more of them:
custody
Where are the real assets right now: user, gateway, connector, vault, or farm?
liabilities
How many redeemable receipt or staked tokens exist on every chain?
messages
Which transitions are queued, processed, retried, skipped, or still in flight?
time
When was a value produced, sent, received, processed, and considered fresh?
recovery
If execution stops here, who can restore the queue, accounting, and assets?
This model made the protocol easier to reason about than a contract-by-contract reading. For every bridge direction, I wrote down the before state and asked what each ledger should contain after every step—not only after the happy path finishes.
The Workflow I Used
1. Start With Scope, Trust, and Known Issues
I first pinned the exact review commit, listed every in-scope contract, and separated supporting dependencies from reportable root causes. Then I documented the trust model: governance, keepers, connectors, pausers, minters, burners, farms, and ordinary users.
This prevented two common audit failures. The first is inventing an exploit that requires a trusted role to become malicious. The second is spending hours rediscovering an issue that the team already knows.
I maintained a living known-issue file for both manual review and AI-assisted scans. Every confirmed lead was added immediately so later passes could focus on new behavior instead of producing increasingly polished duplicates.
2. Build The Protocol Story Before The Bug Story
I traced the main user journeys end to end:
- deposit, mint, stake, unstake, redeem, and withdraw;
- hub-to-outpost, outpost-to-hub, and outpost-to-outpost bridging;
- asset and supply reporting;
- rate propagation and freshness checks;
- reserve transfers, retries, refunds, and nonce recovery;
- initialization, upgrades, pausing, and role-controlled configuration.
For each journey, I tracked the exact token, amount, unit, owner, supply, vault balance, chain ID, connector, nonce, and timestamp. This is tedious on paper and incredibly effective in practice.
3. Turn The Story Into Invariants
The strongest invariants crossed contract and chain boundaries:
- Conservation: moving a claim between chains must not create or destroy net redeemable value.
- Backing: destination liabilities must never become usable before their backing is reserved safely.
- At-most-once settlement: one authenticated message must produce one economic transition.
- Causal reporting: a supply report must be interpreted in the same world state in which it was produced.
- Preview consistency: quoted assets and executable assets must agree after fees and rounding.
- Recovery completeness: restoring liveness must not leave accounting or user funds stranded.
4. Explore Adversarial Schedules
Once the invariants were clear, I stopped asking only “can this function be exploited?” and started asking “in which order can legitimate operations violate this invariant?”
I permuted events across the independent lanes:
The best leads often required no forged message or compromised role. They emerged from honest actions executing in a legal but unsafe order.
5. Reproduce The Smallest Real State
For each promising lead, I built the smallest Foundry test that still used the protocol’s real contracts and call path. Mocks were useful for transport timing, but never allowed to invent authentication or token behavior that the production system did not provide.
The test needed to prove:
- the initial balances, supplies, nonces, and configuration;
- the exact authorized or permissionless trigger;
- the harmful intermediate or final state;
- why rollback, access control, and recovery did not prevent the impact;
- whether a later update truly repaired the system or only hid the mismatch.
The Lessons That Changed My Review
An Honest Snapshot Can Still Be Dangerous
A snapshot does not need to be forged to be wrong when applied. The world may change while it travels. Funds may move, tokens may mint or burn, and another message may execute before the snapshot is processed.
The question is not only “is this report authentic?” It is also “what events does this report know about?” If the payload carries no acknowledgement of processed inbound work, the receiver may be unable to distinguish settled state from in-flight state.
FIFO Is Local, Not Global
Per-chain nonces can guarantee ordering inside one message stream. They do not automatically order the opposite direction. A message from A to B and a report from B to A can each be perfectly FIFO and still cross in a dangerous order.
Per-direction ordering prevents local replay. It does not create global causality.
Intermediate State Is Real State
Two reconciliation sequences can end with the same arithmetic target and behave very differently during execution. If the first operation changes supply, a later external call may observe that temporary state and enforce liquidity, yield, or solvency checks against it.
This changed how I reviewed “mint then burn” and “burn then mint” paths. The final equation is not enough. Every external interaction between the two operations turns temporary accounting into a security boundary.
Recovery Has Three Separate Meanings
During triage, I found it useful to split recovery into three questions:
Can valid messages continue after a failure?
Are supplies, backing, and vault balances correct again?
Can the original owner recover funds already committed?
Skipping a nonce may restore liveness while leaving stale accounting or locked assets behind. “The admin can fix it” is therefore not the end of severity analysis; it is the start of a concrete recovery trace.
Shared Configuration Can Leak Permission Between Flows
A token may be valid for reserve transfers and invalid for receipt-token minting. If both operations use one generic mapping as their only allowlist, validity in one context silently becomes permission in another.
I now distinguish identity mappings from capability mappings. Knowing that token X corresponds to token Y does not tell you whether X may be bridged, minted, deposited, withdrawn, or used only for operational rebalancing.
A Preview Is A Promise
ERC-4626-style previews, quotes, and view functions shape user expectations and often feed minimum-output checks. If the preview ignores a downstream fee while execution applies it, the safety check can reject an otherwise ordinary withdrawal.
My review rule is now explicit: compare previews with real execution under non-zero fees, zero idle liquidity, conversion rounding, stale rates, and external calls.
One Wei Can Be Real And Still Not Be A Finding
Some rounding differences are mechanically real but economically meaningless. Before reporting one, I ask whether it accumulates, creates repeatable profit, blocks execution, violates an explicit exactness guarantee, or changes another protocol calculation materially.
Fuzz tests are especially valuable here. A test that deliberately accepts a one-unit tolerance may be a better statement of protocol intent than a comment promising exact synchronization.
Turning Leads Into Evidence
I used AI heavily during this engagement, but never as the final judge. It helped me map entry points, generate adversarial schedules, search for variants, draft test scaffolding, and challenge assumptions. The compiler, execution trace, state assertions, deployment evidence, and protocol intent remained authoritative.
Every serious lead went through the same gate:
- Reachability: can a realistic actor create the required state?
- Mechanics: does the exact call sequence execute against the pinned code?
- Impact: what changes in balances, supply, liquidity, accounting, or liveness?
- Recovery: is the effect temporary, manually recoverable, or permanent?
- Materiality: can the issue move beyond dust or theoretical inconsistency?
- Novelty: is it fresh, in scope, and distinct from known issues?
This last step matters. A high-quality private audit is not measured by the number of leads generated. It is measured by how many incorrect claims are removed before the client has to review them.
My Cross-Chain DeFi Audit Checklist
If I audit another hub-and-outpost DeFi protocol tomorrow, these are the first questions I will ask.
Custody And Supply
- Where is the real backing before, during, and after every bridge direction?
- Can destination liabilities become usable before backing is safely reserved?
- Can reports mint or burn mirrored assets that belong to an in-flight transfer?
- Does a later report repair both accounting and real liquidity?
Messages And Ordering
- What exactly is authenticated: bytes, source chain, sender, destination, and connector?
- Are receive-time and process-time identities bound together?
- What remains executable while a queued message performs external calls?
- How do independent directional nonce streams interact?
- What happens to the next valid message after the current one fails?
Rates, Fees, And Units
- Is freshness measured from production, sending, receipt, or processing time?
- Do paired rates update atomically or expose mixed epochs?
- Do previews, state-changing paths, and minimum outputs use the same fee model?
- Are values denominated in assets, shares, receipt tokens, or staked tokens?
- Does rounding favor safety, and can dust accumulate?
Configuration And Recovery
- Can mappings, vaults, connectors, or roles change while messages are in flight?
- Does replacing a bidirectional mapping remove every stale direction?
- Does a refund path have enough fee funding to execute?
- Can nonce recovery replay, skip, or strand economic state?
- Can governance restore liveness, accounting, and assets—not just one of them?
The Final Lesson
The most useful finding strategy was not “look for a missing require.” It was to locate every moment when the two chains temporarily disagreed and ask what users, keepers, reports, and contracts could do before they agreed again.
Cross-chain protocols are built from familiar Solidity components, but their security lives in distributed-systems properties: causality, partial failure, delayed truth, idempotency, and recovery. That is the layer auditors need to model explicitly.
The engagement details remain private. The lesson is reusable: trace the money, trace the liabilities, trace the message, and then audit every state that exists before all four tell the same story.