Skip to content

Latest commit

 

History

History
107 lines (77 loc) · 3.26 KB

File metadata and controls

107 lines (77 loc) · 3.26 KB

Fancy Rusty Camel

High

The TokenRewards::depositRewards is vulnerable to sandwich attack via staking and unstaking leading to unfair rewards distribution

Summary

The StakingPool::stake function allows to users to stake their LP tokens and StakingPool::unstake allows them to unstake. However, this functionality is vulnerable to a malicious user staking just before rewards are deposited via TokenRewards::depositRewards and unstaking right after, effectively claiming a part of the deposited rewards and stealing rewards from long-term and honest stakers.

Root Cause

The protocol distributes rewards based on current staking positions without any time-based vesting or lockup period. This allows users to temporarily stake large amounts to capture disproportionate rewards. Staking: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L67 Depositing Rewards: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/TokenRewards.sol#L180 Unstaking: https://github.com/sherlock-audit/2025-01-peapods-finance/blob/d28eb19f4b39d3db7997477460f9f9c76839cb0c/contracts/contracts/StakingPoolToken.sol#L77

Internal Pre-conditions

  • StakingPoolToken must have existing stakers
  • TokenRewards contract must be ready to distribute rewards
  • No minimum staking period implemented

External Pre-conditions

  • Ability to monitor mempool for incoming reward distributions
  • Sufficient capital to execute large stakes

Attack Path

  1. Monitor mempool for TokenRewards.depositRewards transactions
  2. Front-run with large stake:
vm.startPrank(attacker);
stakingToken.approve(address(stakingPool), largeAmount);
stakingPool.stake(attacker, largeAmount);
  1. Reward distribution occurs:
tokenRewards.depositRewards(rewardToken, rewardAmount);
  1. Back-run with unstake:
stakingPool.unstake(largeAmount);
tokenRewards.claimReward(attacker);
vm.stopPrank();

Impact

  1. Honest long-term stakers lose significant portions of rewards
  2. Reduced incentive for legitimate long-term staking
  3. Protocol's reward distribution mechanism becomes ineffective

PoC

Initial State:

// Honest Staker (staking for months)
Staked Amount: 100,000 LP tokens
Total Staked in Protocol: 100,000 LP tokens

// Weekly Reward
50,000 PEAS tokens scheduled for distribution
  1. Attacker monitors mempool and sees reward distribution transaction:
tokenRewards.depositRewards(PEAS, 50_000e18)
  1. Front-run: Attacker stakes 1,000,000 LP tokens
New Total Staked = 1,100,000 LP tokens
Attacker's Share = ~91% (1_000_000/1_100_000)
Honest Staker's Share = ~9% (100_000/1_100_000)
  1. Reward Distribution
50,000 PEAS distributed according to shares:
- Attacker receives: 45,454 PEAS (91%)
- Honest staker receives: 4,546 PEAS (9%)
  1. Back-run: Attacker unstakes 1,000,000 LP tokens
- Claims 45,454 PEAS
- Honest stakers who staked for months gets only 4,546 PEAS

Mitigation

Here are a few mitigation options:

  1. Implement minimum staking period lock
  2. Add vesting period for rewards
  3. Calculate rewards based on time-weighted position size