From b9f04bfaf42981275cef61dc13cd2f1d2240e9ff Mon Sep 17 00:00:00 2001 From: "A.L" Date: Sun, 17 Mar 2024 23:19:31 +0800 Subject: [PATCH] fix: underflow problem for price reduction case --- script/optimized-deployer-meta | 8 ++++---- src/ReservoirDeployer.sol | 8 ++++---- src/ReservoirPair.sol | 14 ++++++++++++-- test/unit/OracleWriter.t.sol | 21 +++++++++------------ 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/script/optimized-deployer-meta b/script/optimized-deployer-meta index f951129d..f842b507 100644 --- a/script/optimized-deployer-meta +++ b/script/optimized-deployer-meta @@ -1,6 +1,6 @@ { - "constant_product_hash": "0x7b0f16c5cf2f4e78c4f697bfefb1365008f8865aaaa942f46ced4e0dea45c42a", - "factory_hash": "0xab6a26d7fb7b6df7e81d9f9354f8b767b5ac11b732f1b3f2e9d6e5b69a7d0754", - "oracle_caller_hash": "0x0d71d4a05b676bfbccd00e8422375c347a056fb93cc502cc962d5e6912213b04", - "stable_hash": "0xc5b9d3031f577510c547b9465241780111ad9d25a80dbe2292d1dc4290d9cd5a" + "constant_product_hash": "0x6bb54a3a2e0eef36a4ee1866eee21dc514fac57496141ce405f9af4e37f34941", + "factory_hash": "0x5d635090575f999ee44232af44309f6053dc93b7844bfdd90ee7a7919a4d363c", + "oracle_caller_hash": "0x529da7083c2ffd40e327dd40a4ae6f796de1561a0191b323d0cbfab827fab3b7", + "stable_hash": "0x51e93a48df3616a3e56c131684d126d7d3ddcd8911d5b807a773aca3a576439e" } \ No newline at end of file diff --git a/src/ReservoirDeployer.sol b/src/ReservoirDeployer.sol index 505698ee..644628c3 100644 --- a/src/ReservoirDeployer.sol +++ b/src/ReservoirDeployer.sol @@ -17,12 +17,12 @@ contract ReservoirDeployer { uint256 public step = 0; // Bytecode hashes. - bytes32 public constant FACTORY_HASH = bytes32(0xab6a26d7fb7b6df7e81d9f9354f8b767b5ac11b732f1b3f2e9d6e5b69a7d0754); + bytes32 public constant FACTORY_HASH = bytes32(0x5d635090575f999ee44232af44309f6053dc93b7844bfdd90ee7a7919a4d363c); bytes32 public constant CONSTANT_PRODUCT_HASH = - bytes32(0x7b0f16c5cf2f4e78c4f697bfefb1365008f8865aaaa942f46ced4e0dea45c42a); - bytes32 public constant STABLE_HASH = bytes32(0xc5b9d3031f577510c547b9465241780111ad9d25a80dbe2292d1dc4290d9cd5a); + bytes32(0x6bb54a3a2e0eef36a4ee1866eee21dc514fac57496141ce405f9af4e37f34941); + bytes32 public constant STABLE_HASH = bytes32(0x51e93a48df3616a3e56c131684d126d7d3ddcd8911d5b807a773aca3a576439e); bytes32 public constant ORACLE_CALLER_HASH = - bytes32(0x0d71d4a05b676bfbccd00e8422375c347a056fb93cc502cc962d5e6912213b04); + bytes32(0x529da7083c2ffd40e327dd40a4ae6f796de1561a0191b323d0cbfab827fab3b7); // Deployment addresses. GenericFactory public factory; diff --git a/src/ReservoirPair.sol b/src/ReservoirPair.sol index 0689e862..30c3b9ce 100644 --- a/src/ReservoirPair.sol +++ b/src/ReservoirPair.sol @@ -518,6 +518,8 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 { // 100 basis points per second which is 60% per minute uint256 internal constant MAX_CHANGE_PER_SEC = 0.01e18; + // 10% + uint256 internal constant MAX_CHANGE_PER_TRADE = 0.1e18; string internal constant MAX_CHANGE_RATE_NAME = "Shared::maxChangeRate"; string internal constant MAX_CHANGE_PER_TRADE_NAME = "Shared::maxChangePerTrade"; string internal constant ORACLE_CALLER_NAME = "Shared::oracleCaller"; @@ -547,6 +549,7 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 { function setClampParams(uint128 aMaxChangeRate, uint128 aMaxChangePerTrade) public onlyFactory { require(0 < aMaxChangeRate && aMaxChangeRate <= MAX_CHANGE_PER_SEC, "RP: INVALID_CHANGE_PER_SECOND"); + require(0 < aMaxChangePerTrade && aMaxChangePerTrade <= MAX_CHANGE_PER_TRADE, "RP: INVALID_CHANGE_PER_TRADE"); emit ClampParamsUpdated(aMaxChangeRate, aMaxChangePerTrade); maxChangeRate = aMaxChangeRate; @@ -582,8 +585,15 @@ abstract contract ReservoirPair is IAssetManagedPair, ReservoirERC20 { rClampedPrice = lRateLimitedPrice.min(lPerTradeLimitedPrice); assert(rClampedPrice < aCurrRawPrice); } else { - uint256 lRateLimitedPrice = aPrevClampedPrice.fullMulDiv(1e18 - maxChangeRate * aTimeElapsed, 1e18); - uint256 lPerTradeLimitedPrice = aPrevClampedPrice.fullMulDiv(1e18 - maxChangePerTrade , 1e18); + // make sure that the time elapsed is not too long, else the subtraction from 1e18 will underflow + // if the time elapsed is too long, then we only depend on the per trade limited price + uint256 lChangeElapsed = maxChangeRate * aTimeElapsed; + if (lChangeElapsed > 1e18) { + lChangeElapsed = 1e18; + } + uint256 lRateLimitedPrice = aPrevClampedPrice.fullMulDiv(1e18 - lChangeElapsed, 1e18); + // subtraction will not underflow as maxChangePerTrade is limited by MAX_CHANGE_PER_TRADE + uint256 lPerTradeLimitedPrice = aPrevClampedPrice.fullMulDiv(1e18 - maxChangePerTrade, 1e18); rClampedPrice = lRateLimitedPrice.max(lPerTradeLimitedPrice); assert(rClampedPrice > aCurrRawPrice); } diff --git a/test/unit/OracleWriter.t.sol b/test/unit/OracleWriter.t.sol index 11d87e52..6b03e0ca 100644 --- a/test/unit/OracleWriter.t.sol +++ b/test/unit/OracleWriter.t.sol @@ -414,28 +414,25 @@ contract OracleWriterTest is BaseTest { } function testOracle_OverflowAccPrice(uint32 aNewStartTime) public randomizeStartTime(aNewStartTime) allPairs { - // assume - vm.assume(aNewStartTime >= 1); - // arrange - make the last observation close to overflowing - (,,, uint16 lIndex) = _stablePair.getReserves(); + (,,, uint16 lIndex) = _pair.getReserves(); + _writeObservation( - _stablePair, lIndex, 1e3, 1e3, type(int88).max, type(int88).max, uint32(block.timestamp % 2 ** 31) + _pair, lIndex, 1e3, 1e3, type(int88).max, type(int88).max, uint32(block.timestamp % 2 ** 31) ); - Observation memory lPrevObs = _oracleCaller.observation(_stablePair, lIndex); + Observation memory lPrevObs = _oracleCaller.observation(_pair, lIndex); // act uint256 lAmountToSwap = 5e18; - _tokenB.mint(address(_stablePair), lAmountToSwap); - _stablePair.swap(-int256(lAmountToSwap), true, address(this), ""); + _tokenB.mint(address(_pair), lAmountToSwap); + _pair.swap(-int256(lAmountToSwap), true, address(this), ""); _stepTime(5); - _stablePair.sync(); + _pair.sync(); // assert - when it overflows it goes from a very positive number to a very negative number - (,,, lIndex) = _stablePair.getReserves(); - Observation memory lCurrObs = _oracleCaller.observation(_stablePair, lIndex); + (,,, lIndex) = _pair.getReserves(); + Observation memory lCurrObs = _oracleCaller.observation(_pair, lIndex); assertLt(lCurrObs.logAccRawPrice, lPrevObs.logAccRawPrice); } - }