Policy

ether.fi's AtomicQueue Exploit: One Missing Check, Fifteen ETH, and a Class of Bug

CryptoPomp

15.45 ETH. SlowMist flagged it. A handful of solver accounts on ether.fi's AtomicQueue contract had balances pulled out from under them by a caller with no right to touch them. No oracle failure. No flash loan. No governance capture. No leaked key. Just a function that forgot to ask who was calling it.

The dollar figure is a rounding error against protocol TVL. Whatever ETH trades at, the attacker walked away with less than the cost of a decent validator. That is why most readers will scroll past. The size of a loss measures the attacker's patience, not the severity of the defect. A missing access-control check is a class of bug, not an instance of one. ether.fi shipped one in AtomicQueue. The question nobody is asking loudly enough is how many other entry points assume the same thing and got lucky.

Where AtomicQueue sits

ether.fi is a liquid restaking protocol. Users deposit ETH, it gets staked and restaked, the protocol issues a receipt token. That receipt — eETH, with weETH as the wrapped non-rebasing version — is a liquid restaking token, an LRT. The product promise is composability: hold the receipt, keep the staking exposure, still deploy the receipt as collateral elsewhere.

Stack it up. L1 Ethereum and staking infrastructure at the bottom. ether.fi in the middle, converting staked ETH into a transferable claim. Downstream, lending markets and AMMs accept weETH as collateral. Pull out the middle layer and the structure stops clearing.

ether.fi's position in that stack is not small. It ranks among the larger LRT issuers, which means its eETH and weETH variants are integrated across major lending markets and AMMs. Score the peers for comparison — Renzo, KelpDAO, the rest of the restaking cohort. Every one of them runs an internal settlement layer of some kind.

AtomicQueue is an internal component of that middle layer. It handles atomic settlement — requests to exchange one asset for another, matched and executed in one transaction. No AMM pool, no slippage curve. A counterparty called a solver fills the request. Modules like this exist because they are cheaper and cleaner than routing size through a constant-product pool.

SlowMist's disclosure was specific. solve() does not verify that the caller is the solver it names. That is the whole bug. One missing comparison.

As of the material I reviewed, no confirmed post-incident report, no confirmed patch commit. Everything below is reconstruction from the disclosure, not the team's own words.

The mechanics: how an allowance becomes an attack surface

ERC-20 offers exactly one authorization primitive that matters here. approve() sets an allowance. transferFrom() lets the approved spender move tokens inside that allowance. The spender does not have to be the holder. That is by design. It is also a loaded gun handed to every contract at birth.

Intended flow in AtomicQueue: a user submits a request via updateAtomicRequest(); a solver fills it; solve() settles and calls want.transferFrom(solver, ...) to move the solver's tokens. The solver is a parameter. The caller is msg.sender. The contract assumes those are the same address. It never enforces it.

The exploit chain is four steps.

updateAtomicRequest() places no restriction on who can construct a request. An attacker builds an AtomicRequest with arbitrary fields.

The attacker sets the solver slot to a victim address — one that has previously approved the AtomicQueue contract to spend its tokens. That approval is the entry ticket.

The attacker calls solve(). Inside, finishSolve executes want.transferFrom(solver, ...). The solver variable now points at the victim. The contract pulls from the victim's balance, spending the victim's own allowance, into the settlement.

The attacker captures the output. 15.45 ETH, several victims, one transaction.

Two preconditions. The victim granted an allowance to AtomicQueue at some point. The attacker could name an arbitrary address in the solver slot. Neither is exotic. The first is what every user does before touching a new protocol. The second is what happens when a parameter is trusted instead of validated.

The blast radius deserves a precise boundary. AtomicQueue is not the eETH token contract. The vulnerability did not mint, burn, or reprice the receipt asset. Users who never granted an allowance to AtomicQueue had nothing to lose here, and weETH collateral sitting in Aave or Morpho was not impaired by this transaction. That distinction matters, because the reflex after any security headline is to assume the token is broken. It wasn't. The attack surface was an internal settlement path, and the victims were the addresses that had pre-authorized it.

The fix is one line. require(msg.sender == solver) at the top of solve(). Or delete the solver parameter and read msg.sender directly — strictly better, because it makes injection impossible instead of merely checked. One commit either way.

The fix isn't the story. Why it was needed is. In 2017, still a student in Dublin, I spent the final hour of the Status Network token sale reading its minting contract. I found an integer overflow in the supply function and reported it privately before mainnet. That was a wrong number. You find wrong numbers by reading arithmetic and asking what happens at the boundary. This was a wrong assumption. There is no wrong number to find. The function does exactly what its name says — it solves. It becomes dangerous only when you read it beside the function that calls it, and then beside the approval that predates both. Auditors miss these because the unit of review is the function and the defect lives at the seam. Pull-based authorization is a trust boundary, not a logic branch, and most audit hours go to logic branches.

Checking your own exposure

The chart is a map, not the territory. What actually happened is that an allowance granted months ago, possibly for a swap that already settled, sat live in a contract that could be told who to pull from. No expiry, no scope, no counterparty verification.

I learned this the expensive way in 2020. Running my SNX staking position, I calculated the collateralization ratio by hand on a local node before touching anything, and I kept the allowance wallet separate from the position wallet. Tedious. It is the only reason a bug like this would have cost me nothing. The practice is not sophisticated: a burner address holding only what the current transaction needs. Most people don't. Most people are the victims in disclosures like this one.

The check is short. Pull your address into revoke.cash, or read the allowance mapping on Etherscan against the AtomicQueue contract. Revoke anything you are not actively using. It costs gas. That gas is cheaper than the alternative.

The contrarian read

The dominant framing is that only users who approved were affected. Read that again. It quietly converts a protocol defect into a user error. Comfortable for protocols, and wrong. Approve is the only interface ERC-20 offers. Every router, vault, and staking contract requires it. Building on a primitive and then blaming users for using it is not a security posture. It is a liability transfer.

The second framing: the loss was small, so the market should shrug. The number is being used as an argument for calm. It is an argument for luck — a larger solver allowance, or a fatter cached balance, and this headline reads differently. It will shrug anyway. It always does. In 2022 I watched the same reflex through the Terra collapse — day one was analysis, day three was amnesia. Yield is just risk wearing a smiley face, and the smiley face always comes back.

There is a cleaner answer than require statements, and the industry keeps not adopting it. Scoped allowances. Per-transaction permits. Approvals that expire with the block. Permit2 exists. Teams skip it because approve is one line and Permit2 is a week of integration. The friction being skipped is exactly the friction that would have made this attack impossible rather than merely unprofitable.

Takeaway

In order. Revoke any outstanding allowance against ether.fi's AtomicQueue — revoke.cash, or the mapping on Etherscan. Watch protocol TVL on DefiLlama across seven days; a one-day dip is noise, a week is a trend. Watch for a post-incident report containing the commit hash of the patch, not just a statement. A patch without a hash is a claim.

Emotion is the only variable I cannot hedge. 15.45 ETH is not the point. The point is that a contract trusted with user allowances did not verify who was calling it, and that failure mode is not unique to ether.fi. The question isn't whether this gets fixed. It's how many other contracts are one require() statement away from the same headline — and how much liquidity is sitting behind that statement. Liquidity doesn't exist until you need it.

Market Prices

BTC Bitcoin
$80,844.1 +5.47%
ETH Ethereum
$2,585.3 +4.80%
SOL Solana
$110.89 +9.80%
BNB BNB Chain
$757.7 +4.19%
XRP XRP Ledger
$1.38 +5.91%
DOGE Dogecoin
$0.0876 +7.22%
ADA Cardano
$0.2192 +8.35%
AVAX Avalanche
$8.11 +6.95%
DOT Polkadot
$1.13 +7.18%
LINK Chainlink
$12.21 +7.67%

Fear & Greed

56

Greed

Market Sentiment

7x24h Flash News

More >
{{快讯列表(10)}} {{loop}}
{{快讯时间}}

{{快讯内容}}

{{快讯标签}}
{{/loop}} {{/快讯列表}}

Event Calendar

{{年份}}
30
04
upgrade Celestia Mainnet Upgrade

Improves data availability sampling efficiency

08
04
upgrade Solana Firedancer

Independent validator client goes live on mainnet

10
05
upgrade Ethereum Pectra Upgrade

Raises validator limit and account abstraction

18
03
unlock Sui Token Unlock

Team and early investor shares released

15
04
halving Bitcoin Halving

Block reward reduced to 3.125 BTC

12
05
halving BCH Halving

Block reward halving event

28
03
unlock Arbitrum Token Unlock

92 million ARB released

22
03
unlock Optimism Unlock

Circulating supply increases by about 2%

Tools

All →

Altseason Index

41

Bitcoin Season

BTC Dominance Altseason

Gas Tracker

Ethereum 28 Gwei
BNB Chain 3 Gwei
Polygon 42 Gwei
Arbitrum 0.5 Gwei
Optimism 0.3 Gwei

Market Cap

All →
1
Bitcoin
BTC
$80,844.1
1
Ethereum
ETH
$2,585.3
1
Solana
SOL
$110.89
1
BNB Chain
BNB
$757.7
1
XRP Ledger
XRP
$1.38
1
Dogecoin
DOGE
$0.0876
1
Cardano
ADA
$0.2192
1
Avalanche
AVAX
$8.11
1
Polkadot
DOT
$1.13
1
Chainlink
LINK
$12.21

🐋 Whale Tracker

🟢
0x83b8...7fa2
1h ago
In
9,324,396 DOGE
🔴
0x1d31...9b0e
12h ago
Out
315,372 USDT
🟢
0xc200...2d9a
3h ago
In
4,468 ETH

💡 Smart Money

0xebe7...7069
Top DeFi Miner
+$4.9M
67%
0x230b...5cca
Arbitrage Bot
+$0.8M
60%
0xf791...30f8
Market Maker
+$2.1M
77%