StakingDYNA Reward Backdating Drain
Exploit Transactions
0x06bbe093d9b84783b8ca92abab5eb8590cb2321285660f9b2a529d665d3f18e40x402ae286da42830b95c2371fdcf115b361d166db3c3721e2d43cfcecd12b36750xd1e8818b1c56b3bb82398d2a49d1abe2c4f1f366463bfd44165a4e216439128b0x60d665890c1ec6c698eca24174465179e04258cdf34d3d4e585e85a8b237924f0x64d45176c79d9616c9159f67507873b3a2119e50387edccab7b98afdfc45265c0x7cda1803f582481306201820757617da0472ad33164d784d4d397b9c05ef99fb0x7fa89d869fd1b89ee07c206c3c89d6169317b7de8b020edd42402d9895f0819e0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0Victim Addresses
0xa7b5eabc3ee82c585f5f4ccc26b81c3bd62ff3a9BSC0x5c0d0111ffc638802c9efccf55934d5c63ab3f79BSCLoss Breakdown
Similar Incidents
SNKMiner Referral Reward Drain
39%QiQi Reward Quote Override Drain
37%H2O helper-token reward drain from unauthorized claim loop
34%DBW Static-Income LP Drain
33%NeverFallToken LP Drain
33%SellToken Arbitrary-Pair LP Drain
33%Root Cause Analysis
StakingDYNA Reward Backdating Drain
1. Incident Overview TL;DR
On BNB Smart Chain, an unprivileged attacker drained 225350547299933433473959064 DYNA from StakingDYNA at 0xa7b5eabc3ee82c585f5f4ccc26b81c3bd62ff3a9. The attacker first deployed helper contract 0xd360b416ce273ab2358419b1015acf476a3b30d9, then used seven priming transactions to create 700 aged worker-specific stakes, and finally executed tx 0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0 to recycle one large DYNA balance across 258 workers. Each worker deposited a large amount and immediately redeemed it, extracting fabricated rewards that were then sold through the public DYNA/WBNB Pancake pair 0xb6148c6fa6ebdd6e22ef5150c5c3cee78b24a3a0.
The root cause is a reward-accounting bug in StakingDYNA.deposit(uint256). On a repeat deposit, the contract increases principal but does not crystallize accrued rewards or refresh lastProcessAt. The next getInterest() call therefore applies the full elapsed time to newly added principal, which lets any actor with an aged stake backdate a fresh deposit and redeem an immediate profit.
2. Key Background
StakingDYNA is a DYNA staking contract with a fixed apr = 3200, meaning 32% annualized rewards with RATE_PRECISION = 10000. Reward state is tracked per msg.sender in a StakeDetail struct containing principal, pendingReward, lastProcessAt, and firstStakeAt. Rewards are not minted; they are paid directly from the staking contract's existing DYNA balance, so any accounting error transfers real value out of the pool.
This design makes address separation important. Because each worker contract has its own StakeDetail, an attacker can manufacture many independent aged positions by deploying many simple contracts, funding each with a small DYNA amount, and calling deposit() once per worker. The collected attacker history shows exactly that pattern: helper deployment in tx 0x0cb3f64df060d97192002ae4500c83c2334bb95bb273c5ae8aa1c6446936ca1f, funding and setup, then seven helper calls that each prime 100 workers.
The monetization path was public and permissionless. DYNA had live liquidity against WBNB on Pancake, and the exploit transaction ended by swapping extracted DYNA through router 0x10ED43C718714eb63d5aA57B78B54704E256024E, sending 73.846655167007440408 WBNB to attacker profit EOA 0x35596bc57c0cab856b87854ecc142020a47f6fdf.
3. Vulnerability Analysis & Root Cause Summary
The vulnerable branch is the repeated-deposit path in StakingDYNA.deposit(uint256). On the first stake, the contract sets both firstStakeAt and lastProcessAt to the current block timestamp. On later deposits, it only adds _stakeAmount into principal. It does not first move accrued rewards into pendingReward, and it does not reset lastProcessAt.
The verified source snapshot shows the bug directly:
function getInterest(address _staker) public view returns (uint256) {
StakeDetail memory stakeDetail = stakers[_staker];
uint256 duration = block.timestamp.sub(stakeDetail.lastProcessAt);
uint256 interest = stakeDetail
.principal
.mul(apr)
.mul(duration)
.div(ONE_YEAR_IN_SECONDS)
.div(RATE_PRECISION);
return interest.add(stakeDetail.pendingReward);
}
function deposit(uint256 _stakeAmount) external {
token.transferFrom(msg.sender, address(this), _stakeAmount);
StakeDetail storage stakeDetail = stakers[msg.sender];
if (stakeDetail.firstStakeAt == 0) {
stakeDetail.principal = stakeDetail.principal.add(_stakeAmount);
stakeDetail.firstStakeAt = block.timestamp;
stakeDetail.lastProcessAt = block.timestamp;
} else {
stakeDetail.principal = stakeDetail.principal.add(_stakeAmount);
}
}
The broken invariant is straightforward: rewards over interval [lastProcessAt, t] must only apply to principal that was staked during that interval. StakingDYNA violates that invariant by letting fresh principal inherit an old lastProcessAt. The code-level breakpoint is the else branch in deposit() where principal changes but reward checkpoint state does not.
4. Detailed Root Cause Analysis
The exploit relies on two phases: first create aged stakes, then backdate large deposits against those old timestamps.
In the priming phase, tx 0x06bbe093d9b84783b8ca92abab5eb8590cb2321285660f9b2a529d665d3f18e4 shows the helper distributing small DYNA amounts into worker contracts and each worker calling deposit(). The receipt contains 100 Deposit events on StakingDYNA, and six later helper calls repeat the same pattern, producing 700 primed workers in total. A representative worker is 0x0196395b9f72c210bd772119d249276c987316f1, which was primed in that first batch.
In the exploit phase, the attacker waited about 585528 seconds, then ran tx 0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0. For worker 0x0196395b9f72c210bd772119d249276c987316f1, the aged pre-state was principal = 9.509900499 DYNA and lastProcessAt = 1676458664. Inside the exploit trace, that worker immediately deposited 64518684.750699557630064907 DYNA and then redeemed 64902018.047956864688208150 DYNA.
That overpayment matches the buggy formula exactly. With total principal after the new deposit equal to 64518694.260600056630064907 DYNA, APR 32%, and dt = 585528, the contract computes total accrued interest of 383333.353759722272536668 DYNA. Redeeming only the freshly added amount yields a claim of 383333.297257307058143243 DYNA, so the returned amount is:
fresh deposit = 64518684.750699557630064907 DYNA
claim amount = 383333.297257307058143243 DYNA
redeem total = 64902018.047956864688208150 DYNA
That is the exact Redeem amount emitted on-chain. The exploit therefore is not an approximation or market effect; it is the deterministic output of the buggy accounting path.
The exploit trace also shows that the helper contract immediately forwards the returned DYNA to the next worker and repeats the sequence. The same principal is recycled across 258 pre-aged workers, so the attacker compounds fabricated rewards without needing 258 independent large bankrolls. Because redeem() pays from the staking contract's existing DYNA balance, each iteration directly depletes StakingDYNA.
5. Adversary Flow Analysis
The attacker-controlled cluster is:
- EOA
0x0c925a25fdaac4460cab0cc7abc90ff71f410094: deployed the helper and submitted every priming and exploit transaction. - Helper
0xd360b416ce273ab2358419b1015acf476a3b30d9: orchestrated worker deployment, staking, recycling, and final swap. - Many worker contracts such as
0x0196395b9f72c210bd772119d249276c987316f1: each held its own aged staking record. - Profit EOA
0x35596bc57c0cab856b87854ecc142020a47f6fdf: received WBNB proceeds.
The end-to-end sequence is:
0x0cb3f64df060d97192002ae4500c83c2334bb95bb273c5ae8aa1c6446936ca1fThe attacker deploys helper contract0xd360....0x06bbe093d9b84783b8ca92abab5eb8590cb2321285660f9b2a529d665d3f18e4First priming batch. The receipt shows 100 workerDepositevents intoStakingDYNA.0x402ae286da42830b95c2371fdcf115b361d166db3c3721e2d43cfcecd12b36750xd1e8818b1c56b3bb82398d2a49d1abe2c4f1f366463bfd44165a4e216439128b0x60d665890c1ec6c698eca24174465179e04258cdf34d3d4e585e85a8b237924f0x64d45176c79d9616c9159f67507873b3a2119e50387edccab7b98afdfc45265c0x7cda1803f582481306201820757617da0472ad33164d784d4d397b9c05ef99fb0x7fa89d869fd1b89ee07c206c3c89d6169317b7de8b020edd42402d9895f0819eSix more priming batches, taking the worker set to 700 aged stakes.0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0The helper cycles DYNA through 258 aged workers, collects the fabricated rewards, and swaps DYNA into WBNB.
The exploit trace excerpt below captures the decisive flow inside the final transaction:
emit Deposit(user: 0x0196395b9f72c210bd772119d249276c987316f1, amount: 64518684750699557630064907)
emit Redeem(user: 0x0196395b9f72c210bd772119d249276c987316f1, amount: 64902018047956864688208150)
...
0x10ED43C718714eb63d5aA57B78B54704E256024E::swapExactTokensForTokensSupportingFeeOnTransferTokens(
216581548421891486166916268,
0,
[DYNA, WBNB],
0x35596bc57c0Cab856b87854EcC142020A47f6fdF,
1677044192
)
...
emit Transfer(from: PancakePair: [0xb6148c6fA6Ebdd6e22eF5150c5C3ceE78b24a3a0], to: 0x35596bc57c0Cab856b87854EcC142020A47f6fdF, value: 73846655167007440408)
The attacker did not need any privileged access. Every step used public contracts, attacker-deployed helper code, and standard token transfers and swaps.
6. Impact & Losses
The measured protocol loss is 225350547299933433473959064 DYNA, taken directly from the collector balance diff for exploit tx 0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0. The staking pool balance changed from 225352968127419151994856842 DYNA before the exploit to 2420827485718520897778 DYNA after it. This is effectively a full drain of the reward inventory held by StakingDYNA.
The attacker monetized the stolen DYNA on the live DYNA/WBNB market. The exploit receipt and trace show a direct transfer of 73846655167007440408 wei of WBNB, equal to 73.846655167007440408 WBNB, to profit EOA 0x35596bc57c0cab856b87854ecc142020a47f6fdf. The sender EOA separately paid 0.285331529 BNB in gas and the attacker cluster still remained strongly net positive.
The incident also caused secondary market harm. Because the attacker dumped extracted DYNA into public Pancake liquidity, the exploit converted the accounting failure into immediate market impact for DYNA holders in addition to draining the staking pool.
7. References
- Victim staking contract:
StakingDYNAat0xa7b5eabc3ee82c585f5f4ccc26b81c3bd62ff3a9 - DYNA token:
0x5c0d0111ffc638802c9efccf55934d5c63ab3f79 - Public monetization venue: Pancake DYNA/WBNB pair
0xb6148c6fa6ebdd6e22ef5150c5c3cee78b24a3a0 - Verified victim source used for validation:
StakingDYNAsource snapshot collected from the explorer - Attacker history used for validation: EOA tx list for
0x0c925a25fdaac4460cab0cc7abc90ff71f410094 - Priming transaction sample:
0x06bbe093d9b84783b8ca92abab5eb8590cb2321285660f9b2a529d665d3f18e4 - Exploit transaction:
0xc09678fec49c643a30fc8e4dec36d0507dae7e9123c270e1f073d335deab6cf0 - Supporting evidence types reviewed: collected traces, transaction metadata, balance diffs, exploit receipt logs, and verified source code