ether.fi Liquid's AtomicQueue.solve() Trusted a Caller-Supplied Solver, and Existing ERC-20 Allowances Were Drained

What happened
Per SlowMist's record, users of ether.fi Liquid (liquidETH) were exploited on 2026-09-11. The reported cause is that AtomicQueue.solve() applied no access control to the caller-supplied solver parameter. The attacker constructed a malicious AtomicRequest placing addresses that had already approved the contract into the solver role, and drained their remaining ERC-20 allowances through transferFrom. About 11 users were affected, for roughly 15.45 ETH — about USD 38,130, or JPY 5.81 million at 152.27 JPY/USD (Google Finance, 2026-09-13), implying an ETH price near USD 2,468.
AtomicQueue batches redemption and exchange requests, with an actor called the solver executing the actual asset movement. Who the solver is can legitimately vary for off-chain reasons, so accepting it as a parameter is unremarkable in itself. The defect is that the contract never checked whether the supplied address had authorised being the source of funds for this operation.
A structurally identical case appeared the same week on BNB Chain. According to SlowMist, on 2026-09-07 an unnamed DEX router failed to authenticate that uniswapV3SwapCallback was invoked by a genuine V3 pool; the attacker supplied a fake pool, set victims as payer and consumed their existing allowances via transferFrom, taking roughly 62.28 WBNB — about USD 46,070, or JPY 7.02 million — from 29 wallets in a single transaction.
No official post-mortem from ether.fi or from the router in question could be found at the time of writing; what follows rests on third-party analysis.
The shape both defects share
Different protocols, different chains, identical defect: a contract treats an address supplied in calldata as the source of funds without verifying that the address authorised this particular operation. An ERC-20 allowance says "this contract may pull from my balance." It does not say "this contract may pull from my balance on anyone's instruction." If the contract never implements the distinction, the two become the same thing.
What makes this class nasty is that the victim did nothing wrong. The approval was granted to a legitimate protocol; no phishing site was signed, no suspicious contract approved. The attack originates inside the code the user trusted, not in the user's behaviour. This is not a category of loss that user education prevents.
Unlimited approvals amplify the blast radius. Approving uint256 max once to save gas has been common practice for years, but it is also a bet that the approved contract will keep validating its arguments correctly forever. One missing check loses that bet. The losses here were small only because the outstanding allowances happened to be small.
The limits of the approval model itself
An ERC-20 approval expresses one thing: a maximum amount. It cannot express on whose instruction, for which operation, or until when the funds may be pulled. That thinness concentrates all responsibility in the approved contract's code. Newer mechanisms such as Permit2 and EIP-2612 add expiry and signature-scoped approvals, but none of them replaces the underlying premise that the approved contract validates its arguments correctly. An expiry narrows the window of exposure; it does not change the outcome if the same defect is exploited inside that window.
In practice the mitigation is two-layered. The contract layer either avoids taking the source of funds from calldata at all, or — if it must — verifies explicit consent from that source (a signature, prior registration, or equality with msg.sender). The user layer simply does not leave unused approvals outstanding. If the first layer holds, the second is unnecessary; if the second holds, a break in the first stays contained. Both are cheap to implement, and both are routinely skipped.
Detection deserves a mention too. From the victim's wallet, an attack like this appears as a balance decrease from a transaction the owner never sent. Monitoring outstanding allowance per spender alongside actual pulls against it would likely have caught the first occurrence. On the protocol side, watching the distribution of addresses appearing in the solver or payer position would show an anomaly the moment a set that is normally a handful of addresses suddenly diversifies.
Pitfalls to watch
First, this class of defect is invisible to any assessment based on "how much value is locked in the contract." A contract with zero TVL still has drainable assets if allowances remain outstanding. Audits and risk reviews that look only at locked balances miss the second surface entirely.
Second, functions designed to be called from outside — callbacks and solver entry points — are exactly where validation gets skipped. Because the design assumes "the pool calls this" or "the solver calls this," the assumption often lives in a comment rather than in code. For Uniswap V3-style callbacks there is a standard pattern for proving the caller is a genuine pool; omitting it reproduces this hole exactly.
Third, the blast radius is hard to establish. Which users hold which outstanding allowances is knowable from the protocol side but generally unknown to the users themselves. Telling people to revoke after the fact offers no guarantee of reaching them. Exposure at ether.fi stopped at about 11 users because few had allowances left, not because any design control held.
Fourth, these are reported without clarity on whether fixes have shipped. Searching your own codebase for the same pattern does not require waiting for anyone else's patch — and conversely, as long as you have a function of the same shape, someone else's small loss guarantees you nothing.
[Technical Insights & Actions]
- An ERC-20 allowance does not mean "pull from me on anyone's instruction," but inside a contract that skips argument validation it effectively does. Every approval you have ever granted is a standing bet that the approved code will keep validating its arguments — a bet re-placed at every code change. [Contract developers] This week, enumerate every path that calls transferFrom, identify where the source address originates in calldata, and confirm that the source authorised this specific operation.
- Callbacks and solver entry points are precisely where validation is omitted, because they are meant to be called from outside. A function whose expected caller is documented only in a comment is, in practice, a function anyone can call. [Contract developers / auditors] Before your next release, list every externally-invoked function and pin validation of caller-derived and parameter-derived addresses with tests that explicitly assert failure for an illegitimate caller.
- The object of risk assessment is outstanding approvals, not locked balances. Even a contract with small TVL has an effective loss ceiling equal to the sum of unlimited approvals users have left pointing at it. [Product and operations] Within two weeks, measure the total outstanding allowance against your protocol, evaluate moving the UI from unlimited approvals to exact-amount approvals, and confirm the channel you would use to prompt users to revoke in an emergency.
[Sources]
- SlowMist Hacked (incident database, Ether.fi Liquid entry, 2026-09-11): https://hacked.slowmist.io/
- SlowMist Team (@SlowMist_Team), technical analysis (2026-09-11): https://x.com/SlowMist_Team/status/2098344499923784048
- SlowMist Team (@SlowMist_Team), analysis of the BNB Chain DEX router case (2026-09-07): https://x.com/SlowMist_Team/status/2097153762746159228
This article is a technical commentary based on publicly available information and is not investment advice. No official post-mortem from the affected protocols was available at the time of writing; the description of the attack paths relies on third-party analysis.
