Skip to content

Latest commit

 

History

History
104 lines (72 loc) · 3.7 KB

File metadata and controls

104 lines (72 loc) · 3.7 KB

Basic Lilac Marmot

High

Due to the lack of amountOutMin setting, the attacker can steal tokens.

Summary

Summary

function claimReward(address _wallet) external override {
  _processFeesIfApplicable();
  _distributeReward(_wallet);
  emit ClaimReward(_wallet);
}
function _processFeesIfApplicable() internal {
  IDecentralizedIndex(INDEX_FUND).processPreSwapFeesAndSwap();
}

Anyone can call the processPreSwapFeesAndSwap function of INDEX_FUND through the claimReward function.

/// @notice The ```processPreSwapFeesAndSwap``` function allows the rewards CA for the pod to process fees as needed
function processPreSwapFeesAndSwap() external override lock {
  require(_msgSender() == IStakingPoolToken(lpStakingPool).POOL_REWARDS(), "R");
  _processPreSwapFeesAndSwap();
}
/// @notice The ```_feeSwap``` function processes built up fees by converting to pairedLpToken
/// @param _amount Number of pTKN being processed for yield
function _feeSwap(uint256 _amount) internal {
  _approve(address(this), address(DEX_HANDLER), _amount);
  address _rewards = IStakingPoolToken(lpStakingPool).POOL_REWARDS();
  uint256 _pairedLpBalBefore = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards);
  **DEX_HANDLER.swapV2Single(address(this), PAIRED_LP_TOKEN, _amount, 0, _rewards);**

  if (PAIRED_LP_TOKEN == lpRewardsToken) {
      uint256 _newPairedLpTkns = IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) - _pairedLpBalBefore;
      if (_newPairedLpTkns > 0) {
          ITokenRewards(_rewards).depositRewardsNoTransfer(PAIRED_LP_TOKEN, _newPairedLpTkns);
      }
  } else if (IERC20(PAIRED_LP_TOKEN).balanceOf(_rewards) > 0) {
      ITokenRewards(_rewards).depositFromPairedLpToken(0);
  }
}

When the processPreSwapFeesAndSwap function is executed, it triggers the swapV2Single function within the _feeSwap function of DEX_HANDLER. This function likely calls the Uniswap V2 swap function to perform a token swap.

function swapV2Single(
  address _tokenIn,
  address _tokenOut,
  uint256 _amountIn,
  uint256 _amountOutMin,
  address _recipient
) external

The fourth parameter of the swapV2Single function is used to validate the minimum amount of tokens expected after the swap. However, in the _feeSwap function, this parameter is being passed as 0, which effectively means there is no minimum amount check.

Through this, a malicious user could exploit the lack of a minimum amount check (by passing 0 as the fourth parameter) to steal fees.

Root Cause

in DecentralizedIndex.sol:232 amountOutMin is set to zero.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

  1. The attacker calculates the swap amount of the DecentralizedIndex contract and adjusts the liquidity of the POOL.
  2. By calling the claimReward function, the attacker causes the DecentralizedIndex contract to swap, resulting in a loss of _rewards worth of tokens and returning tokens close to zero.

Impact

The affected party continuously loses tokens equivalent to _rewards.

PoC

No response

Mitigation

Even if the claimReward function is restricted to authorized users, a malicious actor could still exploit the system via front-running to steal fees.

To prevent this, it is essential to validate the minimum amount during the swap. This would ensure that the swap cannot proceed unless the expected minimum amount of tokens is received, preventing a malicious user from benefiting from an unfavorable swap rate.

References

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/TokenRewards.sol#L137-L139

https://github.com/sherlock-audit/2025-01-peapods-finance/blob/main/contracts/contracts/DecentralizedIndex.sol#L232