Tracing unauthorised payouts in bridge dispatcher

The dispatcher path that turns proof data into spendable value

The traced call started at the bridge dispatcher and followed a selector chain into an entry module, then into proof handling, then into payout execution. The external selector was 0x8c49b257, which reached _createImports(bytes) through delegatecall, then proveImports(bytes), then processTransactions(bytes,uint256).

That sequence matters because each step ran inside the same execution context. The dispatcher did not hand control to separate contracts for independent approval. It handed the payload through a chain of internal modules that treated imported data, proof material, and transfer execution as one flow. In practice, that turns the proof path into a payment path if the validation step accepts what it is given.

The call also carried a large serialised payload through the bridge flow in one go. That makes the trace easy to follow and hard to forgive. If the payload is accepted early, the rest of the chain can behave like a conveyor belt.

Where proof validation stopped being a gate and became input

proveImports(bytes) sat in the middle of the chain, but the trace does not show it acting as a hard stop. It looped over submitted data through hashing and MMR helper calls, then passed forward into transaction processing. That is not a proof gate in the ordinary sense. It is a parser with cryptographic decoration.

The important failure mode is simple: attacker-supplied import material was treated as if it belonged inside the spending path. There was no trace-visible rejection, rollback, or secondary authorisation before transfers began. If the proof layer can accept malformed, replayed, or otherwise unspent material, the payout layer will happily consume it.

Delegatecalls made that worse. The dispatcher, hash helper, and MMR helper all ran as part of the same trusted flow. The trace showed 21 delegatecalls to createHash(bytes) and 5 to GetMMRProofIndex(uint64,uint64,uint8). That kind of repetition is normal for proof work. It is less reassuring when the output lands directly in a module that can release funds.

Tracing the drain through delegatecalls, deserialisation, and payout targets

The payout stage was not subtle. processTransactions(bytes,uint256) deserialised transfers from the proved payload with a count of 3, then sent native ETH and ERC-20 transfers to the drainer address. The token set included tBTC and USDC, with ERC-20 transfer calls using selector 0xa9059cbb.

The trace path matters because it shows where the system stopped treating data as evidence and started treating it as instruction. Once deserializeTransfers(bytes,uint8) completed, the bridge no longer seemed to ask whether the message had already been spent, whether the sender had authority over it, or whether the transfer list matched a valid claim set. It just moved to execution.

Follow the selector chain from entry to transfer execution

The selector chain is the cleanest way to see the route:

  • external selector 0x8c49b257
  • _createImports(bytes) selector 0x2babda4c
  • proveImports(bytes) selector 0xa34ccc20
  • processTransactions(bytes,uint256) selector 0xf419ee83
  • deserializeTransfers(bytes,uint8) selector 0x9d56cf89

That path is the bridge dispatcher in miniature. Entry data went through import creation, proof handling, and transfer decoding before execution. No visible break appears between validation and payout. In a hardened design, that break matters more than the hash work around it.

Check the payout list against the drainer address and token set

The payout target was 0x65cb8b128bf6e690761044cceca422bb239c25f9. The drain covered 1,625.36688649 ETH, 103.56766017 tBTC, and 147,658.836798 USDC. Those figures line up with a multi-asset asset drain rather than a single token mistake.

The interesting part is not the size alone. It is the shape of the payout list. A bridge that releases native ETH and ERC-20 assets from one decoded payload needs very tight proof spend validation before the transfer list is trusted. If that check is missing, replayed, malformed, or skipped, the token mix becomes the attacker’s shopping list.

The USDC leg gives a lower bound of at least $147,658.84 from the local artefacts, but the full loss picture is much larger because the other assets were also transferred. The payout address behaved like a drainer address, and the trace gives no sign that the system challenged it before execution.

Verifying the trace against the loss figures and the remaining unknowns

The block and timestamp are fixed: Ethereum block 25118335 at 2026-05-17T23:55:23Z. The traced sequence is also fixed. What remains unresolved is the exact predicate that failed, because verified source was unavailable. That leaves a few possibilities open: missing proof-spend validation, replay-accounting failure, malformed-message acceptance, or deserialisation abuse in the transfer list.

The trace does not support a neat story about a single bad signature. It looks more like a bridge dispatcher that accepted proof data as spending authority, then handed that authority to payout code without a second boundary. That is the control to watch for in similar systems: proof validation must fail closed before any transfer decoder sees the payload. Once the bridge starts serialising imports into payouts, the rest is just accounting.

Related posts

CVE-2026-3300 and WordPress admin account abuse

Everest Forms Pro CVE-2026-3300 is not a tidy bug, it is the sort of mess that turns form input into PHP and then acts surprised when attackers notice. I would not trust any site running the affected...

Review CI/CD install scripts for malicious code

Install scripts are the bit people wave through until they bite; in CI/CD, that means code runs under a trusted name before anyone has looked properly. I pin versions, record what landed, and treat...

Check npm and PyPI packages for compromise

A clean package yesterday means very little if a fresh update arrives with new maintainers, odd install scripts, or a version jump that does not make sense. I look for software supply chain trouble by...