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));
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-L193C87But further down, in the loop of the same function , when the reserves are updated , the corresponding
lp token supply
is not updated and theinternal _calcRate
function uses stalelp 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-L227C1Similar calculation can be seen in
calcReserveAtRatioLiquidity
function , where it uses thepublic calcRate
function instead of the internal one & thereby updatinglp token supply
internally before calculatingcurrentPrice
as seen here : https://github.com/code-423n4/2024-07-basin/blob/main/src/functions/Stable2.sol#L288C9-L291C82Also 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 forStable2.calcReserveAtRatioSwap
is -The same logic can be seen inside the loop of the
calcReserveAtRatioSwap
function too, except the usage ofinternal _calcRate
instead of thepublic calcRate
which does not updateslptokenSupply
for each iteration for each change in reserves internally.This causes difference in the
pd.currentPrice
value calculated , thereby instantiating problems for the contracts integratingStable2.calcReserveAtRatioSwap
.Tools Used
Manual Review
Recommended Mitigation Steps
Update the code inside the loop of the function
Stable2.calcReserveAtRatioSwap
to this -Assessed type
Context