Reproduced Exploit
Tokemak: removing an empty destination drops 250 rewards
The exit calls rewarder.withdraw with claim false, then pops the destination from debtReportQueue. The next updateDebtReporting does not see it, so the 250 is never added to totalIdle.
Chain
Other
Category
logic
Date
Jul 2024
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: 53523-toke-6-loss-of-reward-from-destinationvaultmainrewarder-if-t. Standalone Foundry PoC and full write-up: 53523-toke-6-loss-of-reward-from-destinationvaultmainrewarder-if-t_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/logic/incorrect-order-of-operations
Key info#
| Protocol | Tokemak |
| Finding | Hexens TOKE-6, AuditVault 53523 |
| Vulnerable code | AutoPool._beforeTokenTransfer |
| Harm | A full exit leaves 250 of accrued rewards in the main rewarder and totalIdle stays 0. The fixed path claims first and totalIdle is 250. |
| Chain | local synthetic |
TL;DR#
The exit calls rewarder.withdraw with claim false, then pops the destination from debtReportQueue. The next updateDebtReporting does not see it, so the 250 is never added to totalIdle.
The vulnerable code#
// @> VULN: claim is false, so accrued reward is not transferred to the auto pool
rewarder.withdraw(from, amount, false);
Root cause#
The queue removal assumes an empty destination has nothing left to report. The rewarder still holds rewards because the exit did not claim them.
Preconditions#
The destination is on the debt queue with 250 of rewards already accounted to the auto pool.
Attack walkthrough#
- Load the destination and fund the rewarder with 250. See output.txt.
flashRebalanceOutsets the balance to 0.- The hook withdraws the stake with
claimfalse. - The destination is popped from all three queues.
updateDebtReportingreturns because the queue is empty.totalIdleis 0 and the rewarder still holds 250. The fixed path claims first.
Diagrams#
Impact#
The auto pool's idle balance misses 250. The fixed order claims before the pop, and totalIdle becomes 250.
Remediation#
Claim the rewarder balance and add it to totalIdle before removing the destination from the queues.
How to reproduce#
cd 53523-toke-6-loss-of-reward-from-destinationvaultmainrewarder-if-t_exp
forge test -vv
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 53523-toke-6-loss-of-reward-from-destinationvaultmainrewarder-if-t_exp (evm-hack-registry mirror).
- AuditVault finding: 53523-toke-6-loss-of-reward-from-destinationvaultmainrewarder-if-t.
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.