Skip to content

Latest commit

 

History

History
82 lines (62 loc) · 3.07 KB

File metadata and controls

82 lines (62 loc) · 3.07 KB

Brave Saffron Rooster

Medium

There may be no last debonder.

Summary

There may be no last debonder.

Root Cause

In DecentralizedIndex.sol, the _isLastOut function may always return false, which implies that a last debonder might not exist based on this function's logic.

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

    function _isLastOut(uint256 _debondAmount) internal view returns (bool) {
        return _debondAmount >= (_totalSupply * 99) / 100;
    }

The amount of pTKN held by a user may not reach 99% of _totalSupply.

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

require(__fees.buy <= (uint256(DEN) * 20) / 100);
        require(__fees.sell <= (uint256(DEN) * 20) / 100);
        require(__fees.burn <= (uint256(DEN) * 70) / 100);
        require(__fees.bond <= (uint256(DEN) * 99) / 100);
        require(__fees.debond <= (uint256(DEN) * 99) / 100);
        require(__fees.partner <= (uint256(DEN) * 5) / 100);

When __fees.bond = (uint256(DEN) * 5) / 100) and __fees.burn = (uint256(DEN) * 40) / 100), let's analyze minted tokens and _totalSupply of a user.

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

function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
        [...]
        if (_firstIn) {
@>        _tokensMinted = (_amount * FixedPoint96.Q96 * 10 ** decimals()) / indexTokens[_tokenIdx].q1;
        } else {
            _tokensMinted = (_totalSupply * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
        }
@>  uint256 _feeTokens = _canWrapFeeFree(_user) ? 0 : (_tokensMinted * _fees.bond) / DEN;
        require(_tokensMinted - _feeTokens >= _amountMintMin, "M");
@>    _totalSupply += _tokensMinted;
        _mint(_user, _tokensMinted - _feeTokens);
        if (_feeTokens > 0) {
            _mint(address(this), _feeTokens);
@>      _processBurnFee(_feeTokens);
        }
        [...]
    }

In this context, _totalSupply can be expressed as tokensMinted - tokensMinted * _fees.bond / DEN * _fees.burn / DEN, and the user's minted tokens are _tokensMinted * (1 - _fees.bond / DEN). Thus, we can derive that: tokensMinted*(1-_fees.bond/ DEN)/_totalSupply = (1-_fees.bond/ DEN) / (1-_fees.bond/DEN*_fees.burn/ DEN )=(1-0.05)/(1-0.05*0.4)=0.95/0.98<0.99. Given the values of _fees.bond and _fees.burn, it is plausible that there may be no last debonder. Additionally, in this scenario, determining the last debonder based solely on the unwrap amount is not accurate.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

The function _isLastOut may always return false and does not decide the last debonder. So the fee may be applied to the real last debonder.

PoC

No response

Mitigation

Please adjust fee parameters or update the algorithm to determining the last debonder.