Skip to content

Latest commit

 

History

History
89 lines (57 loc) · 4.09 KB

File metadata and controls

89 lines (57 loc) · 4.09 KB

Acrobatic Violet Poodle

Medium

Attackers can sandwich first swap whenever _twoHops is == true in Zapper._swapV2()

Summary

In Zapper._swapV2(), whenever _twoHops is == true Attackers can use bots to sandwich first swap in networks where sandwich attacks are possible. This will cause losses for users as amountOutMin is hardcoded as 0 whenever _twoHops is == true.

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

Root Cause

 bool _twoHops = _path.length == 3;
        if (maxSwap[_path[0]] > 0 && _amountIn > maxSwap[_path[0]]) {
            _amountOutMin = (_amountOutMin * maxSwap[_path[0]]) / _amountIn;
            _amountIn = maxSwap[_path[0]];
        }
        IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
        _amountOut =
            DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));//@audit-issue frontrunning and backrunning to profit.

In Zapper._swapV2(), whenever _twoHops is == true Attackers can use bots to sandwich first swap in networks where sandwich attacks are possible. This will cause losses for users as amountOutMin is hardcoded as 0 whenever _twoHops is == true.

According to what protocol states in the contest readme,

Please discuss any design choices you made.

The main consideration for a design choice we made is in a few places we implement unlimited (100%) slippage for dex swaps. Our expectation is wherever we implement this behavior that almost any swap from token0 to token1 will be of small enough value that it would rarely, if ever, be profitable to sandwich for profit by a bot.

They hardcoded amountOutMin as 0 in places where they thought that the amount being swapped is too small to be profitable for sandwich attacks.

But considering that Zapper._swapV2() is used by IndexUtils.addLPAndStake() it is very unlikely that the amount being swapped is going to be small to the extent where the sandwich is unprofitable for the bot.

Attackers can use bots to sniff out and sandwich the first swap that will have amountOutMin as 0 whenever _twoHops is true.

The tx flow is IndexUtils.addLPAndStake() -> Zapper.zap() -> Zapper._swapV2()

Internal Pre-conditions

AmountOutMIn is 0 during swap

External Pre-conditions

  1. _twoHops is true.
  2. amount enough to make the sandwich a profitable one is being swapped.
  3. The tx is happening in a chain where sandwich attacks are possible.

Attack Path

  1. IndexUtils.addLPAndStake() is called.

  2. in the process of IndexUtils.addLPAndStake(), Zapper._swapV2() is called

  3. Here in this line DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this)); the swap occurs with amountOutMin as 0 and attacker's bot sniffs it out and frontruns and backruns it profitting from the sandwich attack.

Impact

Medium Severity. Attackers can sandwich first swap whenever _twoHops is == true, this will cause loses to users whenever they use IndexUtils.addLPAndStake()

    function _swapV2(address[] memory _path, uint256 _amountIn, uint256 _amountOutMin) internal returns (uint256) {
        bool _twoHops = _path.length == 3;
        address _out = _twoHops ? _path[2] : _path[1];
        uint256 _outBefore = IERC20(_out).balanceOf(address(this));
        IERC20(_path[0]).safeIncreaseAllowance(address(DEX_ADAPTER), _amountIn);
        DEX_ADAPTER.swapV2Single(_path[0], _path[1], _amountIn, _twoHops ? 0 : _amountOutMin, address(this));//@audit-issue frontrunning and backrunning this first swap to profit.
        if (_twoHops) {
            uint256 _intermediateBal = IERC20(_path[1]).balanceOf(address(this));
            IERC20(_path[1]).safeIncreaseAllowance(address(DEX_ADAPTER), _intermediateBal);
            DEX_ADAPTER.swapV2Single(_path[1], _path[2], _intermediateBal, _amountOutMin, address(this));
        }
        return IERC20(_out).balanceOf(address(this)) - _outBefore;
    }

PoC

No response

Mitigation

Use specified _amountOutMin even when _twoHops is == true