Reproduced Exploit
A removed operator still claims 1,000 rewards because removal does not touch the balance
The operator has 1,000 unclaimed rewards. Governance removes them. isOperator becomes false and operatorRewards is still 1,000. claim transfers the 1,000. The control removal zeroes the balance, sends the 1,000 to the pool, and the same claim pays 0.
Loss
The removed operator claims 1,000 rewards. Seizing on removal sends that 1,000 to the pool and the later clai…
Chain
Ethereum
Category
logic
Date
Aug 2023
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr. Standalone Foundry PoC and full write-up: 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/access-control/broken-logic
Reproduction: self-contained synthetic Foundry reduction; see output.txt.
Key info#
| Field | Value |
|---|---|
| Loss | The removed operator claims 1,000 rewards. Seizing on removal sends that 1,000 to the pool and the later claim pays 0 |
| Vulnerable contract | PoolGovernance26461.deleteOperators (reduction of Smoothly PoolGovernance) |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2023-08 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | deleteOperators clears isOperator and leaves operatorRewards unchanged |
TL;DR#
The operator has 1,000 unclaimed rewards. Governance removes them. isOperator becomes false and operatorRewards is still 1,000. claim transfers the 1,000. The control removal zeroes the balance, sends the 1,000 to the pool, and the same claim pays 0.
Background#
Pashov, Smoothly, C-02, 1 August 2023. deleteOperators is the path used when an operator is slashed or has gone inactive. It removes the account from PoolGovernance and does not move operatorRewards. A malicious operator who was removed can still claim. An inactive operator's balance stays in the contract with no one to sweep it. The report's fix is to transfer the rewards to a chosen account, for example SmoothlyPool, as part of removal.
The vulnerable code#
function deleteOperators(address op) external {
// @> VULN: the operator is removed from governance but operatorRewards[op] is left intact, so the removed operator can still claim.
isOperator[op] = false;
}
claim then pays operatorRewards[op] and zeroes it. Removal never reaches that mapping.
Root cause#
The operator flag and the reward balance are separate, and only the flag is updated. Claim does not check isOperator. Whoever is still in the mapping gets paid.
Preconditions#
- The operator has a non-zero
operatorRewardsbalance. Here it is 1,000, and the governance contract holds the tokens. - Governance calls
deleteOperators. That is the intended slash path, not a malicious admin. The harm is the removed operator callingclaimafterwards.
Attack walkthrough#
- Accrued rewards are 1,000 (output.txt:9).
- After removal the balance on the books is still 1,000 (output.txt:11).
- The removed operator claims 1,000 (output.txt:13).
- Control seizes 1,000 to the pool (output.txt:15). The later claim pays 0 (output.txt:17).
[PASS](output.txt:6).
Diagrams#
Impact#
Rewards that were supposed to be slashed are paid to the removed operator. The pool is short 1,000. On the control path the pool receives the 1,000 and the operator receives 0.
Remediation#
On removal, read operatorRewards[op], set it to zero, and transfer that amount to the pool before the operator can claim.
How to reproduce#
cd 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr_exp
forge test -vv
Sources#
- AuditVault finding: 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr
- Pashov Audit Group, Smoothly, 1 August 2023: https://github.com/solodit/solodit_content/blob/main/reports/Pashov/2023-08-01-Smoothly.md
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr_exp (evm-hack-registry mirror).
- AuditVault finding: 26461-c-02-operator-can-still-claim-rewards-after-being-removed-fr.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.