All incidents

Jump Farm Epoch Harvest

Share
Sep 05, 2023 12:42 UTCAttackLoss: 2.41 WETHPending manual check1 exploit txWindow: Atomic
Estimated Impact
2.41 WETH
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Sep 05, 2023 12:42 UTC → Sep 05, 2023 12:42 UTC

Exploit Transactions

TX 1Ethereum
0x6189ad07894507d15c5dff83f547294e72f18561dc5662a8113f7eb932a5b079
Sep 05, 2023 12:42 UTCExplorer

Victim Addresses

0x05999eb831ae28ca920ce645a5164fbdb1d74fe9Ethereum
0x1e5f868a297fa80e6a03df69bc3e21c8145fcbb4Ethereum
0xab01b9419ed5b82c9342886206eacc5059268cb3Ethereum

Loss Breakdown

2.41WETH

Similar Incidents

Root Cause Analysis

Jump Farm Epoch Harvest

1. Incident Overview TL;DR

On Ethereum mainnet block 18070347, attacker EOA 0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6 used freshly deployed helper contract 0x154863eb71de4a34f88ea57450840eab1c71aba6 to execute exploit transaction 0x6189ad07894507d15c5dff83f547294e72f18561dc5662a8113f7eb932a5b079. The helper borrowed 15 WETH from Balancer, bought JUMP, repeatedly alternated Jump Farm stake and unstake(..., true) calls while the staking epoch was badly stale, then sold the inflated JUMP back to WETH and exited with profit.

The root cause is a permissionless stale-epoch catch-up path in Jump Farm Staking. Both stake() and unstake(..., true) call rebase() before moving balances, while rebase() advances only one overdue epoch per call. When many epochs are overdue, a flash-loaned attacker can repeatedly enter and exit within one transaction and harvest rewards that should have accrued only to long-lived stakers.

2. Key Background

Jump Farm uses rebasing sJUMP as the staked representation of JUMP. A rebase increases redeemable value by adjusting the rebasing token supply accounting rather than minting a separate claim token. As a result, a user that is staked during a rebase can later redeem more underlying JUMP.

The staking contract uses 8-hour epochs. If no one calls rebase() for a long period, epoch.end can lag far behind block.timestamp, leaving a backlog of overdue epochs.

Reward issuance is funded through the protocol treasury. Distributor.nextReward() is capped by Treasury.excessReserves(), so as long as treasury reserves remain positive, each successful overdue-epoch settlement can mint fresh JUMP into the staking system.

3. Vulnerability Analysis & Root Cause Summary

The bug is a protocol-level accounting failure in reward settlement, not a privileged compromise. Jump Farm exposed a public catch-up mechanism for overdue epochs but settled only one epoch per call. That design becomes exploitable because the same transaction can call back into the public staking entry points repeatedly. The vulnerable ordering is critical: stake() rebases before accepting fresh JUMP, and unstake(..., true) rebases before redeeming sJUMP. This lets transient capital appear staked for each overdue settlement step even though it was not continuously staked across the elapsed time. Every catch-up step also triggers Distributor::distribute(), which mints new JUMP through Treasury while reserves stay positive. The result is that one attacker can serialize many delayed reward settlements into one flash-loaned transaction and extract the backlog as immediate sellable value.

4. Detailed Root Cause Analysis

The verified staking code shows the exploitable ordering directly:

function stake(address _to, uint256 _amount) external {
    rebase();
    TOKEN.transferFrom(msg.sender, address(this), _amount);
    sTOKEN.transfer(_to, _amount);
}

function unstake(address _to, uint256 _amount, bool _rebase) external {
    if (_rebase) rebase();
    sTOKEN.transferFrom(msg.sender, address(this), _amount);
    TOKEN.transfer(_to, _amount);
}

function rebase() public {
    if (epoch.end <= block.timestamp) {
        sTOKEN.rebase(epoch.distribute, epoch.number);
        epoch.end = epoch.end + epoch.length;
        epoch.number++;
        distributor.distribute();
        ...
    }
}

This creates the broken invariant: an elapsed epoch reward should accrue only to stake that remained present through that epoch, but Jump Farm instead let a caller repeatedly become the current staker immediately before each overdue epoch settlement. Because rebase() advances only one epoch per call, the contract remains callable again when many epochs are overdue.

Reward monetization is also explicit in verified code:

function distribute() external {
    require(msg.sender == staking, "Only staking");
    treasury.mint(staking, nextReward());
}

function nextReward() public view returns (uint256 _reward) {
    uint256 excessReserves = treasury.excessReserves();
    _reward = nextRewardAt(rate);
    if (excessReserves < _reward) _reward = excessReserves;
}
function excessReserves() external view returns (uint256 value_) {
    uint256 _balance = IERC20(WETH).balanceOf(address(this));
    uint256 _value = (_balance * 1e9) / BACKING;
    if (IERC20(TOKEN).totalSupply() > _value) return 0;
    return (_value - IERC20(TOKEN).totalSupply());
}

The exploit trace confirms the mechanism. Inside the single seed transaction, the helper first receives a Balancer flash loan, swaps WETH for JUMP, then alternates Staking::stake and Staking::unstake(..., true). The trace repeatedly shows emit LogSupply(epoch: 20 ...), Distributor::distribute(), and Treasury::mint(...), then continues through overdue epochs up to 81. That is direct evidence that the attacker consumed a large stale backlog one epoch at a time inside one transaction.

The submitted profit predicate is deterministic. The native balance diff for the attacker EOA shows before_wei = 26741732613979005590, after_wei = 28932182029895228702, and delta_wei = 2190449415916223112. The helper realized 2408896405802220360 wei gross WETH/ETH before gas, and the net difference is consistent with 218446989885997248 wei of fees/gas.

5. Adversary Flow Analysis

The attack had two transactions:

  1. 0x55a4858c663267dd5e2ed6a5987873e6d5e22cb86955735bbbd9e0a09fe97508 The attacker EOA deployed helper contract 0x154863eb71de4a34f88ea57450840eab1c71aba6.

  2. 0x6189ad07894507d15c5dff83f547294e72f18561dc5662a8113f7eb932a5b079 The helper executed the exploit end to end.

Representative trace sequence:

BalancerVault::flashLoan(..., [WETH], [15 ether], ...)
UniswapV2Router::swapExactTokensForTokens(15 ether, 0, [WETH, JUMP], helper, ...)
Staking::stake(helper, 277039687340311)
emit LogSupply(epoch: 20, ...)
Distributor::distribute()
Treasury::mint(Staking, 12081089504435)
Staking::unstake(helper, 277039687340311, true)
emit LogSupply(epoch: 21, ...)
...
WETH9::withdraw(314409646579908154)

Operationally, the helper used flash-loaned WETH to buy JUMP, staked JUMP, let rebase() settle one overdue epoch, unstaked with _rebase=true to settle the next overdue epoch while still holding sJUMP, then repeated. Each pass made the redeemed JUMP balance larger. Once the backlog was exhausted, the helper sold JUMP back to WETH, repaid Balancer, withdrew residual WETH to ETH, and sent the proceeds to the originating EOA.

6. Impact & Losses

The immediate measurable loss was 2408896405802220360 wei of WETH-equivalent value, or 2.408896405802220360 WETH, extracted from the exploit path's market exit. This value came from monetizing Treasury-backed inflation that should have been distributed over time to legitimate stakers rather than captured instantly by transient capital.

The exploit also caused protocol state distortion beyond the direct realized profit. Repeated backlog settlement increased JUMP supply through the Distributor/Treasury path and exhausted delayed epoch rewards in a way that violated the intended time-weighted staking model.

7. References

  • Seed exploit transaction: 0x6189ad07894507d15c5dff83f547294e72f18561dc5662a8113f7eb932a5b079
  • Helper deployment transaction: 0x55a4858c663267dd5e2ed6a5987873e6d5e22cb86955735bbbd9e0a09fe97508
  • Jump Farm Staking: 0x05999eb831ae28ca920ce645a5164fbdb1d74fe9
  • Jump Farm Distributor: 0x1e5f868a297fa80e6a03df69bc3e21c8145fcbb4
  • Jump Farm Treasury: 0xab01b9419ed5b82c9342886206eacc5059268cb3
  • Attacker EOA: 0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6
  • Attacker helper: 0x154863eb71de4a34f88ea57450840eab1c71aba6
  • Evidence set: exploit metadata, exploit trace, balance diff, verified victim contract source, and helper creation artifact under /workspace/session/artifacts/collector/