On Ethereum block `25118335` at `2026-05-17T23:55:23Z`, attacker EOA `0x5abb91b9c01a5ed3ae762d32b236595b459d5777` called bridge dispatcher `0x71518580f36feceffe0721f06ba4703218cd7f63` and drained bridge-held assets to drainer `0x65cb8b128bf6e690761044cceca422bb239c25f9`. The trace shows a BTC-import and proof-processing flow followed immediately by bridge payouts, which is consistent with a logic error in the bridge’s import/proof path rather than a flash-loan or reentrancy pattern. Exact losses were `1,625.36688649 ETH`, `103.56766017 tBTC`, and `147,658.836798 USDC`; local artifacts support a minimum USD loss of `$147,658.84` from the USDC leg alone, but they do not include a trusted contemporaneous valuation for the ETH and tBTC legs.

## Root Cause

### Vulnerable Contract

`Suspected Bridge Dispatcher` at `0x71518580f36feceffe0721f06ba4703218cd7f63`.

The dispatcher is a delegatecall-based bridge router. In this local run, recovered pseudocode now exists for the dispatcher and the three trace-visible modules behind it: entry module `0xa045cf963b79833faf445f555ee1a6812d6fc87f`, verification module `0x5e8060ecbf415aa25f12c1d67fde832bd87dcfa1`, and payout module `0x08f0fbcc068c70a29326094110769ee5f1d0107d`. These files are decompiler-style recovered approximations, not verified Solidity, so they are used only to express the same control flow already proven by the trace.

### Vulnerable Function

Trace-visible exploit path:

– External entry selector `0x8c49b257` on dispatcher `0x71518580f36feceffe0721f06ba4703218cd7f63`
– Delegatecall to `_createImports(bytes)`( `0x2babda4c`) on `0xa045cf963b79833faf445f555ee1a6812d6fc87f`
– Delegatecall to `proveImports(bytes)`( `0xa34ccc20`) on `0x5e8060ecbf415aa25f12c1d67fde832bd87dcfa1`
– Delegatecall to `processTransactions(bytes,uint256)`( `0xf419ee83`) on `0x08f0fbcc068c70a29326094110769ee5f1d0107d`

The payout is executed inside `processTransactions(bytes,uint256)`, but the trace only proves that the bug sits earlier in the same entry flow: attacker-supplied import/proof material progressed into payout instead of being rejected before bridge funds were released. The exact failed predicate inside that verification path is still not source-validated in this local run.

### Vulnerable Code

The following recovered snippets are not verified source. They are narrow pseudocode approximations derived from the trace and the decompiler artifacts in this analysis directory, and they preserve the call sequence that the validator can cross-check on-chain.

“`
// Recovered by Decompiler Agent — NOT verified source code function route_0x8c49b257(bytes calldata payload) external { bytes memory imports = IEntryModule(ENTRY_MODULE)._createImports(payload); bytes memory proved = IVerificationModule(VERIFICATION_MODULE).proveImports(imports); IPayoutModule(PAYOUT_MODULE).processTransactions(proved, 3); }
“`

“`
// Recovered by Decompiler Agent — NOT verified source code function proveImports(bytes memory imports) external pure returns (bytes memory proved) { // recovered approximation: helper loops over attacker data and returns a payout payload. // the exact failed predicate is unresolved in this local recovery. IHashHelper(HASH_HELPER).createHash(imports); IMmrHelper(MMR_HELPER).GetMMRProofIndex(0, 0, 0); proved = imports; } function processTransactions(bytes memory proved, uint256 count) external { TransferItem[] memory payouts = deserializeTransfers(proved, uint8(count)); _payNative(payouts[0].to, payouts[0].amount); _payToken(payouts[1].token, payouts[1].to, payouts[1].amount); _payToken(payouts[2].token, payouts[2].to, payouts[2].amount); }
“`

### Why It’s Vulnerable

**Expected behavior:** a bridge import path should only reach payout after it proves that the imported message is authentic, uniquely spendable, and mapped to a legitimate bridge transfer set. Before any ETH or ERC-20 transfer is executed, the bridge should reject duplicated proof elements, replayed imports, malformed transfer lists, and any payout not tied to an authorized inbound message.

**Actual behavior:** the trace shows the dispatcher accepts a large attacker-supplied payload, runs the entry and proof modules, and then enters `processTransactions(bytes,uint256)`. Inside that function, the recovered payout logic and the trace both show three outbound transfers to the same drainer: `1,625.36688649 ETH`, `103.56766017 tBTC`, and `147,658.836798 USDC`. There is no trace-visible rejection, rollback, or secondary authorization step between transfer decoding and payout execution.

What matters is the gap between those two states. The local trace supports a `logic_error` in the import/proof path because the bridge demonstrably treated attacker-submitted import material as spendable and converted it into outbound payouts from its own inventory. However, this local run still does not recover enough verified source detail to distinguish missing proof-spend validation from replay-accounting failure, malformed-message acceptance, transfer-list deserialization abuse, or another verification omission inside the same path.

## Attack Execution

### High-Level Flow

1. The attacker EOA submitted a single call to the bridge dispatcher with a large serialized payload.
2. The dispatcher routed that payload into the bridge’s BTC import path.
3. The verification module performed repeated hashing and proof-index helper calls over the submitted data.
4. After the verification phase completed, the bridge entered its payout module and decoded three transfers from the proved payload.
5. The bridge sent native ETH directly to the drainer wallet.
6. The bridge then transferred tBTC from bridge custody to the same drainer wallet.
7. The bridge finally transferred USDC from bridge custody to the same drainer wallet.

### Detailed Call Trace

The flow below is derived from `trace_callTracer.json` and selector-checked against `cast sig` results.

“`
depth 0 CALL 0x5abb91b9c01a5ed3ae762d32b236595b459d5777 -> 0x71518580f36feceffe0721f06ba4703218cd7f63 selector 0x8c49b257 (unresolved), value 0 depth 1 DELEGATECALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0xa045cf963b79833faf445f555ee1a6812d6fc87f selector 0x2babda4c `_createImports(bytes)` depth 2 DELEGATECALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x5e8060ecbf415aa25f12c1d67fde832bd87dcfa1 selector 0xa34ccc20 `proveImports(bytes)` depth 3/4 repeated verification helpers 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x40ec84ca82fbf1b4d6a8edb02bffcf3eb0400aae selector 0x2cd0cff2 `createHash(bytes)` (21 delegatecalls total) 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x918d6f7efe5a1707b83a2f0cf016cc5cf7983ffb selector 0x6d48bb8d `GetMMRProofIndex(uint64,uint64,uint8)` (5 delegatecalls total) depth 2 DELEGATECALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x08f0fbcc068c70a29326094110769ee5f1d0107d selector 0xf419ee83 `processTransactions(bytes,uint256)` depth 3 STATICCALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x796f4236c96e222b727df27978b3a77020356b88 selector 0x9d56cf89 `deserializeTransfers(bytes,uint8)` depth 3 CALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x65cb8b128bf6e690761044cceca422bb239c25f9 value 0x581c7f2bb3271b0400 = 1,625.36688649 ETH depth 3 CALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0x18084fba666a33d37592fa2633fd49a74dd93a88 selector 0xa9059cbb `transfer(address,uint256)` args: to `0x65cb8b128bf6e690761044cceca422bb239c25f9`, amount `103567660170000000000` depth 3 CALL 0x71518580f36feceffe0721f06ba4703218cd7f63 -> 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 selector 0xa9059cbb `transfer(address,uint256)` args: to `0x65cb8b128bf6e690761044cceca422bb239c25f9`, amount `147658836798`
“`

## Financial Impact

The bridge lost three assets in the exploit transaction:

– `1,625.36688649 ETH`
– `103.56766017 tBTC`
– `147,658.836798 USDC`

`funds_flow.json` correctly captured the ETH and tBTC legs, but its USDC decimal metadata is wrong because local token metadata lookup failed; the raw transfer amount `147658836798` must be interpreted with USDC’s canonical 6 decimals, not 18. The direct economic floor is therefore `$147,658.84` from the USDC leg alone, with the true total materially higher once the ETH and tBTC legs are valued. The funds moved from bridge custody at `0x71518580f36feceffe0721f06ba4703218cd7f63` to drainer wallet `0x65cb8b128bf6e690761044cceca422bb239c25f9`, so the loss appears to have hit protocol-held bridge liquidity rather than an intermediate AMM or lending pool.

No flash loan or repayment path appears anywhere in the trace. The bridge remained callable after the transaction, but this path drained a meaningful portion of immediately spendable bridge inventory and demonstrates that the proof-to-payout control boundary was broken.

## Evidence

– Transaction hash: `0x6990f01720f57fc515d0e976a0c4f8157e0a9529194c4c15d190e98d087eb321`
– Chain: Ethereum mainnet
– Block number: `25118335`
– Block timestamp: `2026-05-17T23:55:23Z`
– Receipt status: `0x1`
– Attacker EOA: `0x5abb91b9c01a5ed3ae762d32b236595b459d5777`
– Vulnerable bridge dispatcher: `0x71518580f36feceffe0721f06ba4703218cd7f63`
– Entry module: `0xa045cf963b79833faf445f555ee1a6812d6fc87f`
– Verification module: `0x5e8060ecbf415aa25f12c1d67fde832bd87dcfa1`
– Payout module: `0x08f0fbcc068c70a29326094110769ee5f1d0107d`
– Hash helper: `0x40ec84ca82fbf1b4d6a8edb02bffcf3eb0400aae`
– Proof-index helper: `0x918d6f7efe5a1707b83a2f0cf016cc5cf7983ffb`
– Drainer wallet: `0x65cb8b128bf6e690761044cceca422bb239c25f9`
– ERC-20 transfer 1: `tBTC` token `0x18084fba666a33d37592fa2633fd49a74dd93a88`, amount `103567660170000000000`
– ERC-20 transfer 2: `USDC` token `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48`, amount `147658836798`
– Native ETH payout is visible in the call trace as a direct CALL with value `0x581c7f2bb3271b0400`
– Selector verification completed locally for `_createImports(bytes)`, `proveImports(bytes)`, `processTransactions(bytes,uint256)`, `deserializeTransfers(bytes,uint8)`, `GetMMRProofIndex(uint64,uint64,uint8)`, `createHash(bytes)`, `transfer(address,uint256)`, and `decimals()`