Reproduced Exploit
DIA — a new stake is paid every reward the pool ever accrued
_internalStakeForAddress records principal and never copies rewardAccumulator into the new store. The global index is already 1,000. The user's stored index stays 0.
Chain
Other
Category
logic
Date
Jun 2025
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: 57915-missing-reward-accumulator-initialization-in-stake-creation. Standalone Foundry PoC and full write-up: 57915-missing-reward-accumulator-initialization-in-stake-creation_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/data/uninitialized
Reproduction: a self-contained Foundry PoC that compiles and runs offline with only
forge-std— no fork and no RPC. Trace: output.txt. PoC: test/57915-missing-reward-accumulator-initialization-in-stake-creation_exp.sol.
Key info#
| Impact | HIGH — a new stake withdraws 1,000 WDIA of rewards accrued before it existed |
| Protocol | DIA DIAWhitelistedStaking / DIAStakingCommons |
| Vulnerable code | stakerDelta = rewardAccumulator - currentStore.rewardAccumulator |
| Bug class | Stake creation leaves the user index at 0 |
| Finding | MixBytes — DIA Lumina Staking, June 2025 · id 57915 |
| Report | Lumina Staking |
| Source | AuditVault finding |
| Compiler | solc 0.8.27, cancun, via IR |
TL;DR#
_internalStakeForAddress records principal and never copies rewardAccumulator into the new store. The global index is already 1,000. The user's stored index stays 0.
claim computes stakerDelta = 1000 - 0 and pays (1000 * 10000 ether) / 10000 = 1000 ether. The principal round-trips. Net theft is 1,000 WDIA. rewardLastUpdateTime is this block, so the update inside the claim adds nothing. run() does not warp.
The vulnerable code#
// stake() does not set newStore.rewardAccumulator
_updateRewardAccumulator();
// @> VULN: stakerDelta = rewardAccumulator - currentStore.rewardAccumulator
stakerDelta = rewardAccumulator - currentStore.rewardAccumulator;
stakerReward = (stakerDelta * currentStore.principal) / 10000;
The report quotes that subtraction. Because the store field was never written, it is 0 and the delta is the whole historical index.
Root cause#
The index is a cumulative rate. A late staker must start at the current index so they only earn what accrues after they arrive. The stake path skips that write.
Preconditions#
rewardAccumulatoris already 1,000 from earlier accrual.- The new stake's stored index is the zero default.
- Principal is 10,000 ether, so the payout is
1000 * principal / 10000 = 1000 ether. - The contract holds that 1,000 WDIA.
Attack walkthrough#
- Stake 10,000 against a global index of 1,000. The store index stays 0.
- Claim. Delta is 1,000. The transfer is 1,000 WDIA.
- Unstake the 10,000 principal.
- The caller's balance is 1,000 higher than it started. The reward pool is empty.
Control: stake sets newStore.rewardAccumulator = rewardAccumulator. The same claim pays 0 and the 1,000 stays in the contract.
Diagrams#
Impact#
Anyone who stakes after rewards have accrued is paid those historical rewards. Here that is 1,000 WDIA on a single 10,000 stake, taken from the reward balance other stakers were owed.
Remediation#
On stake creation set both rewardAccumulator and initialRewardAccumulator to the current global index, and compute rewards from the difference.
Sources#
- AuditVault finding 57915
- MixBytes DIA Lumina Staking, June 2025
- DIAWhitelistedStaking.sol at dfd0bb6
Taxonomy#
vuln/logic/reward-calculation·vuln/data/uninitialized
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 57915-missing-reward-accumulator-initialization-in-stake-creation_exp (evm-hack-registry mirror).
- AuditVault finding: 57915-missing-reward-accumulator-initialization-in-stake-creation.
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.