code-423n4 / 2024-07-basin-validation

0 stars 0 forks source link

Stale `lp token supply` used causes wrong current price calculations #104

Open c4-bot-2 opened 3 months ago

c4-bot-2 commented 3 months ago

Lines of code

https://github.com/code-423n4/2024-07-basin/blob/7d5aacbb144d0ba0bc358dfde6e0cc913d25310e/src/functions/Stable2.sol#L220-L236

Vulnerability details

Impact

Wrong calculation of currentPrice during swaps .

Details

Stable2.calcReserveAtRatioSwap function performs the calculation of reserve using newtons method . Initially the function updates the lp token supply for the given reserves at : https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L193C9-L193C87

uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18));

But further down, in the loop of the same function , when the reserves are updated , the corresponding lp token supply is not updated and the internal _calcRate function uses stale lp token supply for the calculation of current price as shown here : https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L221C2-L227C1

           scaledReserves[j] = updateReserve(pd, scaledReserves[j]);

            // calculate scaledReserve[i]:
            scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18));
            // calc currentPrice:
            pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply); <@here

Similar calculation can be seen in calcReserveAtRatioLiquidity function , where it uses the public calcRate function instead of the internal one & thereby updating lp token supply internally before calculating currentPrice as seen here : https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L288C9-L291C82

for (uint256 k; k < 255; k++) {
            scaledReserves[j] = updateReserve(pd, scaledReserves[j]);
            // calculate new price from reserves:
            pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18));

Also the sponsor mentioned in the pinned discord message that The logic used to calculate the ratios used can be found in StableswapCalcRatiosLiqSim.s.sol and StableswapCalcRatiosSwapSim.s.sol.

And looking into StableswapCalcRatiosSwapSim.s.sol., we see the logic used for Stable2.calcReserveAtRatioSwap is -

         // for n times (1...n) :
        // 1) increment x_n-1 by some amount to get x_n
        // 2) calc y_n using calcReserves(...)
        // 3) calc price_n using calcRate(...)

        // csv header
        console.log("Price (P),Reserve (x),Reserve (y)");

        for (uint256 i; i < 20; i++) {
            // update reserve x
            reserve_x = reserve_x * 92 / 100;
            reserves[0] = reserve_x;
            // get y_n --> corresponding reserve y for a given liquidity level
            uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data);
            // update reserve y
            reserves[1] = reserve_y;
            // mark price
            price = stable2.calcRate(reserves, 0, 1, data);      <@audit-here
            console.log("%d,%d,%d", price, reserve_x, reserve_y);
        }

The same logic can be seen inside the loop of the calcReserveAtRatioSwap function too, except the usage of internal _calcRate instead of the public calcRate which does not updates lptokenSupply for each iteration for each change in reserves internally.

This causes difference in the pd.currentPrice value calculated , thereby instantiating problems for the contracts integrating Stable2.calcReserveAtRatioSwap.

Tools Used

Manual Review

Recommended Mitigation Steps

Update the code inside the loop of the function Stable2.calcReserveAtRatioSwap to this -

for (uint256 k; k < 255; k++) {
            scaledReserves[j] = updateReserve(pd, scaledReserves[j]);

            // calculate scaledReserve[i]:
            scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18));
            // calc currentPrice:
            pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18));

Assessed type

Context