Ekubo's $1.4M Loss and How Olympix Would Have Prevented It
Date: May 5, 2026
Chain: Ethereum, Arbitrum
Loss: about $1.42M taken from 31 addresses
Class: a router that let the caller decide whose token approval paid for a swap
At 17:49:47 UTC on May 5, 2026, a fresh wallet deployed a small unverified contract on Ethereum. Forty-eight seconds later, that contract called one of Ekubo's swap routers 85 times inside a single transaction, and each call moved 0.2 WBTC out of a wallet that had nothing to do with the trade. By the end of the transaction, 17 WBTC, worth about $1.37M, had left that wallet, and all of it had gone to the attacker.
The victim had once approved the router to spend its WBTC, the way traders approve a DEX router before a swap. That approval was meant to cover the victim's own trades. A parsing bug in the router, a gas-optimized contract written in the low-level EVM language Huff, let any caller choose whose approval the router would spend, and Ekubo's own analysis later counted $1,419,543 taken from 31 addresses on Ethereum and Arbitrum before the routers were abandoned.
A Starknet AMM That Came to Ethereum
Ekubo is a concentrated liquidity AMM, a DEX where liquidity providers place their capital within chosen price ranges, built first on Starknet and later expanded to Ethereum and Arbitrum. On the EVM side, all pools live inside one singleton contract called Core. Swaps happen inside a lock: a caller opens a lock on Core, performs any number of swaps and transfers, and Core tracks what each side owes. When the lock closes, every debt has to be zero, or the whole transaction reverts.
Routers sit in front of Core and turn a user's swap request into that sequence of calls. Ekubo built HuffRouter for gas efficiency and deployed it more than once. Blockscout records the first Ethereum router, tied to Ekubo V2, being created on July 28, 2025, a second V2-generation router on September 5, 2025, and a V3-generation router on January 5, 2026. A fourth deployment sat on Arbitrum. None of them was upgradeable, and none of the Ethereum routers has verified source on Blockscout.
To swap through a router, a user first lets the router spend their tokens with an ERC-20 approval. HuffRouter was designed for approvals that matched the exact swap amount, bundled into the same action as the swap. According to Ekubo's incident blog post, two things pushed users toward larger approvals anyway. An internal fixed-price page, reachable by direct URL, asked for an unlimited allowance, and wallets that couldn't batch calls through EIP-5792 needed a separate approval transaction, which made approving an unlimited amount once more convenient than approving each trade.
The Router That Read Past Its Own Route
An ERC-20 approval works like a standing permission slip. Once a wallet approves a router, the router can call transferFrom and pull tokens from that wallet whenever it wants, up to the approved amount, and the permission never expires on its own. The only thing standing between that permission and someone else's trade is the router's own decision about whose tokens to pull. A router that holds approvals from thousands of wallets has to pull only from the wallet that's actually trading, which is almost always the caller.
HuffRouter did that, in principle. According to Ekubo's technical post-mortem, when the router opened a lock on Core it passed along the caller's encoded route and appended two pieces of settlement data after it: the recipient of the output, and the address that tokens should be pulled from. When a user didn't name a recipient, the router filled both with msg.sender, the caller. Later, during settlement, the router went back to that appended data to decide whom to pay and whom to charge.
The bug was in how settlement found that data. It counted from where the route parser had stopped reading, the logical end of the route, and the post-mortem points out that this wasn't guaranteed to be the actual end of the calldata. If a caller added extra bytes after an otherwise valid route, the route parser never looked at them, and settlement picked them up as if the router had written them. The recipient was always meant to be something a caller could choose. The address tokens were pulled from never was, and those extra bytes could put any wallet that had approved the router in that position.
Ekubo's post-mortem puts the result plainly: the effective transferFrom address could be read from attacker-supplied trailing calldata and pointed at a victim. A router is supposed to pay for a swap with the caller's money, and the rule underneath every approval-based router is that the caller and the payer are the same address unless the payer has signed something saying otherwise. HuffRouter's settlement let the two come apart.
Eighty-Five Pulls in One Transaction
The attacker at 0xa911 deployed the attack contract at 0x61b0 at 17:49:47 UTC. At 17:50:35 UTC, in block 25030409, the attack contract called the V2-generation router at 0x8CCB. Blockscout's trace of the transaction shows the same pattern repeating: the router called into Core, Core sent 0.2 WBTC to the attacker's wallet, and the router then settled that debt by pulling 0.2 WBTC from the victim at 0x765D into Core. Core's books balanced every time, because from Core's point of view the router really had paid for what it withdrew.
The route itself didn't need to do anything useful. Ekubo's post-mortem notes that the hop in the first exploit transaction was built to fail, and that a failed hop was convenient because it left no real swap amounts to account for, though the bug didn't depend on it. The attack worked the same way whether the route traded or not, because the money came from the settlement step that followed.
The transaction repeated that pull 85 times and moved exactly 17 WBTC from the victim to the attacker. Ekubo's later analysis attributes about 98% of all losses to that one address, 17.0148 WBTC worth about $1,368,068 and 0.3439 cbBTC worth about $27,715. The other 30 addresses lost roughly $24,000 between them, in USDC, EKUBO, gEKUBO-26Q2, USDT and LINK. On Arbitrum, Ekubo's manual inspection found one successful exploit, for 5 USDC.
According to The Block, the attacker converted the WBTC into WETH and DAI. Merkle Science traced about $1.36M into Railgun in two deposits on May 5 and 6, withdrawn one to two hours later, and about 577 ETH, roughly $1.34M, into Tornado Cash on May 6.
Revoke, Rescue and a Recovery Fund
Ekubo's alert went out on X at 20:02 UTC on May 5, about two hours and twelve minutes after the first exploit transaction. It told users the incident was limited to the swap router contract on EVM chains, that liquidity providers and Starknet weren't affected, and that everyone should revoke outstanding approvals. Blockaid's alert followed at 20:15 UTC, reporting $1.4M drained so far and urging anyone who had approved the V2 contract as a spender to revoke.
Because the routers were immutable, Ekubo couldn't patch them in place. It removed them from its interface, stopped offering separate approval flows for them, and whitehatted the finite approvals still exposed on the affected contracts. Infinite approvals couldn't be rescued the same way, so the interface now shows a banner to any wallet that still has one, and the post-mortem keeps a list of the live approvals that should be revoked.
On June 15, 2026, Ekubo's DAO voted to contribute 300,000 USDC to a recovery fund. Against about $1.42M of identified losses, that meant a single per-address cap applied to every victim regardless of approval size or origin, with payouts made in kind in the token each address lost. As of October 23, 2026, the HuffRouter repository is archived, the router and its SDK are retired, and swaps run through a new Yul router whose approvals are always limited to the amount being spent.
Why a Small Router Skipped the Audit
Ekubo's post-mortem explains the decision. The team chose not to audit HuffRouter because it judged the practical risk low, based on two assumptions, that approvals would be limited and bundled with swaps, and that swaps initiated through the interface would stay small and infrequent. The router wasn't in the scope of an independent audit, and its Huff source wasn't published at the time, so the one contract holding user approvals was also the one outside the audit scope.
The team concluded that low expected usage and constrained approval patterns weren't sufficient reasons to skip review of a contract that could move user funds. The blog post adds that automated tooling now makes discovering bugs like this one far cheaper for attackers, and that the pre-deployment risk assessment didn't account for that shift. Routers are now in Ekubo's audit scope, the team has increased its use of AI tooling alongside independent review, future routers will carry an explicit expiration, and Ekubo opened ERC-8255, a draft standard for token approvals that expire on their own.
The fix itself was small. Settlement now reads its data anchored to the actual end of the calldata, so it always consumes the last bytes the router appended, and regression tests in the repository append extra bytes after a route and assert that a caller can't spend anyone's approval but their own.
How Olympix Keeps the Caller and the Payer Together
HuffRouter was written in Huff, so this section speaks to the bug class rather than claiming a run against the Huff router itself: an address that pays for a transfer, chosen from bytes the caller controls instead of from msg.sender. Olympix reads a contract the way an attacker does, as states, transitions and entry points, and it takes in the context around the code, here the approvals users grant and the lock-and-settle flow of the Core it calls into, to infer the invariants the system depends on. For an approval-holding router, the state that matters is every allowance users have granted it, and every path that ends in transferFrom is a path worth mapping.
A router's input space looks infinite because the caller writes the calldata, but the engine doesn't need to enumerate it. Signals decide which paths deserve an attack. In Solidity routers, Olympix's static analysis surfaces the signal at the heart of this case: a transferFrom whose from address is derived from caller-supplied data rather than from msg.sender or a verified signature. Olympix has flagged this class before in other codebases. From that signal, the engine narrows from every path, to the feasible ones that reach settlement, to the ones where the from address of a token pull can differ from the caller, which is exactly where the invariant lives.
The invariant takes one sentence: a router may only pull tokens from the address that called it, unless that address has explicitly authorized someone else. BugPoCer attacks that rule with Counterexample-Guided Invariant Refutation, working outward from the router as a whole, reading the rule off how the router fills its own settlement data, and executing paths with arbitrary calldata until one makes the payer and the caller come apart. Symbolic execution supports that search by solving for inputs that steer the from address, the same engine component BugScout uses when it works inward from a signal with known approval-draining strategies.
For this exploit, the counterexample would show a second address that had approved the router, a caller with no approval of its own, a route that settles through the router, and a transferFrom that pulls from the second address while the output goes to the caller. It would state the minimal conditions, an existing allowance and a from address the caller can influence, and it would come with a generated Forge test that replays the call and asserts that a non-caller's balance fell. That's the May 5 drain produced in development instead of on Ethereum.
Because Olympix runs automatically in development, at every merge and before release, the moment that would have surfaced this class is the commit that introduced settlement data read from a relative offset, before the first router was deployed in July 2025. Ekubo's own regression tests now encode the same rule, checking that a caller can't spend an approval that isn't theirs.
Every Approval Is a Promise About Who Pays
Ekubo's Core never lost a token, and its liquidity providers were never at risk. The loss sat in the thinnest layer of the stack, a router whose whole job was moving other people's tokens on their behalf, and it turned on a single question about whose tokens to move. For builders shipping anything that holds approvals, the rule that the payer is the caller deserves its own test, run against inputs an attacker writes, before the first user approves anything. To see how Olympix traces every transferFrom in your contracts back to the address that authorized it and returns a runnable counterexample where it doesn't, book a technical walkthrough.
On-Chain References
Protocol: Ekubo (EVM HuffRouter deployments)
Chain: Ethereum, Arbitrum
Date: May 5, 2026, 17:50:35 UTC (first Ethereum exploit transaction)
Loss: about $1,419,543 across 31 addresses, per Ekubo's analysis (17 WBTC from one wallet in the first transaction)
Attacker EOA: 0xa911...83e3
Attack contract: 0x61b0...3A75
Attack contract deployment tx: 0x2058...f542
First exploit tx: 0x770b...daa0
Block: 25030409
Largest victim: 0x765D...Edd1
Ekubo Core: 0xe0e0...d444
HuffRouter, Ethereum V2 (used in first exploit tx): 0x8ccb...60fd
HuffRouter, Ethereum V2: 0x8f52...0d15
HuffRouter, Ethereum V3: 0x4f16...edf2
HuffRouter, Arbitrum V3: 0xc93c...c47d
Arbitrum exploit tx (5 USDC): 0x2acc...d35a
Sources
Ekubo: Post-Mortem, HuffRouter Approval Draining via Trailing Calldata
Ekubo Blog: The Huff Router Approval Incident
Ekubo on X: Active security incident on the EVM swap router
Blockaid on X: Ongoing exploit on an Ekubo contract on Ethereum
The Block: Attackers drain $1.4M in wrapped bitcoin from Ekubo
More post-mortems
See every post-mortem →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.




