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.

Jun 2025•Other•logic•3 min read

Chain

Other

Category

logic

Date

Jun 2025

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

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.

Loading fork state…

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-registry mirror.


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#

ImpactHIGH — a new stake withdraws 1,000 WDIA of rewards accrued before it existed
ProtocolDIA DIAWhitelistedStaking / DIAStakingCommons
Vulnerable codestakerDelta = rewardAccumulator - currentStore.rewardAccumulator
Bug classStake creation leaves the user index at 0
FindingMixBytes — DIA Lumina Staking, June 2025 · id 57915
ReportLumina Staking
SourceAuditVault finding
Compilersolc 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#

SOLIDITY
// 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#

  • rewardAccumulator is 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#

  1. Stake 10,000 against a global index of 1,000. The store index stays 0.
  2. Claim. Delta is 1,000. The transfer is 1,000 WDIA.
  3. Unstake the 10,000 principal.
  4. 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#

flowchart TD A["Global index is already 1000"] --> B["New store index stays 0"] B --> C["Delta is 1000 minus 0"] C --> D["Claim pays 1000 WDIA"] D --> E["Principal comes back too"]

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#

Taxonomy#

  • vuln/logic/reward-calculation · vuln/data/uninitialized

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

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.