Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bent Beige Dachshund - Some users may not be able to use some multi pods #557

Open
sherlock-admin4 opened this issue Feb 17, 2025 · 0 comments

Comments

@sherlock-admin4
Copy link

Bent Beige Dachshund

High

Some users may not be able to use some multi pods

Summary

Some multi asset pods cannot be used by some users as the required deposit amounts are of extremely disproportionate values thus preventing (small) users from using the POD

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

Root Cause

The problem is that certain pods will not allow users to bond asset in them due to dispropotionated value of assets needed to do the bonding as described in details below

Cosnsider a pod created with 3 asset and respective weights as shown below

  • indexTokens[0].token = DAI, indexTokens[0].weighting = 40
  • indexTokens[1].token = ETH, indexTokens[1].weighting = 20
  • indexTokens[2].token = USDC, indexTokens[2].weighting = 40
File: WeightedIndex.sol
47:     function __WeightedIndex_init(
48:         Config memory _config,
49:         address[] memory _tokens,
50:         uint256[] memory _weights,
51:         bytes memory _immutables
52:     ) internal {

/////SNIP
81:         // at idx == 0, need to find X in [1/X = tokenWeightAtIdx/totalWeights]
82:         // at idx > 0, need to find Y in (Y/X = tokenWeightAtIdx/totalWeights)
83:  @>     uint256 _xX96 = (FixedPoint96.Q96 * _totalWeights) / _weights[0];
84:         for (uint256 _i; _i < _tl; _i++) { // amountsPerIdxTokenX96
85:   @>        indexTokens[_i].q1 = (_weights[_i] * _xX96 * 10 ** IERC20Metadata(_tokens[_i]).decimals()) / _totalWeights;
86:         }
87:     }

From the above, _xX96 is evaluted as

83:         uint256 _xX96 = (FixedPoint96.Q96 * _totalWeights) / _weights[0];
83:         _xX96 = (FixedPoint96.Q96 * 100) / 40;
83:         _xX96 = 2.5 * Q96 

indexTokens[0].q1 is evaluated as

85:             indexTokens[0].q1 = (_weights[0] * _xX96 * 10 ** IERC20Metadata(_tokens[0]).decimals()) / _totalWeights;
85:             indexTokens[0].q1 = 40 * (2.5*FixedPoint96.Q96) * (10**18) / 100
85:             indexTokens[0].q1 = Q96 * (10**18)

indexTokens[1].q1 is evaluated as

85:             indexTokens[1].q1 = (_weights[1] * _xX96 * 10 ** IERC20Metadata(_tokens[1]).decimals()) / _totalWeights;
85:             indexTokens[1].q1 = 20 * (2.5*FixedPoint96.Q96) * (10**18) / 100
85:             indexTokens[1].q1 = 0.5 *Q96 * (10**18)

indexTokens[2].q1 is evaluated as

85:             indexTokens[2].q1 = (_weights[2] * _xX96 * 10 ** IERC20Metadata(_tokens[2]).decimals()) / _totalWeights;
85:             indexTokens[2].q1 = 40 * (2.5*FixedPoint96.Q96) * (10**6) / 100
85:             indexTokens[2].q1 = Q96 * (10**6)

Assuming totalSupply == 0 (i.e) _firstIn and bond() is called with

  • _token = DAI
  • _amount = 1000e18
  • _tokenAmtSupplyRatioX96. = FixedPoint96.Q96
File: WeightedIndex.sol
135:     function bond(address _token, uint256 _amount, uint256 _amountMintMin) external override lock noSwapOrFee {
136: @>      _bond(_token, _amount, _amountMintMin, _msgSender());
137:     }


139:     function _bond(address _token, uint256 _amount, uint256 _amountMintMin, address _user) internal {
140:         require(_isTokenInIndex[_token], "IT");
141:         uint256 _tokenIdx = _fundTokenIdx[_token];
142: 
143:         bool _firstIn = _isFirstIn(); // is totalSupply == 0
144:  @>     uint256 _tokenAmtSupplyRatioX96 =
145:   @>        _firstIn ? FixedPoint96.Q96 : (_amount * FixedPoint96.Q96) / _totalAssets[_token];
146:         uint256 _tokensMinted;

/////SNIP
161:         for (uint256 _i; _i < _il; _i++) {
162: @>          uint256 _transferAmt = _firstIn
163:  @>             ? getInitialAmount(_token, _amount, indexTokens[_i].token)
164:                 : (_totalAssets[indexTokens[_i].token] * _tokenAmtSupplyRatioX96) / FixedPoint96.Q96;
165:             require(_transferAmt > 0, "T0");
166:             _totalAssets[indexTokens[_i].token] += _transferAmt;
167:             _transferFromAndValidate(IERC20(indexTokens[_i].token), _user, _transferAmt);
168:         }
169:         _internalBond();
170:         emit Bond(_user, _token, _amount, _tokensMinted);
171:     }

The loop on L161 is used to calculate the amount of each indexTokens that the depositor needs to deposit

File: WeightedIndex.sol
204:     function getInitialAmount(address _sourceToken, uint256 _sourceAmount, address _targetToken)
205:         public
206:         view
207:         override
208:         returns (uint256)
209:     {
210:   @>    uint256 _sourceTokenIdx = _fundTokenIdx[_sourceToken];
211:    @>   uint256 _targetTokenIdx = _fundTokenIdx[_targetToken];
212:    @>   return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
213:             / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
214:     }

_sourceTokenIdx = DAI and _sourceAmount = 1000e18,

  • For i = 0
    • indexTokens[_targetTokenIdx].weighting = 40
    • indexTokens[_sourceTokenIdx].weighting = 40
    • _targetToken = DAI
212:    @>   return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
213:             / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
212:            1000e18 * 40 * (10 ** 18) / 40 / 10 ** 18
212:            1000e18 // 1000 DAI is required from the bonder
  • For i = 1
    • indexTokens[_targetTokenIdx].weighting = 20
    • indexTokens[_sourceTokenIdx].weighting = 40
    • _targetToken = ETH
212:    @>   return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
213:             / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
212:            1000e18 * 20 * (10 ** 18) / 40 / 10 ** 18
212:            500e18 // 500 ETH is required form the bonder
  • For i = 2
    • indexTokens[_targetTokenIdx].weighting = 40
    • indexTokens[_sourceTokenIdx].weighting = 40
    • _targetToken = USDC
212:    @>   return (_sourceAmount * indexTokens[_targetTokenIdx].weighting * 10 ** IERC20Metadata(_targetToken).decimals())
213:             / indexTokens[_sourceTokenIdx].weighting / 10 ** IERC20Metadata(_sourceToken).decimals();
212:            1000e18 * 40 * (10 ** 6) / 40 / 10 ** 18
212:            1000e6 // 1000 USDC is required form the bonder

As you can see from the calculations above, notice that for minting about1000e18 pTKNs the user needed to provide

  • 1000 DAI (1000 Dollars)
  • 500 ETH (1.4million Dollars @2800 per ETH)
  • 1000 USDC (1000 Dollars)

If you increase these numbers by a factor of 10 say 10K DAI, the amount of ETH become 14million too and so on.

This can render the POD useless except for well funded users if there exist susch that are wiling to take execute such bonds.

Internal Pre-conditions

NIL

External Pre-conditions

NIL

Attack Path

Cosnsider a pod created with 3 asset and respective weights as shown below

  • indexTokens[0].token = DAI, indexTokens[0].weighting = 40
  • indexTokens[1].token = ETH, indexTokens[1].weighting = 20
  • indexTokens[2].token = USDC, indexTokens[2].weighting = 40

A user trying to bond asset and wrap into the pod token will be required to be well funded in the asset with the least weight as described in detail in the Root Cause section.

Impact

This can lead to a DOS for some users as they will not be able to use certain PODs

PoC

Check Root Cause section

Mitigation

A trivial solution cannot be recommended for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant