From 39d094bdefea31f78365f88cc9c8561c21070131 Mon Sep 17 00:00:00 2001 From: "A.L." Date: Wed, 11 Dec 2024 17:23:49 +0800 Subject: [PATCH] test: stable oracle math * test: add cases for spot price calculation * fix: safeguards for not reverting * test: use estimates * lint: fix issues * ci: update hash * gas: update snapshot * ci: update hash * ci: update hashes * gas: update snapshot --- .gas-snapshot | 199 +++++++++++---------- package.json | 2 +- script/optimized-deployer-meta | 2 +- script/unoptimized-deployer-meta | 2 +- src/ReservoirDeployer.sol | 2 +- src/libraries/StableOracleMath.sol | 19 +- test/__mocks/StableOracleMathCanonical.sol | 46 +++++ test/unit/libraries/StableOracleMath.t.sol | 86 +++++++++ 8 files changed, 250 insertions(+), 108 deletions(-) create mode 100644 test/__mocks/StableOracleMathCanonical.sol create mode 100644 test/unit/libraries/StableOracleMath.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index 57939655..38b0d551 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,28 +1,28 @@ -AssetManagedPairTest:testAdjustManagement(uint256,uint256) (runs: 256, μ: 315400, ~: 315670) -AssetManagedPairTest:testAdjustManagement_AdjustAfterLoss(uint256) (runs: 256, μ: 538969, ~: 543614) -AssetManagedPairTest:testAdjustManagement_DecreaseManagement(uint256,uint256) (runs: 256, μ: 508066, ~: 508841) +AssetManagedPairTest:testAdjustManagement(uint256,uint256) (runs: 256, μ: 315390, ~: 315670) +AssetManagedPairTest:testAdjustManagement_AdjustAfterLoss(uint256) (runs: 256, μ: 539244, ~: 543731) +AssetManagedPairTest:testAdjustManagement_DecreaseManagement(uint256,uint256) (runs: 256, μ: 508060, ~: 508841) AssetManagedPairTest:testAdjustManagement_GreaterThanUint104() (gas: 124232) AssetManagedPairTest:testAdjustManagement_Int256Min() (gas: 124764) -AssetManagedPairTest:testAdjustManagement_KStillHolds(uint256) (runs: 256, μ: 632527, ~: 632195) +AssetManagedPairTest:testAdjustManagement_KStillHolds(uint256) (runs: 256, μ: 632771, ~: 632429) AssetManagedPairTest:testAdjustManagement_Uint104() (gas: 745418) -AssetManagedPairTest:testBurn_AfterAlmostTotalLoss() (gas: 633477) -AssetManagedPairTest:testBurn_AfterLoss(uint256,uint256) (runs: 256, μ: 714141, ~: 712027) -AssetManagedPairTest:testMint_AfterLoss(uint256,uint256) (runs: 256, μ: 614530, ~: 624495) +AssetManagedPairTest:testBurn_AfterAlmostTotalLoss() (gas: 633614) +AssetManagedPairTest:testBurn_AfterLoss(uint256,uint256) (runs: 256, μ: 713733, ~: 706273) +AssetManagedPairTest:testMint_AfterLoss(uint256,uint256) (runs: 256, μ: 614347, ~: 624612) AssetManagedPairTest:testSetManager() (gas: 131642) -AssetManagedPairTest:testSetManager_CannotMigrateWithManaged(uint256,uint256) (runs: 256, μ: 412062, ~: 412332) +AssetManagedPairTest:testSetManager_CannotMigrateWithManaged(uint256,uint256) (runs: 256, μ: 412052, ~: 412332) AssetManagedPairTest:testSkimExcessManaged() (gas: 406472) AssetManagedPairTest:testSkimExcessManaged_InvalidToken() (gas: 68737) AssetManagedPairTest:testSkimExcessManaged_NoExcess() (gas: 308150) -AssetManagedPairTest:testSwap_AfterLoss(uint256) (runs: 256, μ: 811658, ~: 816273) -AssetManagedPairTest:testSync(uint256,uint256,uint256,uint256) (runs: 256, μ: 518737, ~: 530121) -AssetManagedPairTest:testSyncManaged_ConstantProduct(uint256,uint256) (runs: 256, μ: 347969, ~: 355451) -AssetManagedPairTest:testSyncManaged_Stable(uint256,uint256) (runs: 256, μ: 385860, ~: 380008) +AssetManagedPairTest:testSwap_AfterLoss(uint256) (runs: 256, μ: 811797, ~: 816624) +AssetManagedPairTest:testSync(uint256,uint256,uint256,uint256) (runs: 256, μ: 521179, ~: 530275) +AssetManagedPairTest:testSyncManaged_ConstantProduct(uint256,uint256) (runs: 256, μ: 347733, ~: 355451) +AssetManagedPairTest:testSyncManaged_Stable(uint256,uint256) (runs: 256, μ: 386049, ~: 380359) BufferTest:testAdd_IndexGreaterThanBufferSize(uint16,uint16) (runs: 256, μ: 6788, ~: 6755) BufferTest:testNext_AtLimit() (gas: 3343) BufferTest:testNext_GreaterThanBufferSize(uint16) (runs: 256, μ: 7375, ~: 7333) BufferTest:testPrev_AtLimit() (gas: 3321) BufferTest:testSub_IndexGreaterThanBufferSize(uint16,uint16) (runs: 256, μ: 6844, ~: 6811) -ConstantProductMathTest:testCalcLogPrice_ReturnsOneWeiWhenPriceDiffGreaterThan1e18(uint256,uint256) (runs: 256, μ: 10742, ~: 10722) +ConstantProductMathTest:testCalcLogPrice_ReturnsOneWeiWhenPriceDiffGreaterThan1e18(uint256,uint256) (runs: 256, μ: 10724, ~: 10566) ConstantProductMathTest:testConstantProductOracleMath() (gas: 34880) ConstantProductPairGas:testGasBurn() (gas: 93601) ConstantProductPairGas:testGasMint() (gas: 101264) @@ -41,10 +41,10 @@ ConstantProductPairTest:testOracle_ClampedPrice_AtLimit() (gas: 147983) ConstantProductPairTest:testOracle_ClampedPrice_NoDiffWithinLimit() (gas: 148995) ConstantProductPairTest:testOracle_ClampedPrice_OverLimit() (gas: 150131) ConstantProductPairTest:testOracle_CorrectPrice(uint32) (runs: 256, μ: 4777873, ~: 4777873) -ConstantProductPairTest:testOracle_CorrectPriceDiffDecimals(uint32) (runs: 256, μ: 4700062, ~: 4700067) +ConstantProductPairTest:testOracle_CorrectPriceDiffDecimals(uint32) (runs: 256, μ: 4700061, ~: 4700067) ConstantProductPairTest:testOracle_SimplePrices(uint32) (runs: 256, μ: 4743033, ~: 4743033) ConstantProductPairTest:testSwap() (gas: 113322) -ConstantProductPairTest:testSwap_ExactOut(uint256) (runs: 256, μ: 110909, ~: 120249) +ConstantProductPairTest:testSwap_ExactOut(uint256) (runs: 256, μ: 110506, ~: 100456) ConstantProductPairTest:testSwap_ExactOutExceedReserves() (gas: 28772) ConstantProductPairTest:testSwap_ExactOut_NewReservesExceedUint104() (gas: 78855) ConstantProductPairTest:testSwap_ExtremeAmounts() (gas: 4647340) @@ -52,54 +52,54 @@ ConstantProductPairTest:testSwap_MinInt256() (gas: 15306) ConstantProductPairTest:testSwap_Reenter() (gas: 78501) FixedPointMathLibTest:testFullMulDiv(uint256,uint256,uint256) (runs: 256, μ: 9691, ~: 9580) FixedPointMathLibTest:testMulDiv(uint256,uint256,uint256) (runs: 256, μ: 9274, ~: 9312) -FlashSwapTest:testSwap_FlashSwap_ExactIn(uint256) (runs: 256, μ: 281389, ~: 239367) -FlashSwapTest:testSwap_FlashSwap_ExactOut(uint256) (runs: 256, μ: 258912, ~: 240186) -FlashSwapTest:testSwap_FlashSwap_NoPay(uint256) (runs: 256, μ: 175044, ~: 176637) +FlashSwapTest:testSwap_FlashSwap_ExactIn(uint256) (runs: 256, μ: 283135, ~: 240823) +FlashSwapTest:testSwap_FlashSwap_ExactOut(uint256) (runs: 256, μ: 259938, ~: 250754) +FlashSwapTest:testSwap_FlashSwap_NoPay(uint256) (runs: 256, μ: 175029, ~: 176637) GenericFactoryGasTest:testCreateConstantProductPair() (gas: 4411143) GenericFactoryGasTest:testCreateFactory() (gas: 847078) -GenericFactoryGasTest:testCreateStablePair() (gas: 5212488) +GenericFactoryGasTest:testCreateStablePair() (gas: 5222532) GenericFactoryTest:testAddCurve() (gas: 119232) GenericFactoryTest:testAddCurve_OnlyOwner() (gas: 14164) -GenericFactoryTest:testAllPairs() (gas: 9609681) -GenericFactoryTest:testCreatePair_AllCurves(uint256) (runs: 256, μ: 4870305, ~: 5220834) -GenericFactoryTest:testCreatePair_Create2AddressCorrect() (gas: 9680198) +GenericFactoryTest:testAllPairs() (gas: 9619725) +GenericFactoryTest:testCreatePair_AllCurves(uint256) (runs: 256, μ: 4853773, ~: 5230878) +GenericFactoryTest:testCreatePair_Create2AddressCorrect() (gas: 9690377) GenericFactoryTest:testCreatePair_CurveDoesNotExist(uint256) (runs: 256, μ: 21248, ~: 21244) -GenericFactoryTest:testCreatePair_IdenticalAddress(uint256) (runs: 256, μ: 14762, ~: 14721) -GenericFactoryTest:testCreatePair_MoreThan18Decimals(uint256) (runs: 256, μ: 156106, ~: 159235) -GenericFactoryTest:testCreatePair_PairAlreadyExists(uint256) (runs: 256, μ: 19045, ~: 19004) -GenericFactoryTest:testCreatePair_ZeroAddress(uint256) (runs: 256, μ: 14908, ~: 14867) -GenericFactoryTest:testGetBytecode_CorrectConstructorData() (gas: 112679) +GenericFactoryTest:testCreatePair_IdenticalAddress(uint256) (runs: 256, μ: 14765, ~: 14721) +GenericFactoryTest:testCreatePair_MoreThan18Decimals(uint256) (runs: 256, μ: 155930, ~: 159270) +GenericFactoryTest:testCreatePair_PairAlreadyExists(uint256) (runs: 256, μ: 19048, ~: 19004) +GenericFactoryTest:testCreatePair_ZeroAddress(uint256) (runs: 256, μ: 14911, ~: 14867) +GenericFactoryTest:testGetBytecode_CorrectConstructorData() (gas: 112802) GenericFactoryTest:testGetPair() (gas: 20918) OracleWriterTest:testMaxChangeRate_Default() (gas: 68110) OracleWriterTest:testOracle_MintWrongPriceThenConverge() (gas: 247897806) -OracleWriterTest:testOracle_NoWriteInSameTimestamp() (gas: 488841) -OracleWriterTest:testOracle_OverflowAccPrice(uint32) (runs: 256, μ: 376761, ~: 376684) -OracleWriterTest:testOracle_SamePriceDiffLiq(uint32) (runs: 256, μ: 10158279, ~: 10158292) -OracleWriterTest:testOracle_SamePriceSameLiq(uint32) (runs: 256, μ: 10154891, ~: 10154904) -OracleWriterTest:testOracle_SameReservesDiffPrice(uint32) (runs: 256, μ: 10146791, ~: 10146779) -OracleWriterTest:testOracle_WrapsAroundAfterFull() (gas: 4217529379) +OracleWriterTest:testOracle_NoWriteInSameTimestamp() (gas: 489192) +OracleWriterTest:testOracle_OverflowAccPrice(uint32) (runs: 256, μ: 377003, ~: 376918) +OracleWriterTest:testOracle_SamePriceDiffLiq(uint32) (runs: 256, μ: 10168674, ~: 10168687) +OracleWriterTest:testOracle_SamePriceSameLiq(uint32) (runs: 256, μ: 10165286, ~: 10165299) +OracleWriterTest:testOracle_SameReservesDiffPrice(uint32) (runs: 256, μ: 10157187, ~: 10157174) +OracleWriterTest:testOracle_WrapsAroundAfterFull() (gas: 4225197559) OracleWriterTest:testSetClampParams_OnlyFactory() (gas: 91155) -OracleWriterTest:testSetClampParams_TooHigh(uint256) (runs: 256, μ: 76542, ~: 76652) +OracleWriterTest:testSetClampParams_TooHigh(uint256) (runs: 256, μ: 76549, ~: 76652) OracleWriterTest:testSetClampParams_TooLow() (gas: 69054) -OracleWriterTest:testUpdateOracle_AccumulateOldPricesNotNew() (gas: 221072) -OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedBothMaxChangeRateAndMaxChangePerTrade() (gas: 357498) -OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedMaxChangePerTrade() (gas: 305783) -OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedMaxChangeRate() (gas: 306501) -OracleWriterTest:testUpdateOracle_DecreasePrice_LongElapsedTime() (gas: 299742) -OracleWriterTest:testUpdateOracle_LatestTimestampWritten(uint256) (runs: 256, μ: 290872, ~: 291226) -OracleWriterTest:testUpdateOracle_MintThenSwapSameBlock() (gas: 314930) -OracleWriterTest:testUpdateOracle_MultipleSwapsSameBlock() (gas: 410086) -OracleWriterTest:testWriteObservations() (gas: 431094) +OracleWriterTest:testUpdateOracle_AccumulateOldPricesNotNew() (gas: 221189) +OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedBothMaxChangeRateAndMaxChangePerTrade() (gas: 357615) +OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedMaxChangePerTrade() (gas: 305900) +OracleWriterTest:testUpdateOracle_DecreasePrice_ExceedMaxChangeRate() (gas: 306618) +OracleWriterTest:testUpdateOracle_DecreasePrice_LongElapsedTime() (gas: 299859) +OracleWriterTest:testUpdateOracle_LatestTimestampWritten(uint256) (runs: 256, μ: 290996, ~: 291343) +OracleWriterTest:testUpdateOracle_MintThenSwapSameBlock() (gas: 315164) +OracleWriterTest:testUpdateOracle_MultipleSwapsSameBlock() (gas: 410437) +OracleWriterTest:testWriteObservations() (gas: 431328) PairTest:testCustomPlatformFee_OffByDefault() (gas: 74381) PairTest:testCustomSwapFee_OffByDefault() (gas: 68833) -PairTest:testEmitEventOnCreation() (gas: 9601363) +PairTest:testEmitEventOnCreation() (gas: 9611407) PairTest:testNonPayable() (gas: 79592) PairTest:testRecoverToken() (gas: 200069) PairTest:testSetPlatformFeeForPair() (gas: 111080) -PairTest:testSetPlatformFeeForPair_BreachMaximum(uint256) (runs: 256, μ: 110818, ~: 110445) +PairTest:testSetPlatformFeeForPair_BreachMaximum(uint256) (runs: 256, μ: 110814, ~: 110445) PairTest:testSetPlatformFeeForPair_Reset() (gas: 118525) PairTest:testSetSwapFeeForPair() (gas: 112048) -PairTest:testSetSwapFeeForPair_BreachMaximum(uint256) (runs: 256, μ: 111023, ~: 110691) +PairTest:testSetSwapFeeForPair_BreachMaximum(uint256) (runs: 256, μ: 111034, ~: 110691) PairTest:testSetSwapFeeForPair_Reset() (gas: 125017) PairTest:testSwapFee_UseDefault() (gas: 19026) PairTest:testUpdateDefaultFees() (gas: 166371) @@ -114,55 +114,60 @@ ReservoirERC20Test:testApprove_TransferOne() (gas: 67005) ReservoirERC20Test:testApprove_TransferTooMuch() (gas: 37978) ReservoirPairTest:testCheckedTransfer_RevertWhenTransferFail() (gas: 131174) ReservoirPairTest:testCheckedTransfer_RevertWhenTransferReverts() (gas: 433392) -ReservoirPairTest:testPlatformFee_Disable() (gas: 1019616) -ReservoirPairTest:testPlatformFee_DisableReenable() (gas: 1435332) -ReservoirPairTest:testReentrancyGuard_LargeTimestamp() (gas: 197899) -ReservoirPairTest:testSkim(uint256,uint256) (runs: 256, μ: 257053, ~: 256997) -ReservoirPairTest:testSync() (gas: 192471) +ReservoirPairTest:testPlatformFee_Disable() (gas: 1020364) +ReservoirPairTest:testPlatformFee_DisableReenable() (gas: 1436642) +ReservoirPairTest:testReentrancyGuard_LargeTimestamp() (gas: 198133) +ReservoirPairTest:testSkim(uint256,uint256) (runs: 256, μ: 257057, ~: 256997) +ReservoirPairTest:testSync() (gas: 192588) ReservoirTimelockTest:testRampA(uint32) (runs: 256, μ: 36132, ~: 36132) ReservoirTimelockTest:testRampA_NotAdmin() (gas: 17852) ReservoirTimelockTest:testSetCustomPlatformFee(uint256) (runs: 256, μ: 134458, ~: 135105) ReservoirTimelockTest:testSetCustomPlatformFee_NotAdmin() (gas: 81112) -ReservoirTimelockTest:testSetCustomSwapFee(uint256) (runs: 256, μ: 136532, ~: 137142) +ReservoirTimelockTest:testSetCustomSwapFee(uint256) (runs: 256, μ: 136525, ~: 137142) ReservoirTimelockTest:testSetCustomSwapFee_NotAdmin() (gas: 81406) -StableMathTest:testComputeLiquidityFromAdjustedBalances_ConvergeEvenWithVeryUnbalancedValues(uint256,uint256,uint256) (runs: 256, μ: 13985, ~: 12424) -StableMathTest:testGetAmountIn(uint256,uint256,uint256) (runs: 256, μ: 22131, ~: 21987) -StableMathTest:testGetAmountOut(uint256,uint256,uint256) (runs: 256, μ: 26076, ~: 25762) +StableMathTest:testComputeLiquidityFromAdjustedBalances_ConvergeEvenWithVeryUnbalancedValues(uint256,uint256,uint256) (runs: 256, μ: 14133, ~: 12439) +StableMathTest:testGetAmountIn(uint256,uint256,uint256) (runs: 256, μ: 22130, ~: 21987) +StableMathTest:testGetAmountOut(uint256,uint256,uint256) (runs: 256, μ: 26107, ~: 25722) StableMathTest:testMinALessThanMaxA() (gas: 3381) -StablePairGas:testGasBurn() (gas: 102875) -StablePairGas:testGasMint() (gas: 124790) -StablePairGas:testGasMint_Initial() (gas: 253977) -StablePairGas:testGasSwap() (gas: 78936) -StablePairGas:testGasSwap_UpdateOracle() (gas: 105348) -StablePairGas:testGasSwap_UpdateOracleClamped() (gas: 102230) -StablePairTest:testAttackWhileRampingDown_LongInterval() (gas: 202899) -StablePairTest:testAttackWhileRampingDown_ShortInterval() (gas: 204209) -StablePairTest:testBurn() (gas: 144841) -StablePairTest:testBurn_DiffDecimalPlaces(uint256) (runs: 256, μ: 5491420, ~: 5491299) -StablePairTest:testBurn_LastInvariantUseReserveInsteadOfBalance() (gas: 263676) +StableOracleMathTest:testCalcSpotPrice_CanonicalVersion_VerySmallAmounts(uint256,uint256) (runs: 256, μ: 13406, ~: 13271) +StableOracleMathTest:testCalcSpotPrice_VerySmallAmounts(uint256,uint256) (runs: 256, μ: 13728, ~: 13593) +StableOracleMathTest:testCalculatedSpotPriceIsCloseToEstimated(uint256,uint256) (runs: 256, μ: 21625, ~: 22024) +StableOracleMathTest:testPrice_Token0MoreExpensive() (gas: 9381) +StableOracleMathTest:testPrice_Token1MoreExpensive() (gas: 9679) +StablePairGas:testGasBurn() (gas: 102992) +StablePairGas:testGasMint() (gas: 124907) +StablePairGas:testGasMint_Initial() (gas: 254094) +StablePairGas:testGasSwap() (gas: 79053) +StablePairGas:testGasSwap_UpdateOracle() (gas: 105465) +StablePairGas:testGasSwap_UpdateOracleClamped() (gas: 102347) +StablePairTest:testAttackWhileRampingDown_LongInterval() (gas: 203133) +StablePairTest:testAttackWhileRampingDown_ShortInterval() (gas: 204443) +StablePairTest:testBurn() (gas: 144978) +StablePairTest:testBurn_DiffDecimalPlaces(uint256) (runs: 256, μ: 5501729, ~: 5501577) +StablePairTest:testBurn_LastInvariantUseReserveInsteadOfBalance() (gas: 264027) StablePairTest:testBurn_Reenter() (gas: 48719) -StablePairTest:testBurn_SucceedEvenIfMintFeeReverts() (gas: 143703) -StablePairTest:testBurn_WhenRampingA(uint256,uint32) (runs: 256, μ: 332413, ~: 332430) -StablePairTest:testBurn_Zero() (gas: 81490) -StablePairTest:testFactoryAmpTooHigh() (gas: 271071) -StablePairTest:testFactoryAmpTooLow() (gas: 266661) +StablePairTest:testBurn_SucceedEvenIfMintFeeReverts() (gas: 143820) +StablePairTest:testBurn_WhenRampingA(uint256,uint32) (runs: 256, μ: 332634, ~: 332664) +StablePairTest:testBurn_Zero() (gas: 81607) +StablePairTest:testFactoryAmpTooHigh() (gas: 271106) +StablePairTest:testFactoryAmpTooLow() (gas: 266696) StablePairTest:testGetCurrentA() (gas: 32277) -StablePairTest:testMint() (gas: 117916) +StablePairTest:testMint() (gas: 118033) StablePairTest:testMintFee_CallableBySelf() (gas: 16698) -StablePairTest:testMintFee_DiffPlatformFees(uint256) (runs: 256, μ: 6911981, ~: 6913235) +StablePairTest:testMintFee_DiffPlatformFees(uint256) (runs: 256, μ: 6926947, ~: 6928193) StablePairTest:testMintFee_NotCallableByOthers() (gas: 8541) -StablePairTest:testMintFee_WhenRampingA_PoolBalanced(uint256) (runs: 256, μ: 6980778, ~: 6980823) -StablePairTest:testMintFee_WhenRampingA_PoolUnbalanced(uint256) (runs: 256, μ: 6396046, ~: 6395907) -StablePairTest:testMint_CalculationOverflowInStableMath() (gas: 5318816) -StablePairTest:testMint_NonOptimalProportion() (gas: 172605) -StablePairTest:testMint_NonOptimalProportion_ThenBurn() (gas: 354840) -StablePairTest:testMint_OnlyTransferOneToken() (gas: 5257255) -StablePairTest:testMint_PlatformFeeOff() (gas: 120979) +StablePairTest:testMintFee_WhenRampingA_PoolBalanced(uint256) (runs: 256, μ: 6995863, ~: 6995898) +StablePairTest:testMintFee_WhenRampingA_PoolUnbalanced(uint256) (runs: 256, μ: 6408791, ~: 6408642) +StablePairTest:testMint_CalculationOverflowInStableMath() (gas: 5328860) +StablePairTest:testMint_NonOptimalProportion() (gas: 172722) +StablePairTest:testMint_NonOptimalProportion_ThenBurn() (gas: 355191) +StablePairTest:testMint_OnlyTransferOneToken() (gas: 5267299) +StablePairTest:testMint_PlatformFeeOff() (gas: 121096) StablePairTest:testMint_Reenter() (gas: 48141) -StablePairTest:testMint_WhenRampingA(uint256,uint32) (runs: 256, μ: 356874, ~: 356887) -StablePairTest:testOracle_ClampedPrice_NoDiffWithinLimit() (gas: 164912) -StablePairTest:testOracle_CorrectPrice(uint32) (runs: 256, μ: 5618924, ~: 5618924) -StablePairTest:testOracle_SimplePrices(uint32) (runs: 256, μ: 5624665, ~: 5624665) +StablePairTest:testMint_WhenRampingA(uint256,uint32) (runs: 256, μ: 357096, ~: 357121) +StablePairTest:testOracle_ClampedPrice_NoDiffWithinLimit() (gas: 165146) +StablePairTest:testOracle_CorrectPrice(uint32) (runs: 256, μ: 5629670, ~: 5629670) +StablePairTest:testOracle_SimplePrices(uint32) (runs: 256, μ: 5635411, ~: 5635411) StablePairTest:testRampA() (gas: 32550) StablePairTest:testRampA_BreachMaxSpeed() (gas: 24046) StablePairTest:testRampA_BreachMaxSpeed_Halve() (gas: 23585) @@ -173,25 +178,25 @@ StablePairTest:testRampA_MaxSpeed_Halve() (gas: 29006) StablePairTest:testRampA_OnlyFactory() (gas: 8715) StablePairTest:testRampA_SetAtMaximum() (gas: 27421) StablePairTest:testRampA_SetAtMinimum() (gas: 26938) -StablePairTest:testRampA_SwappingDuringRampingDown(uint256,uint256,uint256,uint256) (runs: 256, μ: 576232, ~: 459071) -StablePairTest:testRampA_SwappingDuringRampingUp(uint256,uint256,uint256,uint256) (runs: 256, μ: 575664, ~: 458802) +StablePairTest:testRampA_SwappingDuringRampingDown(uint256,uint256,uint256,uint256) (runs: 256, μ: 577469, ~: 471062) +StablePairTest:testRampA_SwappingDuringRampingUp(uint256,uint256,uint256,uint256) (runs: 256, μ: 576885, ~: 470596) StablePairTest:testStopRampA() (gas: 34839) -StablePairTest:testStopRampA_Early(uint256,uint32) (runs: 256, μ: 43076, ~: 43162) -StablePairTest:testStopRampA_Late(uint256) (runs: 256, μ: 42508, ~: 42590) +StablePairTest:testStopRampA_Early(uint256,uint32) (runs: 256, μ: 43059, ~: 43162) +StablePairTest:testStopRampA_Late(uint256) (runs: 256, μ: 42500, ~: 42590) StablePairTest:testStopRampA_OnlyFactory() (gas: 9181) -StablePairTest:testSwap() (gas: 119527) -StablePairTest:testSwap_BetterPerformanceThanConstantProduct() (gas: 187191) -StablePairTest:testSwap_DiffAs(uint256,uint256,uint256,uint256) (runs: 256, μ: 5554278, ~: 5555287) -StablePairTest:testSwap_DiffSwapFees(uint256) (runs: 256, μ: 5531346, ~: 5532106) +StablePairTest:testSwap() (gas: 119644) +StablePairTest:testSwap_BetterPerformanceThanConstantProduct() (gas: 187308) +StablePairTest:testSwap_DiffAs(uint256,uint256,uint256,uint256) (runs: 256, μ: 5564592, ~: 5565549) +StablePairTest:testSwap_DiffSwapFees(uint256) (runs: 256, μ: 5541619, ~: 5542384) StablePairTest:testSwap_ExactInExceedUint104() (gas: 74746) StablePairTest:testSwap_ExactOutExceedReserves() (gas: 29861) -StablePairTest:testSwap_IncreasingSwapFees(uint256,uint256,uint256) (runs: 256, μ: 389336, ~: 389504) +StablePairTest:testSwap_IncreasingSwapFees(uint256,uint256,uint256) (runs: 256, μ: 389693, ~: 389855) StablePairTest:testSwap_MinInt256() (gas: 19284) StablePairTest:testSwap_Reenter() (gas: 87163) -StablePairTest:testSwap_Token0ExactOut(uint256) (runs: 256, μ: 152002, ~: 137530) -StablePairTest:testSwap_Token1ExactOut(uint256) (runs: 256, μ: 152191, ~: 137667) -StablePairTest:testSwap_VeryLargeLiquidity(uint256) (runs: 256, μ: 5481880, ~: 5480098) -StablePairTest:testSwap_VerySmallLiquidity(uint256,uint256,uint256) (runs: 256, μ: 5520407, ~: 5498975) +StablePairTest:testSwap_Token0ExactOut(uint256) (runs: 256, μ: 151273, ~: 137450) +StablePairTest:testSwap_Token1ExactOut(uint256) (runs: 256, μ: 151459, ~: 137587) +StablePairTest:testSwap_VeryLargeLiquidity(uint256) (runs: 256, μ: 5492201, ~: 5490376) +StablePairTest:testSwap_VerySmallLiquidity(uint256,uint256,uint256) (runs: 256, μ: 5530434, ~: 5508249) StablePairTest:testSwap_ZeroInput() (gas: 12788) StdMathTest:testPercentDelta() (gas: 3351) StdMathTest:testPercentDelta_MinusOne() (gas: 3197) diff --git a/package.json b/package.json index 7946a77b..5c715fc7 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "gas:check": "forge snapshot --check", "generate": "typechain --target ethers-v5 --out-dir typings 'out/**/*.json'", "install": "npm run install:balancer", - "install:balancer": "cd reference/balancer-v2-monorepo && yarn", + "install:balancer": "cd reference/balancer-v2-monorepo && yarn && yarn workspace @balancer-labs/balancer-js build", "lint": "npm run lint:check", "lint:check": "npm run solhint:check && npm run prettier:check && npm run mdlint:check", "lint:fix": "npm run solhint:fix && npm run prettier:fix && npm run eslint:fix && npm run mdlint:fix", diff --git a/script/optimized-deployer-meta b/script/optimized-deployer-meta index 38f5dd50..31d28789 100644 --- a/script/optimized-deployer-meta +++ b/script/optimized-deployer-meta @@ -1,5 +1,5 @@ { "constant_product_hash": "0xe174de1f7ab5f7c871f23787d956a8d1b4ebbb3b195eb2d6af27fb3a8c9e812e", "factory_hash": "0x87b0f73fafcf4bb41e013c8423dc679f6885527007d6c3f1e1834a670cbaadc5", - "stable_hash": "0x3ae886aee24fa2cc0144d24306033a7ed47e91bc0f962e4bffcef5922ae175f5" + "stable_hash": "0x6f62531ebc702a07ab48405ba9437786af3664b83b0da26ec295402590ed738f" } \ No newline at end of file diff --git a/script/unoptimized-deployer-meta b/script/unoptimized-deployer-meta index c7487494..b9a696c3 100644 --- a/script/unoptimized-deployer-meta +++ b/script/unoptimized-deployer-meta @@ -1,5 +1,5 @@ { "constant_product_hash": "0x89ba88e63d531f6343d8f30d0baf35d10eb900e4e9e5cf64e857a17d63c27863", "factory_hash": "0x09a9ce1ed77c95be4842dddd771939e048b8bfe2837863be3a2766b1c13ea5a2", - "stable_hash": "0x2556755e769639b4fbeff548907a3a675f94209a24a97c7fca31eedb76567c2f" + "stable_hash": "0xe1770a7f5ab45f8da1a575db115e93cc8f48d5c768f72090dfe9cc85c62dc8d4" } \ No newline at end of file diff --git a/src/ReservoirDeployer.sol b/src/ReservoirDeployer.sol index 7c016a13..37dc8c20 100644 --- a/src/ReservoirDeployer.sol +++ b/src/ReservoirDeployer.sol @@ -28,7 +28,7 @@ contract ReservoirDeployer { bytes32 public constant FACTORY_HASH = bytes32(0x87b0f73fafcf4bb41e013c8423dc679f6885527007d6c3f1e1834a670cbaadc5); bytes32 public constant CONSTANT_PRODUCT_HASH = bytes32(0xe174de1f7ab5f7c871f23787d956a8d1b4ebbb3b195eb2d6af27fb3a8c9e812e); - bytes32 public constant STABLE_HASH = bytes32(0x3ae886aee24fa2cc0144d24306033a7ed47e91bc0f962e4bffcef5922ae175f5); + bytes32 public constant STABLE_HASH = bytes32(0x6f62531ebc702a07ab48405ba9437786af3664b83b0da26ec295402590ed738f); // Deployment addresses. GenericFactory public factory; diff --git a/src/libraries/StableOracleMath.sol b/src/libraries/StableOracleMath.sol index 52a74b0e..3bc2a972 100644 --- a/src/libraries/StableOracleMath.sol +++ b/src/libraries/StableOracleMath.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; - import { LogCompression } from "src/libraries/LogCompression.sol"; import { StableMath } from "src/libraries/StableMath.sol"; @@ -26,7 +25,7 @@ library StableOracleMath { logSpotPrice = LogCompression.toLowResLog(spotPrice); } - /// @notice Calculates the spot price of token1 in token0. + /// @notice Calculates the spot price of token1 in token0. i.e. token0 is base, token1 is quote /// @param amplificationParameter The stable amplification parameter in precise form (see StableMath.A_PRECISION). /// @param reserve0 The reserve of token0 normalized to 18 decimals. /// @param reserve1 The reserve of token1 normalized to 18 decimals. @@ -55,15 +54,21 @@ library StableOracleMath { uint256 axy2 = (a * 2 * reserve0).mulWad(reserve1); // n = 2 + uint256 by = b.mulWad(reserve1); + uint256 ay2 = ((a * reserve1).mulWad(reserve1)); + if (by > axy2 + ay2) return 1e18; // dx = a.x.y.2 + a.y^2 - b.y - uint256 derivativeX = axy2 + ((a * reserve1).mulWad(reserve1)) - (b.mulWad(reserve1)); + uint256 derivativeX = axy2 + ay2 - by; // dy = a.x.y.2 + a.x^2 - b.x - uint256 derivativeY = axy2 + ((a * reserve0).mulWad(reserve0)) - (b.mulWad(reserve0)); + uint256 bx = (b.mulWad(reserve0)); + uint256 ax2 = ((a * reserve0).mulWad(reserve0)); + if (bx > axy2 + ax2) return 1e18; + uint256 derivativeY = axy2 + ax2 - bx; - if (derivativeY == 0 || derivativeX == 0) { - return 1e18; - } + // This is to prevent division by 0 which happens if reserve0 and reserve1 are sufficiently small (~1e6 after normalization) which can brick the pair + // If the reserves are that small, their prices will not be serving as price oracles, thus this is safe. + if (derivativeY == 0) return 1e18; // The rounding direction is irrelevant as we're about to introduce a much larger error when converting to log // space. We use `divWadUp` as it prevents the result from being zero, which would make the logarithm revert. A diff --git a/test/__mocks/StableOracleMathCanonical.sol b/test/__mocks/StableOracleMathCanonical.sol new file mode 100644 index 00000000..ea56cc6e --- /dev/null +++ b/test/__mocks/StableOracleMathCanonical.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.10; + +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { StableMath } from "src/libraries/StableMath.sol"; + +// the original implementation without safeguards as implemented by balancer +// https://github.com/balancer/balancer-v2-monorepo/blob/903d34e491a5e9c5d59dabf512c7addf1ccf9bbd/pkg/pool-stable/contracts/meta/StableOracleMath.sol +library StableOracleMathCanonical { + using FixedPointMathLib for uint256; + + function calcSpotPrice(uint256 amplificationParameter, uint256 reserve0, uint256 reserve1) + internal + view + returns (uint256 spotPrice) + { + // // + // 2.a.x.y + a.y^2 + b.y // + // spot price Y/X = - dx/dy = ----------------------- // + // 2.a.x.y + a.x^2 + b.x // + // // + // n = 2 // + // a = amp param * n // + // b = D + a.(S - D) // + // D = invariant // + // S = sum of balances but x,y = 0 since x and y are the only tokens // + + uint256 invariant = + StableMath._computeLiquidityFromAdjustedBalances(reserve0, reserve1, 2 * amplificationParameter); + + uint256 a = (amplificationParameter * 2) / StableMath.A_PRECISION; + uint256 b = (invariant * a) - invariant; + uint256 axy2 = (a * 2 * reserve0).mulWad(reserve1); // n = 2 + + // dx = a.x.y.2 + a.y^2 - b.y + uint256 derivativeX = axy2 + ((a * reserve1).mulWad(reserve1)) - (b.mulWad(reserve1)); + + // dy = a.x.y.2 + a.x^2 - b.x + uint256 derivativeY = axy2 + ((a * reserve0).mulWad(reserve0)) - (b.mulWad(reserve0)); + + // The rounding direction is irrelevant as we're about to introduce a much larger error when converting to log + // space. We use `divWadUp` as it prevents the result from being zero, which would make the logarithm revert. A + // result of zero is therefore only possible with zero balances, which are prevented via other means. + spotPrice = derivativeX.divWadUp(derivativeY); + } +} diff --git a/test/unit/libraries/StableOracleMath.t.sol b/test/unit/libraries/StableOracleMath.t.sol new file mode 100644 index 00000000..e5f29dce --- /dev/null +++ b/test/unit/libraries/StableOracleMath.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.10; + +import "forge-std/Test.sol"; +import { FixedPointMathLib } from "solady/utils/FixedPointMathLib.sol"; +import { StableOracleMath, StableMath } from "src/libraries/StableOracleMath.sol"; +import { Constants } from "src/Constants.sol"; +import { StableOracleMathCanonical } from "test/__mocks/StableOracleMathCanonical.sol"; + +contract StableOracleMathTest is Test { + using FixedPointMathLib for uint256; + + uint256 internal _defaultAmp = Constants.DEFAULT_AMP_COEFF * StableMath.A_PRECISION; + + // estimates the spot price by giving a very small input to simulate dx (an infinitesimally small x) + function estimateSpotPrice( + uint256 reserve0, + uint256 reserve1, + uint256 token0Multiplier, + uint256 token1Multiplier, + uint256 N_A + ) internal pure returns (uint256 rPrice) { + uint256 lInputAmt = 1e8; // anything smaller than 1e7 the error becomes larger, as experimented + uint256 lOut = StableMath._getAmountOut(lInputAmt, reserve0, reserve1, token0Multiplier, token1Multiplier, true, 0, N_A); + rPrice = lOut.divWadUp(lInputAmt); + } + + function testPrice_Token0MoreExpensive() external { + // arrange + uint256 lToken0Amt = 1_000_000e18; + uint256 lToken1Amt = 2_000_000e18; + + // act + uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); + + // assert + assertEq(lPrice, 1_000_842_880_946_746_931); + } + + function testPrice_Token1MoreExpensive() external { + // arrange + uint256 lToken0Amt = 2_000_000e18; + uint256 lToken1Amt = 1_000_000e18; + + // act + uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); + + // assert + assertEq(lPrice, 999_157_828_903_224_444); + } + + function testCalcSpotPrice_CanonicalVersion_VerySmallAmounts(uint256 aToken0Amt, uint256 aToken1Amt) external { + // assume - if token amounts exceed these amounts then they will probably not revert + uint256 lToken0Amt = bound(aToken0Amt, 1, 1e6); + uint256 lToken1Amt = bound(aToken1Amt, 1, 6e6); + + // act & assert - reverts when the amount is very small + vm.expectRevert(); + StableOracleMathCanonical.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); + } + + function testCalcSpotPrice_VerySmallAmounts(uint256 aToken0Amt, uint256 aToken1Amt) external { + // assume + uint256 lToken0Amt = bound(aToken0Amt, 1, 1e6); + uint256 lToken1Amt = bound(aToken1Amt, 1, 6e6); + + // act - does not revert in this case, but instead just returns 1e18 + uint256 lPrice = StableOracleMath.calcSpotPrice(_defaultAmp, lToken0Amt, lToken1Amt); + + // assert + assertEq(lPrice, 1e18); + } + + function testCalculatedSpotPriceIsCloseToEstimated(uint256 aReserve0, uint256 aReserve1) external { + // assume + uint256 lReserve0 = bound(aReserve0, 1e18, 1000e18); + uint256 lReserve1 = bound(aReserve1, 1e18, 1000e18); + + // act + uint256 lSpotEstimated = estimateSpotPrice(lReserve0, lReserve1, 1, 1, _defaultAmp * 2); + uint256 lSpotCalculated = StableOracleMath.calcSpotPrice(_defaultAmp, lReserve0, lReserve1); + + // assert + assertApproxEqRel(lSpotEstimated, lSpotCalculated, 0.000001e18); // 1% of 1bp, or a 0.0001% error + } +}