Multi-chain
Signature and replay

MAP Protocol's $111K Loss and How Olympix Would Have Prevented It

Loss
Loss
Exploit date
May 20, 2026

At 16:13:59 UTC on May 20, 2026, a brand-new Ethereum wallet received one quadrillion MAPO. The legitimate supply of MAP Protocol's token was about 208 million, so that single mint created roughly 4.8 million times more MAPO than had ever existed. The tokens came out of Butter Bridge V3.1, the cross-chain bridge MAP Protocol and Butter Network run between MAP's mainnet and other chains, and the bridge minted them because it believed it was retrying a message it had already approved.

The attacker sold about a billion of those tokens into the Uniswap v4 ETH/MAPO pool and pulled out 52.21 ETH, about $111,075 at the time. MAPO fell more than 96%. The light client was fine, the oracle multisig was fine, and no key leaked. The flaw sat in how the bridge fingerprinted a message, which MAP Protocol itself traced to a bug in Butter Bridge V3.1.

A Bridge That Remembers Failed Deliveries

Bridges get messages wrong for boring reasons all the time. A destination contract reverts, a gas estimate falls short, a target isn't ready yet. Butter Bridge handles that the way most bridges do. When a verified message can't be delivered, the bridge stores a commitment to it and lets anyone call a retry function later to finish the delivery.

The commitment is a hash. When the message first fails, the bridge hashes its fields and writes that hash to storage. When someone calls retryMessageIn, the bridge hashes the fields they supply and checks the result against what it stored. If the hashes match, the bridge treats the retry as the same message it already verified, so it doesn't verify the signatures again, and it executes the delivery.

That design makes the hash the entire security boundary for retries. Whatever produces the same hash is, as far as the retry path is concerned, an approved message.

Same Bytes, Different Message

Butter Bridge built that hash with abi.encodePacked over several fields, and more than one of them was dynamic bytes. Blockaid identified this as the root cause, and MAP Protocol confirmed it: an abi.encodePacked collision across dynamic-bytes fields in the bridge's retry path let a forged retry pass the guard check.

The problem with packing several variable-length fields side by side is that packing throws away the boundaries between them. Two fields of 10 and 20 bytes and two fields of 20 and 10 bytes can produce the same 30-byte string, and the same string means the same hash. The fields mean completely different things, but the hash can't tell them apart.

That's what the attacker used. According to Blockaid, they first sent a legitimate MAP-to-Ethereum message, signed through the bridge's normal oracle and multisig validation, and arranged for its delivery to fail so the bridge would store it for retry. A detailed analysis of the transactions on Paragraph describes that first message as a decoy: its only job was to get a hash on file whose packed bytes already contained the MAPO token address and a mint payload.

Then they called the retry function with the same bytes split at different boundaries. Packed together, the fields came out identical, so the hash matched the stored one. Read field by field, though, the retry now said something new: deliver to the MAPO token contract and mint 10^15 tokens to the attacker. The bridge had the authority to tell MAPO to mint, and the retry path's only check was the hash, so the mint went through.

One Quadrillion Tokens and a Thin Pool

The mint transaction landed in Ethereum block 25137572 and was sent by 0x4059...4279, which Blockscout now tags as a MAP/Butter Bridge exploiter. It called an unverified contract at 0x2475...1C30, and the token transfer it produced is a single mint of MAPO from the zero address to that same wallet. Blockaid reported that the bridge was tricked the same way on BNB Chain.

A quadrillion tokens is only worth what someone will pay for them. The attacker dumped about a billion MAPO into the Uniswap v4 ETH/MAPO pool and received 52.21 ETH, and the price dropped from about $0.003 to about $0.0001. Blockaid and SlowMist valued the ETH at about $180,000. Valued at the ETH price at the time of the drain, the 52.21 ETH comes to about $111,075, and that's the figure used here. Roughly 999.999 billion MAPO stayed in the attacker's wallet, according to Blockaid.

Pausing, Migrating and Invalidating

MAP Protocol said on X that it was aware and coordinating with external security partners, and it paused the bridge between the MAPO ERC-20 and mainnet MAPO. Butter Network paused ButterSwap and said user funds were secure. In its later AMA recap, MAP Protocol wrote that the flaw in Butter Bridge V3.1 had led to unauthorized MAPO minting on Ethereum and BSC, that mainnet consensus and light-client verification weren't affected, and that it suspended the conversion service for the legacy ERC-20 and BEP-20 contracts.

The team also announced a migration to a new token contract with an asset snapshot, and said any tokens still held by attacker-controlled addresses would be fully invalidated and left out of any future snapshot or conversion. That shuts down the billion-sized pile still sitting in the attacker's wallet.

Why a Retry Path Gets Less Attention

Retry logic exists for the unlucky message that ran out of gas or hit a contract that wasn't ready. The checks that guard a bridge's first delivery, signatures, quorums and the light client, held up perfectly here. MAP Protocol's light client and oracle multisig were never compromised.

The retry function inherited all of that trust through one comparison. Once a message had been verified, the bridge treated anything that hashed to it as the same message. The fields that were supposed to pin the message down weren't bound to their own positions, so the hash could only vouch for a string of bytes, and the same bytes could be read as a different message.

How Olympix Would Have Caught the Collision

Olympix reads a contract as states, transitions and entry points, and takes in its context to infer the invariants the code depends on. For Butter Bridge, the state is the set of stored message commitments, the transition is retryMessageIn, and the trust it spends is the bridge's power to execute deliveries, including mints on the MAPO token. The engine maps the paths from every entry point to that execution, then narrows them from all paths to the feasible ones to the ones that touch an invariant.

Static analysis would have surfaced the signal here. Hashing several dynamic values together with abi.encodePacked is a well-known collision risk, and it's a pattern static analysis flags on sight. In most contracts it's a code-quality note. In the one function that decides whether a cross-chain message counts as already verified, it's the whole attack surface.

The invariant is short. A retry must execute exactly the message that was stored, field for field, and nothing else. BugPoCer starts from an invariant like that and searches for a sequence of state changes that breaks it. Had the Butter team run it during development, the search would have produced a stored failed message followed by a retry whose fields differ but whose packed encoding matches, and it would have shown the bridge executing the retry. BugPoCer would have returned that as a counterexample, with the minimal conditions and a generated Forge test that reproduces it, at the merge where the retry path was written.

The fix it points to is small. Hash each field with its length, use abi.encode instead of abi.encodePacked, or hash the full message struct, so a retry can only ever match the message that was stored. Any of those, enforced at every merge and before release, would have left the retry path doing the one thing it was meant to do.

Hash What the Message Means

A hash is only as strong as the boundaries inside the data it covers. For bridges, relayers and anything else that verifies once and trusts the result later, the commitment has to bind every field to its place. To see how Olympix maps the trust that flows through your retry, replay and settlement paths and returns a working counterexample for each one that breaks, book a technical walkthrough.

On-Chain References

Mint tx (May 20, 2026, 16:13:59 UTC): 0x31e5...664a

Attacker wallet: 0x4059...4279

Contract called in the mint tx: 0x2475...1C30

Butter Bridge V3.1 proxy: 0x0000...6A56

MAPO token (legacy ERC-20): 0x66d7...9a99

See what Olympix finds on your own code

Send us a repo that's already been audited, and we'll run Olympix at the same commit and show you what comes back.