We do not have a reliable USD price for the recorded assets yet.
0x8584ddbd1e28bca4bc6fb96bafe39f850301940eEthereumOn Ethereum mainnet, an unprivileged attacker used JuiceStaking at 0x8584ddbd1e28bca4bc6fb96bafe39f850301940e to create staking positions with stakeWeek=3000000000, then harvested almost immediately. The exploit transaction sequence was 0xaa14164b3c0759274b9b430ffa88c56389aa6fd28602f3d8e81fcdd8365eb3cf to open the positions and 0xc9b2cbc1437bbcd8c328b6d7cdbdae33d7d2a9ef07eca18b4922aac0430991e7 to harvest before maturity.
The root cause is a reward-accounting flaw, not privileged access. stake() accepts an unbounded user-supplied stakeWeek, pendingReward() uses that value as a linear multiplier for the bonus term, and harvest() transfers pending + bonus without checking endTime. That combination makes the reward pool permissionlessly drainable by anyone who can buy any positive amount of JUICE and wait long enough for pending to become non-zero.
JuiceStaking maintains per-position data in mapStakingInfo, including stakedAmount, startTime, endTime, , , and . The staking program separately tracks emission state through reward variables and exposes to compute the currently harvestable amount.
stakingWeekrewardDebtunstakeStatuspendingReward(address,uint256)Three protocol behaviors matter for this incident:
stake(uint256 amount, uint256 stakeWeek) only requires stakeWeek > 0, stores stakingWeek directly, and derives endTime from that user input.pendingReward(address staker, uint256 stakeCount) computes a bonus term that scales with (stakingWeek - 1) * 9 / 100.harvest(uint256 stakeCount) pays pending + bonus before maturity, while the maturity check is only enforced in unstake().This means the lock parameter is not merely descriptive. It directly controls the bonus multiplier even before the lock has expired.
The vulnerability class is an application-layer reward-accounting bug. The contract treats an attacker-controlled configuration input as trusted economic weight, then realizes that weight through harvest() without enforcing the time assumptions implied by the lock schedule.
The violated invariant is: for any active position, rewards paid before maturity must remain bounded by elapsed emissions and must not scale with an unbounded attacker-selected lock parameter. JuiceStaking breaks that invariant because pendingReward() computes the bonus from the stored stakingWeek, and harvest() transfers the result while the position is still active.
The code-level breakpoint is the combination of:
// Simplified from the verified JuiceStaking logic described in the artifacts.
stake(amount, stakeWeek) {
require(stakeWeek > 0);
info.stakingWeek = stakeWeek;
info.endTime = block.timestamp + stakeWeek * 7 days;
}
pendingReward(staker, stakeCount) returns (pending, bonus) {
bonus = (pending * (info.stakingWeek - 1) * 9) / 100;
}
harvest(stakeCount) {
// no maturity gate here
juice.transfer(msg.sender, pending + bonus);
}
Because stakeWeek is unbounded, the attacker can choose a huge multiplier. Because harvest() has no endTime gate, the attacker does not need to wait for the lock to expire. Because pending only needs to become slightly positive, the exploit is cheap to trigger and highly asymmetric.
The relevant pre-state is Ethereum mainnet immediately before block 19395636, when JuiceStaking was live, the reward program was active, and public liquidity existed for buying JUICE. The attacker did not need any privileged role, signed authorization, or special helper from the protocol.
The first exploit step was position creation. In transaction 0xaa14164b3c0759274b9b430ffa88c56389aa6fd28602f3d8e81fcdd8365eb3cf, the attacker helper contract 0xa8b45dee8306b520465f1f8da7e11cd8cfd1bbc4 bought JUICE and opened three staking positions. The auditor’s captured state shows that stake index 0 stored:
{
"stakedAmount": "1000000000000000000000",
"startTime": "1709964335",
"endTime": "1814401709964335",
"stakingWeek": "3000000000",
"rewardDebt": "1625101588308051000",
"unstakeStatus": "0"
}
This state is already sufficient to show the defect. stakingWeek=3000000000 was accepted without rejection, and endTime was pushed absurdly far into the future instead of being bounded by the 90-day reward program.
The second exploit step was early reward realization. At block 19395643, the collected archive reads show:
{
"pendingReward_0": {
"pending": "2840549374065000",
"bonus": "766948330741900556334150"
}
}
The bonus was many orders of magnitude larger than the time-based pending component. That is the determinative evidence that the payout was dominated by the attacker-selected multiplier rather than elapsed staking time.
The harvest transaction 0xc9b2cbc1437bbcd8c328b6d7cdbdae33d7d2a9ef07eca18b4922aac0430991e7 then executed before maturity. The trace records the victim contract transferring JUICE to the attacker helper and emitting the harvest event:
JUICE::transfer(0xa8b45dEE8306b520465f1f8da7E11CD8cFD1bBc4, 894773055846326585466130)
emit Harvest(staker: 0xa8b45dEE8306b520465f1f8da7E11CD8cFD1bBc4, amount: 894773055846326585466130)
The balance-diff artifact matches the trace. JuiceStaking lost 894773055846326585466130 JUICE and the attacker helper gained the same amount in the same transaction. The position was still far from maturity because its stored endTime was 1814401709964335, vastly greater than the harvest block timestamp.
That proves the exploit mechanism end to end:
stake() with a giant stakeWeek.pending becomes positive.harvest() before maturity.bonus computed from the attacker-chosen multiplier.The adversary cluster consists of EOA 0x3fa19214705bc82ce4b898205157472a79d026be and helper contract 0xa8b45dee8306b520465f1f8da7e11cd8cfd1bbc4. The EOA deployed the helper in transaction 0x5e1c01cf5da7dfbe130d21f22dbb80ef2c1fa09b3d9b1a53795c56e6f122660b and then used it to package buy, stake, and harvest operations.
The observed exploit flow was:
0x7a250d5630b4cf539739df2c5dacb4c659f2488d.JuiceStaking positions, including one with 1000e18 JUICE and stakeWeek=3000000000.JuiceStaking.harvest(0) before the position matures.Nothing in that sequence requires attacker-exclusive infrastructure. The helper contract is only a convenience wrapper; an unprivileged EOA or freshly deployed contract could reproduce the same behavior on a fork.
The seed harvest transferred 894773055846326585466130 JUICE from JuiceStaking to the attacker helper in a single early-harvest transaction. The collected state observations also show further related harvests, which indicates the issue is repeatable as long as the staking contract still holds reward inventory.
The protocol impact is direct depletion of the reward pool. Honest stakers are diluted because rewards intended to accrue over time can instead be extracted immediately by any actor willing to provide a small seed stake and choose an extreme stakeWeek.
JuiceStaking at 0x8584ddbd1e28bca4bc6fb96bafe39f850301940eJUICE at 0xde5d2530a877871f6f0fc240b9fce117246dadae0x3fa19214705bc82ce4b898205157472a79d026be0xa8b45dee8306b520465f1f8da7e11cd8cfd1bbc40x5e1c01cf5da7dfbe130d21f22dbb80ef2c1fa09b3d9b1a53795c56e6f122660b0xaa14164b3c0759274b9b430ffa88c56389aa6fd28602f3d8e81fcdd8365eb3cf0xc9b2cbc1437bbcd8c328b6d7cdbdae33d7d2a9ef07eca18b4922aac0430991e7/workspace/session/artifacts/auditor/iter_0/key_state_observations.json/workspace/session/artifacts/collector/seed/1/0xc9b2cbc1437bbcd8c328b6d7cdbdae33d7d2a9ef07eca18b4922aac0430991e7/trace.cast.log/workspace/session/artifacts/collector/seed/1/0xc9b2cbc1437bbcd8c328b6d7cdbdae33d7d2a9ef07eca18b4922aac0430991e7/balance_diff.json