Reproduced Exploit
Sweep n Flip — LayerZeroAdapter.executeMessages irrecoverable state
1. Failed LZ deliveries are stored in s_pendingMessagesToExecute. 2. executeMessages runs pending[0] but pop() removes the last element. 3. With [msg1, msg2]: first call delivers token 1 and deletes msg2. 4. Second call re-runs msg1 (already delivered) and clears the queue — token 2 is gone forever.
Chain
Other
Category
dos
Date
Nov 2024
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 46494-layerzeroadapterexecutemessages-could-lead-to-irrecoverable. Standalone Foundry PoC and full write-up: 46494-layerzeroadapterexecutemessages-could-lead-to-irrecoverable_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/dos/frozen-funds · locked-funds · cross-chain-message · bridge-message-validation
Reproduction: self-contained Foundry PoC, offline, forge-std only. Full trace: output.txt. PoC: test/46494-layerzeroadapterexecutemessages-could-lead-to-irrecoverable_exp.sol.
Key info#
| Impact | HIGH — pending cross-chain NFT delivery permanently lost |
| Protocol | Sweep n Flip Bridge / LayerZeroAdapter |
| Vulnerable code | LayerZeroAdapter.executeMessages — executes index 0, pops last |
| Bug class | Array pop mismatch → wrong message deleted, correct one stuck/re-run |
| Finding | Cantina — Sweep n Flip Bridge, Nov 2024 · #46494 · reporter slowfi |
| Report | cantina_sweepnflip_bridge_november2024.pdf |
| Source | AuditVault |
| Status | Audit finding — PRs 9/13; residual issues noted by Cantina |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Failed LZ deliveries are stored in
s_pendingMessagesToExecute. executeMessagesrunspending[0]butpop()removes the last element.- With
[msg1, msg2]: first call delivers token 1 and deletes msg2. - Second call re-runs msg1 (already delivered) and clears the queue — token 2 is gone forever.
The vulnerable code#
bytes memory payload = s_pendingMessagesToExecute[0];
(bool ok,) = address(bridge).call(payload);
// ...
s_pendingMessagesToExecute.pop(); // @> VULN: pops LAST, not the executed index-0 message
Fix: execute from the last index (swap-and-pop), or assign nonces per message.
Root cause#
Mismatch between which message is executed and which is removed from storage.
Preconditions#
- At least two messages queued after failed delivery.
- Operator retries with
limitToExecute_ = 1(or any partial limit).
Attack walkthrough#
- Queue failed deliveries for token 1 and token 2.
- Re-enable deliveries;
executeMessages(1)→ token 1 delivered, msg2 popped. executeMessages(1)→ re-executes msg1 (reverts ALREADY_DELIVERED), pops msg1.- HARM: token 2 never delivered, no longer pending — NFT permanently locked.
Diagrams#
Impact#
Permanent lock of bridged NFTs whose delivery messages are silently dropped from the retry queue.
Sources#
- AuditVault finding #46494
- Cantina report — Sweep n Flip Bridge (Nov 2024)
- Reduced C2 synthetic from finding-quoted
executeMessagesbehavior
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 46494-layerzeroadapterexecutemessages-could-lead-to-irrecoverable_exp (evm-hack-registry mirror).
- AuditVault finding: 46494-layerzeroadapterexecutemessages-could-lead-to-irrecoverable.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.