From 20bb66aed5dc58c612ee6478b6698951f0a5d4e8 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 29 Aug 2024 11:54:54 +0200 Subject: [PATCH 1/2] Add check for oscillation in calcLpTokenSupply. --- src/functions/Stable2.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 05d2fa5..4f7ac98 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -86,6 +86,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; lpTokenSupply = sumReserves; for (uint256 i = 0; i < 255; i++) { + bool stableOscillation; uint256 dP = lpTokenSupply; // If division by 0, this will be borked: only withdrawal will work. And that is good dP = dP * lpTokenSupply / (scaledReserves[0] * N); @@ -93,10 +94,25 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 prevReserves = lpTokenSupply; lpTokenSupply = (Ann * sumReserves / A_PRECISION + (dP * N)) * lpTokenSupply / (((Ann - A_PRECISION) * lpTokenSupply / A_PRECISION) + ((N + 1) * dP)); + // Equality with the precision of 1 + // If the difference between the current lpTokenSupply and the previous lpTokenSupply is 2, + // Check that the oscillation is stable, and if so, return the average between the two. if (lpTokenSupply > prevReserves) { + if (lpTokenSupply - prevReserves == 2) { + if (stableOscillation) { + return lpTokenSupply - 1; + } + stableOscillation = true; + } if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; } else { + if (prevReserves - lpTokenSupply == 2) { + if (stableOscillation) { + return lpTokenSupply + 1; + } + stableOscillation = true; + } if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; } } From ff021fed640021ef10972035b7991b2328ce132e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 29 Aug 2024 12:57:50 +0200 Subject: [PATCH 2/2] change absolute diff to relative diff, increase price precision. --- src/functions/Stable2.sol | 21 ++++++++++++++++--- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 2 +- ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 10 ++++----- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 4f7ac98..430683a 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -42,7 +42,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // price threshold. more accurate pricing requires a lower threshold, // at the cost of higher execution costs. - uint256 constant PRICE_THRESHOLD = 100; // 0.01% + uint256 constant PRICE_THRESHOLD = 10; // 0.001% address immutable lookupTable; uint256 immutable a; @@ -213,7 +213,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 parityReserve = lpTokenSupply / 2; // update `scaledReserves` based on whether targetPrice is closer to low or high price: - if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + if (percentDiff(pd.lutData.highPrice, pd.targetPrice) > percentDiff(pd.lutData.lowPrice, pd.targetPrice)) { // targetPrice is closer to lowPrice. scaledReserves[i] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; scaledReserves[j] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; @@ -297,7 +297,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // update scaledReserve[j] such that calcRate(scaledReserves, i, j) = low/high Price, // depending on which is closer to targetPrice. - if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + if (percentDiff(pd.lutData.highPrice, pd.targetPrice) > percentDiff(pd.lutData.lowPrice, pd.targetPrice)) { // targetPrice is closer to lowPrice. scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; @@ -444,4 +444,19 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { + pd.maxStepSize * (pd.currentPrice - pd.targetPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); } } + + /** + * @notice Calculate the percentage difference between two numbers. + * @return The percentage difference as a fixed-point number with 18 decimals. + * @dev This function calculates the absolute percentage difference: + * |(a - b)| / ((a + b) / 2) * 100 + * The result is scaled by 1e18 for precision. + */ + function percentDiff(uint256 _a, uint256 _b) internal pure returns (uint256) { + if (_a == _b) return 0; + uint256 difference = _a > _b ? _a - _b : _b - _a; + uint256 average = (_a + _b) / 2; + // Multiply by 100 * 1e18 to get percentage with 18 decimal places + return (difference * 100 * 1e18) / average; + } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index 83241ae..9d0395e 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -61,7 +61,7 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertApproxEqRel(reserve0, 4.575771214546676444e18, 0.0001e18); + assertApproxEqRel(reserve0, 4.576236561359714812e18, 0.0001e18); assertApproxEqRel(reserve1, 0.21852354514449462e18, 0.0001e18); } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 3a491d8..a21d80f 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -30,8 +30,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 100.005058322101089709e18); - assertEq(reserve1, 100.005058322101089709e18); + assertEq(reserve0, 99.999921040536083478e18); + assertEq(reserve1, 99.999921040536083478e18); } function test_calcReserveAtRatioSwap_equal_diff() public view { @@ -45,8 +45,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 73.517644476151580971e18); - assertEq(reserve1, 73.517644476151580971e18); + assertEq(reserve0, 73.513867858788351572e18); + assertEq(reserve1, 73.513867858788351572e18); } function test_calcReserveAtRatioSwap_diff_equal() public view { @@ -61,7 +61,7 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); assertEq(reserve0, 180.644064978044534737e18); // 180.644064978044534737e18, 100e18 - assertEq(reserve1, 39.475055811844664131e18); // 100e18, 39.475055811844664131e18 + assertEq(reserve1, 39.474244037189430513e18); // 100e18, 39.475055811844664131e18 } function test_calcReserveAtRatioSwap_diff_diff() public view {