From a241b6aceda9d5967c548a6a5de4b487c8198fb3 Mon Sep 17 00:00:00 2001 From: "A.L" Date: Fri, 7 Jun 2024 17:38:01 +0800 Subject: [PATCH] feat: `PriceType` config * refactor: rename `Variable` into `PriceType` * feat: make `priceType` configurable * gas: update snapshot * fix: mark visibility of public variable * gas: update snapshot * fix: read price type from storage during `updatePrice` * gas: update snapshot --- .gas-snapshot | 90 ++++++++++++------------ src/Enums.sol | 2 +- src/ReservoirPriceOracle.sol | 24 +++++-- src/Structs.sol | 12 ++-- src/interfaces/IReservoirPriceOracle.sol | 4 +- src/libraries/QueryProcessor.sol | 26 +++---- src/libraries/Samples.sol | 18 ++--- test/__fixtures/BaseTest.t.sol | 4 +- test/unit/ReservoirPriceOracle.t.sol | 30 +++++--- test/unit/libraries/QueryProcessor.t.sol | 32 ++++----- test/unit/libraries/Samples.t.sol | 14 ++-- test/wrapper/QueryProcessorWrapper.sol | 14 ++-- 12 files changed, 145 insertions(+), 125 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index cd436e6..fc6fccc 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,25 +1,25 @@ FlagsLibTest:testGetDecimalDifference() (gas: 3974) FlagsLibTest:testIsCompositeRoute() (gas: 4341) FlagsLibTest:testPackSimplePrice(int8,uint256) (runs: 256, μ: 7794, ~: 7555) -QueryProcessorTest:testFindNearestSample_CanFindExactValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 67359104, ~: 75557360) -QueryProcessorTest:testFindNearestSample_CanFindIntermediateValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 72740235, ~: 82229136) +QueryProcessorTest:testFindNearestSample_CanFindExactValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 69058566, ~: 77743481) +QueryProcessorTest:testFindNearestSample_CanFindIntermediateValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 72812141, ~: 81208062) QueryProcessorTest:testFindNearestSample_NotInitialized() (gas: 8937393461068805977) -QueryProcessorTest:testFindNearestSample_OneSample(uint256) (runs: 256, μ: 80321, ~: 80360) +QueryProcessorTest:testFindNearestSample_OneSample(uint256) (runs: 256, μ: 80315, ~: 80360) QueryProcessorTest:testGetInstantValue() (gas: 124248) QueryProcessorTest:testGetInstantValue_NotInitialized(uint256) (runs: 256, μ: 19397, ~: 19397) -QueryProcessorTest:testGetInstantValue_NotInitialized_BeyondBufferSize(uint8,uint16) (runs: 256, μ: 68389639, ~: 68389599) -QueryProcessorTest:testGetPastAccumulator_BufferEmpty(uint8) (runs: 256, μ: 27031, ~: 27087) -QueryProcessorTest:testGetPastAccumulator_ExactMatch(uint32,uint256,uint256,uint16) (runs: 256, μ: 68735833, ~: 78421559) -QueryProcessorTest:testGetPastAccumulator_ExactMatch_LatestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 67855253, ~: 76188526) -QueryProcessorTest:testGetPastAccumulator_ExactMatch_OldestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 67885080, ~: 76220126) -QueryProcessorTest:testGetPastAccumulator_ExtrapolatesBeyondLatest(uint32,uint256,uint256,uint256) (runs: 256, μ: 72713324, ~: 82200036) -QueryProcessorTest:testGetPastAccumulator_InterpolatesBetweenPastAccumulators(uint32,uint256,uint256,uint256) (runs: 256, μ: 72747625, ~: 82235144) -QueryProcessorTest:testGetPastAccumulator_InvalidAgo(uint32,uint256,uint256,uint256) (runs: 256, μ: 72704951, ~: 82192090) -QueryProcessorTest:testGetPastAccumulator_QueryTooOld(uint32,uint256,uint256,uint256) (runs: 256, μ: 72716162, ~: 82201974) -QueryProcessorTest:testGetTimeWeightedAverage(uint32,uint256,uint256,uint256,uint256) (runs: 256, μ: 109440354, ~: 113540903) +QueryProcessorTest:testGetInstantValue_NotInitialized_BeyondBufferSize(uint8,uint16) (runs: 256, μ: 68389643, ~: 68389600) +QueryProcessorTest:testGetPastAccumulator_BufferEmpty(uint8) (runs: 256, μ: 27022, ~: 27087) +QueryProcessorTest:testGetPastAccumulator_ExactMatch(uint32,uint256,uint256,uint16) (runs: 256, μ: 69817768, ~: 78996938) +QueryProcessorTest:testGetPastAccumulator_ExactMatch_LatestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 68901555, ~: 79087570) +QueryProcessorTest:testGetPastAccumulator_ExactMatch_OldestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 68931370, ~: 79119170) +QueryProcessorTest:testGetPastAccumulator_ExtrapolatesBeyondLatest(uint32,uint256,uint256,uint256) (runs: 256, μ: 72785052, ~: 81179346) +QueryProcessorTest:testGetPastAccumulator_InterpolatesBetweenPastAccumulators(uint32,uint256,uint256,uint256) (runs: 256, μ: 72819515, ~: 81214070) +QueryProcessorTest:testGetPastAccumulator_InvalidAgo(uint32,uint256,uint256,uint256) (runs: 256, μ: 72776690, ~: 81171057) +QueryProcessorTest:testGetPastAccumulator_QueryTooOld(uint32,uint256,uint256,uint256) (runs: 256, μ: 72787880, ~: 81180941) +QueryProcessorTest:testGetTimeWeightedAverage(uint32,uint256,uint256,uint256,uint256) (runs: 256, μ: 108173406, ~: 112092926) QueryProcessorTest:testGetTimeWeightedAverage_BadSecs() (gas: 10995) -ReservoirPriceOracleTest:testClearRoute() (gas: 52178) -ReservoirPriceOracleTest:testClearRoute_AllWordsCleared() (gas: 155206) +ReservoirPriceOracleTest:testClearRoute() (gas: 52231) +ReservoirPriceOracleTest:testClearRoute_AllWordsCleared() (gas: 155316) ReservoirPriceOracleTest:testDesignatePair() (gas: 29135) ReservoirPriceOracleTest:testDesignatePair_IncorrectPair() (gas: 21200) ReservoirPriceOracleTest:testDesignatePair_NotOwner() (gas: 17531) @@ -27,50 +27,50 @@ ReservoirPriceOracleTest:testDesignatePair_TokenOrderReversed() (gas: 30796) ReservoirPriceOracleTest:testGasBountyAvailable(uint256) (runs: 256, μ: 9883, ~: 9881) ReservoirPriceOracleTest:testGasBountyAvailable_Zero() (gas: 8939) ReservoirPriceOracleTest:testGetLargestSafeQueryWindow() (gas: 8412) -ReservoirPriceOracleTest:testGetLatest(uint32) (runs: 256, μ: 92794, ~: 92731) +ReservoirPriceOracleTest:testGetLatest(uint32) (runs: 256, μ: 92782, ~: 92731) ReservoirPriceOracleTest:testGetLatest_Inverted() (gas: 96786) ReservoirPriceOracleTest:testGetPastAccumulators() (gas: 196383) -ReservoirPriceOracleTest:testGetPastAccumulators_Inverted() (gas: 156771) -ReservoirPriceOracleTest:testGetQuote(uint256,uint256) (runs: 256, μ: 35814, ~: 35927) +ReservoirPriceOracleTest:testGetPastAccumulators_Inverted() (gas: 156794) +ReservoirPriceOracleTest:testGetQuote(uint256,uint256) (runs: 256, μ: 35817, ~: 35927) ReservoirPriceOracleTest:testGetQuote_AmountInTooLarge() (gas: 13030) -ReservoirPriceOracleTest:testGetQuote_ComplicatedDecimals() (gas: 10353281) -ReservoirPriceOracleTest:testGetQuote_Inverse(uint256,uint256) (runs: 256, μ: 37970, ~: 38150) -ReservoirPriceOracleTest:testGetQuote_MultipleHops() (gas: 114499) +ReservoirPriceOracleTest:testGetQuote_ComplicatedDecimals() (gas: 10353303) +ReservoirPriceOracleTest:testGetQuote_Inverse(uint256,uint256) (runs: 256, μ: 37978, ~: 38150) +ReservoirPriceOracleTest:testGetQuote_MultipleHops() (gas: 114521) ReservoirPriceOracleTest:testGetQuote_MultipleHops_Inverse() (gas: 114821) -ReservoirPriceOracleTest:testGetQuote_MultipleHops_PriceZero() (gas: 127407) +ReservoirPriceOracleTest:testGetQuote_MultipleHops_PriceZero() (gas: 127429) ReservoirPriceOracleTest:testGetQuote_NoFallbackOracle() (gas: 13914) ReservoirPriceOracleTest:testGetQuote_PriceZero() (gas: 16564) -ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_1HopRoute(uint256,uint256,address,address,uint8,uint8) (runs: 256, μ: 5327965, ~: 5328073) -ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_2HopRoute(uint256,uint256,uint256,address,address,address,uint8,uint8,uint8) (runs: 256, μ: 10493988, ~: 10494081) +ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_1HopRoute(uint256,uint256,address,address,uint8,uint8) (runs: 256, μ: 5327948, ~: 5328087) +ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_2HopRoute(uint256,uint256,uint256,address,address,address,uint8,uint8,uint8) (runs: 256, μ: 10494021, ~: 10494095) ReservoirPriceOracleTest:testGetQuote_SameBaseQuote(uint256,address) (runs: 256, μ: 9030, ~: 9030) -ReservoirPriceOracleTest:testGetQuote_UseFallback() (gas: 35311) +ReservoirPriceOracleTest:testGetQuote_UseFallback() (gas: 35304) ReservoirPriceOracleTest:testGetQuote_ZeroIn() (gas: 39390) -ReservoirPriceOracleTest:testGetQuotes(uint256,uint256) (runs: 256, μ: 33347, ~: 33460) +ReservoirPriceOracleTest:testGetQuotes(uint256,uint256) (runs: 256, μ: 33350, ~: 33460) ReservoirPriceOracleTest:testGetTimeWeightedAverage() (gas: 141958) ReservoirPriceOracleTest:testGetTimeWeightedAverage_Inverted() (gas: 121129) ReservoirPriceOracleTest:testSetFallbackOracle_NotOwner() (gas: 11003) -ReservoirPriceOracleTest:testSetRoute() (gas: 58848) -ReservoirPriceOracleTest:testSetRoute_InvalidRoute() (gas: 17982) -ReservoirPriceOracleTest:testSetRoute_InvalidRouteLength() (gas: 17611) -ReservoirPriceOracleTest:testSetRoute_MultipleHops() (gas: 196135) -ReservoirPriceOracleTest:testSetRoute_NotSorted() (gas: 12095) -ReservoirPriceOracleTest:testSetRoute_OverwriteExisting() (gas: 162578) -ReservoirPriceOracleTest:testSetRoute_SameToken() (gas: 12048) +ReservoirPriceOracleTest:testSetRoute() (gas: 58892) +ReservoirPriceOracleTest:testSetRoute_InvalidRoute() (gas: 18049) +ReservoirPriceOracleTest:testSetRoute_InvalidRouteLength() (gas: 17655) +ReservoirPriceOracleTest:testSetRoute_MultipleHops() (gas: 196245) +ReservoirPriceOracleTest:testSetRoute_NotSorted() (gas: 12117) +ReservoirPriceOracleTest:testSetRoute_OverwriteExisting() (gas: 162666) +ReservoirPriceOracleTest:testSetRoute_SameToken() (gas: 12070) ReservoirPriceOracleTest:testUndesignatePair() (gas: 30307) -ReservoirPriceOracleTest:testUndesignatePair_NotOwner() (gas: 15288) +ReservoirPriceOracleTest:testUndesignatePair_NotOwner() (gas: 15266) ReservoirPriceOracleTest:testUpdatePriceDeviationThreshold(uint256) (runs: 256, μ: 21392, ~: 21107) -ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold() (gas: 216404) -ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_InsufficientReward(uint256) (runs: 256, μ: 205469, ~: 205672) -ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_ZeroRecipient() (gas: 197963) -ReservoirPriceOracleTest:testUpdatePrice_FirstUpdate() (gas: 205527) -ReservoirPriceOracleTest:testUpdatePrice_IntermediateRoutes() (gas: 15870947) -ReservoirPriceOracleTest:testUpdatePrice_PriceOutOfRange() (gas: 5355182) -ReservoirPriceOracleTest:testUpdatePrice_WithinThreshold() (gas: 206559) -ReservoirPriceOracleTest:testUpdateRewardGasAmount() (gas: 19055) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold() (gas: 216851) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_InsufficientReward(uint256) (runs: 256, μ: 205891, ~: 206097) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_ZeroRecipient() (gas: 198433) +ReservoirPriceOracleTest:testUpdatePrice_FirstUpdate() (gas: 205930) +ReservoirPriceOracleTest:testUpdatePrice_IntermediateRoutes() (gas: 15872049) +ReservoirPriceOracleTest:testUpdatePrice_PriceOutOfRange() (gas: 5355607) +ReservoirPriceOracleTest:testUpdatePrice_WithinThreshold() (gas: 206962) +ReservoirPriceOracleTest:testUpdateRewardGasAmount() (gas: 19077) ReservoirPriceOracleTest:testUpdateRewardGasAmount_NotOwner() (gas: 11006) -ReservoirPriceOracleTest:testUpdateTwapPeriod(uint256) (runs: 256, μ: 21704, ~: 21806) -ReservoirPriceOracleTest:testUpdateTwapPeriod_InvalidTwapPeriod(uint256) (runs: 256, μ: 17868, ~: 18164) -ReservoirPriceOracleTest:testWritePriceCache(uint256) (runs: 256, μ: 29978, ~: 29777) +ReservoirPriceOracleTest:testUpdateTwapPeriod(uint256) (runs: 256, μ: 21787, ~: 21892) +ReservoirPriceOracleTest:testUpdateTwapPeriod_InvalidTwapPeriod(uint256) (runs: 256, μ: 17918, ~: 18208) +ReservoirPriceOracleTest:testWritePriceCache(uint256) (runs: 256, μ: 29981, ~: 29777) SamplesTest:testAccumulator() (gas: 3959) SamplesTest:testAccumulator_BadVariableRequest() (gas: 3523) SamplesTest:testInstant() (gas: 3909) diff --git a/src/Enums.sol b/src/Enums.sol index e726fe4..0507d3e 100644 --- a/src/Enums.sol +++ b/src/Enums.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; // as a countermeasure to oracle manipulation attempts. // Refer to `maxChangeRate` and `maxChangePerTrade` in `ReservoirPair` and the `Observation` struct // Note that the price is computed *including* the tokens decimals, just like the raw price. -enum Variable { +enum PriceType { RAW_PRICE, CLAMPED_PRICE } diff --git a/src/ReservoirPriceOracle.sol b/src/ReservoirPriceOracle.sol index 69f68e5..e3bb6f9 100644 --- a/src/ReservoirPriceOracle.sol +++ b/src/ReservoirPriceOracle.sol @@ -11,7 +11,7 @@ import { OracleAccumulatorQuery } from "src/interfaces/IReservoirPriceOracle.sol"; import { IPriceOracle } from "src/interfaces/IPriceOracle.sol"; -import { QueryProcessor, ReservoirPair, Buffer, Variable } from "src/libraries/QueryProcessor.sol"; +import { QueryProcessor, ReservoirPair, Buffer, PriceType } from "src/libraries/QueryProcessor.sol"; import { Utils } from "src/libraries/Utils.sol"; import { Owned } from "lib/amm-core/lib/solmate/src/auth/Owned.sol"; import { ReentrancyGuard } from "lib/amm-core/lib/solmate/src/utils/ReentrancyGuard.sol"; @@ -37,6 +37,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. event RewardGasAmount(uint256 newAmount); event Route(address token0, address token1, address[] route); event Price(address token0, address token1, uint256 price); + event SetPriceType(PriceType priceType); event TwapPeriod(uint256 newPeriod); /////////////////////////////////////////////////////////////////////////////////////////////// @@ -47,6 +48,8 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. /// @dev If `address(0)` then there is no fallback. address public fallbackOracle; + /// @dev the following 4 storage variables take up 1 storage slot + /// @notice percentage change greater than which, a price update may result in a reward payout of native tokens, /// subject to availability of rewards. /// 1e18 == 100% @@ -59,6 +62,9 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. /// @notice TWAP period (in seconds) for querying the oracle uint64 public twapPeriod; + /// @notice The type of price queried and stored, possibilities as defined by `PriceType`. + PriceType public priceType; + /// @notice Designated pairs to serve as price feed for a certain token0 and token1 mapping(address token0 => mapping(address token1 => ReservoirPair pair)) public pairs; @@ -66,10 +72,11 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. // CONSTRUCTOR, FALLBACKS // /////////////////////////////////////////////////////////////////////////////////////////////// - constructor(uint64 aThreshold, uint64 aTwapPeriod, uint64 aMultiplier) { + constructor(uint64 aThreshold, uint64 aTwapPeriod, uint64 aMultiplier, PriceType aType) { updatePriceDeviationThreshold(aThreshold); updateTwapPeriod(aTwapPeriod); updateRewardGasAmount(aMultiplier); + setPriceType(aType); } /// @dev contract will hold native tokens to be distributed as gas bounty for updating the prices @@ -142,7 +149,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. (lToken0, lToken1) = lRoute[i].sortTokens(lRoute[i + 1]); lQueries[i] = OracleAverageQuery( - Variable.RAW_PRICE, + priceType, lToken0, lToken1, twapPeriod, @@ -188,7 +195,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. _validatePair(lPair); (,,, uint16 lIndex) = lPair.getReserves(); - uint256 lResult = lPair.getTimeWeightedAverage(lQuery.variable, lQuery.secs, lQuery.ago, lIndex); + uint256 lResult = lPair.getTimeWeightedAverage(lQuery.priceType, lQuery.secs, lQuery.ago, lIndex); rResults[i] = lResult; } } @@ -199,7 +206,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. _validatePair(lPair); (,,, uint256 lIndex) = lPair.getReserves(); - uint256 lResult = lPair.getInstantValue(aQuery.variable, lIndex); + uint256 lResult = lPair.getInstantValue(aQuery.priceType, lIndex); return lResult; } @@ -218,7 +225,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. _validatePair(lPair); (,,, uint16 lIndex) = lPair.getReserves(); - int256 lAcc = lPair.getPastAccumulator(lQuery.variable, lIndex, lQuery.ago); + int256 lAcc = lPair.getPastAccumulator(lQuery.priceType, lIndex, lQuery.ago); rResults[i] = lAcc; } } @@ -493,6 +500,11 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. emit DesignatePair(aToken0, aToken1, ReservoirPair(address(0))); } + function setPriceType(PriceType aType) public onlyOwner { + priceType = aType; + emit SetPriceType(aType); + } + /// @notice Sets the price route between aToken0 and aToken1, and also intermediate routes if previously undefined /// @param aToken0 Address of the lower token /// @param aToken1 Address of the higher token diff --git a/src/Structs.sol b/src/Structs.sol index 3323789..94858e0 100644 --- a/src/Structs.sol +++ b/src/Structs.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import { Variable } from "src/Enums.sol"; +import { PriceType } from "src/Enums.sol"; /** * @dev Information for a Time Weighted Average query. @@ -12,7 +12,7 @@ import { Variable } from "src/Enums.sol"; * The address of `base` is strictly less than the address of `quote` */ struct OracleAverageQuery { - Variable variable; + PriceType priceType; address base; address quote; uint256 secs; @@ -20,13 +20,13 @@ struct OracleAverageQuery { } /** - * @dev Information for a query for the latest variable + * @dev Information for a query for the latest priceType * - * Each query computes the latest instantaneous variable. + * Each query computes the latest instantaneous priceType. * The address of `base` is strictly less than the address of `quote` */ struct OracleLatestQuery { - Variable variable; + PriceType priceType; address base; address quote; } @@ -38,7 +38,7 @@ struct OracleLatestQuery { * The address of `base` is strictly less than the address of `quote` */ struct OracleAccumulatorQuery { - Variable variable; + PriceType priceType; address base; address quote; uint256 ago; diff --git a/src/interfaces/IReservoirPriceOracle.sol b/src/interfaces/IReservoirPriceOracle.sol index af16534..16e92ce 100644 --- a/src/interfaces/IReservoirPriceOracle.sol +++ b/src/interfaces/IReservoirPriceOracle.sol @@ -37,9 +37,9 @@ interface IReservoirPriceOracle { returns (uint256[] memory results); /** - * @dev Returns latest sample of `variable`. Prices are represented as 18 decimal fixed point values. + * @dev Returns latest sample of `priceType`. Prices are represented as 18 decimal fixed point values. */ - function getLatest(OracleLatestQuery calldata variable) external view returns (uint256); + function getLatest(OracleLatestQuery calldata priceType) external view returns (uint256); /** * @dev Returns largest time window that can be safely queried, where 'safely' means the Oracle is guaranteed to be diff --git a/src/libraries/QueryProcessor.sol b/src/libraries/QueryProcessor.sol index b90fce1..5d6dbe3 100644 --- a/src/libraries/QueryProcessor.sol +++ b/src/libraries/QueryProcessor.sol @@ -18,7 +18,7 @@ import { Buffer } from "amm-core/libraries/Buffer.sol"; import { ReservoirPair, Observation } from "amm-core/ReservoirPair.sol"; import { OracleErrors } from "src/libraries/OracleErrors.sol"; -import { Samples, Variable } from "src/libraries/Samples.sol"; +import { Samples, PriceType } from "src/libraries/Samples.sol"; /** * @dev Auxiliary library for PoolPriceOracle, offloading most of the query code to reduce bytecode size by using this @@ -32,13 +32,13 @@ library QueryProcessor { using Samples for Observation; /** - * @dev Returns the value for `variable` at the indexed sample. + * @dev Returns the value for `priceType` at the indexed sample. */ - function getInstantValue(ReservoirPair pair, Variable variable, uint256 index) internal view returns (uint256) { + function getInstantValue(ReservoirPair pair, PriceType priceType, uint256 index) internal view returns (uint256) { Observation memory sample = pair.observation(index); if (sample.timestamp == 0) revert OracleErrors.OracleNotInitialized(); - int256 rawInstantValue = sample.instant(variable); + int256 rawInstantValue = sample.instant(priceType); return LogCompression.fromLowResLog(rawInstantValue); } @@ -47,7 +47,7 @@ library QueryProcessor { */ function getTimeWeightedAverage( ReservoirPair pair, - Variable variable, + PriceType priceType, uint256 secs, uint256 ago, uint16 latestIndex @@ -61,14 +61,14 @@ library QueryProcessor { // `endAccumulator` and `beginAccumulators` themselves will not overflow/underflow until at least after year 2106. So the subtraction will not underflow as well. // Therefore it is safe to use unchecked here unchecked { - int256 beginAccumulator = getPastAccumulator(pair, variable, latestIndex, ago + secs); - int256 endAccumulator = getPastAccumulator(pair, variable, latestIndex, ago); + int256 beginAccumulator = getPastAccumulator(pair, priceType, latestIndex, ago + secs); + int256 endAccumulator = getPastAccumulator(pair, priceType, latestIndex, ago); return LogCompression.fromLowResLog((endAccumulator - beginAccumulator) / int256(secs)); } } /** - * @dev Returns the value of the accumulator for `variable` `ago` seconds ago. `latestIndex` must be the index of + * @dev Returns the value of the accumulator for `priceType` `ago` seconds ago. `latestIndex` must be the index of * the latest sample in the buffer. * * Reverts under the following conditions: @@ -84,7 +84,7 @@ library QueryProcessor { * timestamp is stored every two minutes), it is estimated by performing linear interpolation using the closest * values. This process is guaranteed to complete performing at most 11 storage reads. */ - function getPastAccumulator(ReservoirPair pair, Variable variable, uint16 latestIndex, uint256 ago) + function getPastAccumulator(ReservoirPair pair, PriceType priceType, uint16 latestIndex, uint256 ago) internal view returns (int256) @@ -116,7 +116,7 @@ library QueryProcessor { // The accumulator can be represented in 53 bits, timestamps in 31bits, and the instant value in 22 bits. So this addition will not overflow. unchecked { uint256 elapsed = lookUpTime - latestTimestamp; - return latestSample.accumulator(variable) + (latestSample.instant(variable) * int256(elapsed)); + return latestSample.accumulator(priceType) + (latestSample.instant(priceType) * int256(elapsed)); } } else { // The look up time is before the latest sample, but we need to make sure that it is not before the oldest @@ -166,14 +166,14 @@ library QueryProcessor { // The accumulators can be represented in 53 bits, and timestamps are in 31 bits. So the addition and subtraction will not under/overflow. // `lookupTime` is greater than `latestTimestamp` and is thus also greater than `prev.timestamp` so subtraction will not underflow. unchecked { - int256 samplesAccDiff = next.accumulator(variable) - prev.accumulator(variable); + int256 samplesAccDiff = next.accumulator(priceType) - prev.accumulator(priceType); uint256 elapsed = lookUpTime - prev.timestamp; - return prev.accumulator(variable) + ((samplesAccDiff * int256(elapsed)) / int256(samplesTimeDiff)); + return prev.accumulator(priceType) + ((samplesAccDiff * int256(elapsed)) / int256(samplesTimeDiff)); } } else { // Rarely, one of the samples will have the exact requested look up time, which is indicated by `prev` // and `next` being the same. In this case, we simply return the accumulator at that point in time. - return prev.accumulator(variable); + return prev.accumulator(priceType); } } } diff --git a/src/libraries/Samples.sol b/src/libraries/Samples.sol index 03cd638..992b049 100644 --- a/src/libraries/Samples.sol +++ b/src/libraries/Samples.sol @@ -15,17 +15,17 @@ pragma solidity ^0.8.0; import { Observation } from "amm-core/ReservoirPair.sol"; -import { Variable } from "src/Enums.sol"; +import { PriceType } from "src/Enums.sol"; import { OracleErrors } from "src/libraries/OracleErrors.sol"; library Samples { /** - * @dev Returns the instant value stored in `sample` for `variable`. + * @dev Returns the instant value stored in `sample` for `priceType`. */ - function instant(Observation memory sample, Variable variable) internal pure returns (int256) { - if (variable == Variable.RAW_PRICE) { + function instant(Observation memory sample, PriceType priceType) internal pure returns (int256) { + if (priceType == PriceType.RAW_PRICE) { return sample.logInstantRawPrice; - } else if (variable == Variable.CLAMPED_PRICE) { + } else if (priceType == PriceType.CLAMPED_PRICE) { return sample.logInstantClampedPrice; } else { revert OracleErrors.BadVariableRequest(); @@ -33,12 +33,12 @@ library Samples { } /** - * @dev Returns the accumulator value stored in `sample` for `variable`. + * @dev Returns the accumulator value stored in `sample` for `priceType`. */ - function accumulator(Observation memory sample, Variable variable) internal pure returns (int256) { - if (variable == Variable.RAW_PRICE) { + function accumulator(Observation memory sample, PriceType priceType) internal pure returns (int256) { + if (priceType == PriceType.RAW_PRICE) { return sample.logAccRawPrice; - } else if (variable == Variable.CLAMPED_PRICE) { + } else if (priceType == PriceType.CLAMPED_PRICE) { return sample.logAccClampedPrice; } else { revert OracleErrors.BadVariableRequest(); diff --git a/test/__fixtures/BaseTest.t.sol b/test/__fixtures/BaseTest.t.sol index f335695..e0ef5ab 100644 --- a/test/__fixtures/BaseTest.t.sol +++ b/test/__fixtures/BaseTest.t.sol @@ -11,7 +11,7 @@ import { Constants } from "amm-core/Constants.sol"; import { FactoryStoreLib } from "amm-core/libraries/FactoryStore.sol"; import { MintableERC20 } from "lib/amm-core/test/__fixtures/MintableERC20.sol"; -import { ReservoirPriceOracle, IReservoirPriceOracle, IPriceOracle } from "src/ReservoirPriceOracle.sol"; +import { ReservoirPriceOracle, IReservoirPriceOracle, PriceType, IPriceOracle } from "src/ReservoirPriceOracle.sol"; contract BaseTest is Test { using FactoryStoreLib for GenericFactory; @@ -19,7 +19,7 @@ contract BaseTest is Test { GenericFactory internal _factory = new GenericFactory(); ReservoirPair internal _pair; - ReservoirPriceOracle internal _oracle = new ReservoirPriceOracle(0.02e18, 15 minutes, 500_000); + ReservoirPriceOracle internal _oracle = new ReservoirPriceOracle(0.02e18, 15 minutes, 500_000, PriceType.CLAMPED_PRICE); MintableERC20 internal _tokenA = MintableERC20(address(0x100)); MintableERC20 internal _tokenB = MintableERC20(address(0x200)); diff --git a/test/unit/ReservoirPriceOracle.t.sol b/test/unit/ReservoirPriceOracle.t.sol index 2ab7422..4e20f79 100644 --- a/test/unit/ReservoirPriceOracle.t.sol +++ b/test/unit/ReservoirPriceOracle.t.sol @@ -6,7 +6,7 @@ import { BaseTest, console2, ReservoirPair, MintableERC20 } from "test/__fixture import { Utils } from "src/libraries/Utils.sol"; import { Buffer, - Variable, + PriceType, OracleErrors, OracleLatestQuery, OracleAccumulatorQuery, @@ -69,6 +69,8 @@ contract ReservoirPriceOracleTest is BaseTest { _addressSet.add(address(_tokenB)); _addressSet.add(address(_tokenC)); _addressSet.add(address(_tokenD)); + _addressSet.add(address(_factory)); + _addressSet.add(address(_oracle)); } // solhint-disable-next-line no-empty-blocks @@ -842,7 +844,7 @@ contract ReservoirPriceOracleTest is BaseTest { _pair.sync(); _oracle.designatePair(address(_tokenA), address(_tokenB), _pair); OracleAverageQuery[] memory lQueries = new OracleAverageQuery[](1); - lQueries[0] = OracleAverageQuery(Variable.RAW_PRICE, address(_tokenA), address(_tokenB), 10, 0); + lQueries[0] = OracleAverageQuery(PriceType.RAW_PRICE, address(_tokenA), address(_tokenB), 10, 0); // act uint256[] memory lResults = _oracle.getTimeWeightedAverage(lQueries); @@ -862,7 +864,7 @@ contract ReservoirPriceOracleTest is BaseTest { // act uint256 lLatestPrice = - _oracle.getLatest(OracleLatestQuery(Variable.RAW_PRICE, address(_tokenA), address(_tokenB))); + _oracle.getLatest(OracleLatestQuery(PriceType.RAW_PRICE, address(_tokenA), address(_tokenB))); // assert assertEq(lLatestPrice, 98_918_868_099_219_913_512); @@ -878,9 +880,9 @@ contract ReservoirPriceOracleTest is BaseTest { _pair.sync(); _oracle.designatePair(address(_tokenA), address(_tokenB), _pair); OracleAccumulatorQuery[] memory lQueries = new OracleAccumulatorQuery[](3); - lQueries[0] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenA), address(_tokenB), 0); - lQueries[1] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenA), address(_tokenB), 1 hours); - lQueries[2] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenA), address(_tokenB), 2 hours); + lQueries[0] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenA), address(_tokenB), 0); + lQueries[1] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenA), address(_tokenB), 1 hours); + lQueries[2] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenA), address(_tokenB), 2 hours); // act int256[] memory lResults = _oracle.getPastAccumulators(lQueries); @@ -941,7 +943,7 @@ contract ReservoirPriceOracleTest is BaseTest { // act & assert vm.expectRevert(OracleErrors.NoDesignatedPair.selector); - _oracle.getLatest(OracleLatestQuery(Variable.RAW_PRICE, address(_tokenB), address(_tokenA))); + _oracle.getLatest(OracleLatestQuery(PriceType.RAW_PRICE, address(_tokenB), address(_tokenA))); } function testGetPastAccumulators_Inverted() external { @@ -954,9 +956,9 @@ contract ReservoirPriceOracleTest is BaseTest { _pair.sync(); _oracle.designatePair(address(_tokenA), address(_tokenB), _pair); OracleAccumulatorQuery[] memory lQueries = new OracleAccumulatorQuery[](3); - lQueries[0] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenB), address(_tokenA), 0); - lQueries[1] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenB), address(_tokenA), 1 hours); - lQueries[2] = OracleAccumulatorQuery(Variable.RAW_PRICE, address(_tokenB), address(_tokenA), 2 hours); + lQueries[0] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenB), address(_tokenA), 0); + lQueries[1] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenB), address(_tokenA), 1 hours); + lQueries[2] = OracleAccumulatorQuery(PriceType.RAW_PRICE, address(_tokenB), address(_tokenA), 2 hours); // act & assert vm.expectRevert(OracleErrors.NoDesignatedPair.selector); @@ -971,7 +973,7 @@ contract ReservoirPriceOracleTest is BaseTest { _pair.sync(); _oracle.designatePair(address(_tokenB), address(_tokenA), _pair); OracleAverageQuery[] memory lQueries = new OracleAverageQuery[](1); - lQueries[0] = OracleAverageQuery(Variable.RAW_PRICE, address(_tokenB), address(_tokenA), 10, 0); + lQueries[0] = OracleAverageQuery(PriceType.RAW_PRICE, address(_tokenB), address(_tokenA), 10, 0); // act & assert vm.expectRevert(OracleErrors.NoDesignatedPair.selector); @@ -1044,6 +1046,12 @@ contract ReservoirPriceOracleTest is BaseTest { _oracle.updatePrice(address(_tokenB), address(_tokenC), address(0)); } + function setPriceType() external { + vm.prank(address(123)); + vm.expectRevert("UNAUTHORIZED"); + _oracle.setPriceType(PriceType.RAW_PRICE); + } + function testSetRoute_SameToken() external { // arrange address lToken0 = address(0x1); diff --git a/test/unit/libraries/QueryProcessor.t.sol b/test/unit/libraries/QueryProcessor.t.sol index 972599c..41f37f7 100644 --- a/test/unit/libraries/QueryProcessor.t.sol +++ b/test/unit/libraries/QueryProcessor.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { BaseTest, FactoryStoreLib, GenericFactory } from "test/__fixtures/BaseTest.t.sol"; import { Buffer, OracleErrors } from "src/libraries/QueryProcessor.sol"; -import { QueryProcessorWrapper, ReservoirPair, Observation, Variable } from "test/wrapper/QueryProcessorWrapper.sol"; +import { QueryProcessorWrapper, ReservoirPair, Observation, PriceType } from "test/wrapper/QueryProcessorWrapper.sol"; contract QueryProcessorTest is BaseTest { using FactoryStoreLib for GenericFactory; @@ -74,8 +74,8 @@ contract QueryProcessorTest is BaseTest { _pair.swap(-105e18, true, address(this), ""); // act - uint256 lInstantRawPrice = _queryProcessor.getInstantValue(_pair, Variable.RAW_PRICE, 0); - uint256 lInstantClampedPrice = _queryProcessor.getInstantValue(_pair, Variable.CLAMPED_PRICE, 0); + uint256 lInstantRawPrice = _queryProcessor.getInstantValue(_pair, PriceType.RAW_PRICE, 0); + uint256 lInstantClampedPrice = _queryProcessor.getInstantValue(_pair, PriceType.CLAMPED_PRICE, 0); // assert - instant price should be the new price after swap, not the price before swap assertApproxEqRel(lInstantRawPrice, 100e18, 0.01e18); @@ -111,12 +111,12 @@ contract QueryProcessorTest is BaseTest { // act (,,, uint16 lLatestIndex) = _pair.getReserves(); uint256 lAveragePrice = - _queryProcessor.getTimeWeightedAverage(_pair, Variable.RAW_PRICE, lSecs, lAgo, lLatestIndex); + _queryProcessor.getTimeWeightedAverage(_pair, PriceType.RAW_PRICE, lSecs, lAgo, lLatestIndex); // assert // as it is hard to calc the exact average price given so many fuzz parameters, we just assert that the price should be within a range uint256 lStartingPrice = 98.9223e18; - uint256 lEndingPrice = _queryProcessor.getInstantValue(_pair, Variable.RAW_PRICE, lLatestIndex); + uint256 lEndingPrice = _queryProcessor.getInstantValue(_pair, PriceType.RAW_PRICE, lLatestIndex); assertLt(lAveragePrice, lStartingPrice); assertGt(lAveragePrice, lEndingPrice); } @@ -138,7 +138,7 @@ contract QueryProcessorTest is BaseTest { // act uint256 lAgo = lBlockTime * lBlocksAgo; - int256 lAcc = _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, lAgo); + int256 lAcc = _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, lAgo); // assert uint256 lDesiredIndex = lIndex.sub(lBlocksAgo); @@ -161,7 +161,7 @@ contract QueryProcessorTest is BaseTest { (,,, uint16 lIndex) = _pair.getReserves(); // act - int256 lAcc = _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, 0); + int256 lAcc = _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, 0); // assert vm.prank(address(_queryProcessor)); @@ -188,7 +188,7 @@ contract QueryProcessorTest is BaseTest { uint256 lAgo = lObservationsToWrite > Buffer.SIZE ? block.timestamp - _pair.observation(lIndex.next()).timestamp : block.timestamp - (lStartTime + lBlockTime); - int256 lAcc = _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, lAgo); + int256 lAcc = _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, lAgo); // assert Observation memory lObs = _pair.observation(lObservationsToWrite > Buffer.SIZE ? lIndex.next() : 0); @@ -216,7 +216,7 @@ contract QueryProcessorTest is BaseTest { Observation memory lPrevObs = _pair.observation(lRandomSlot); uint256 lWantedTimestamp = lPrevObs.timestamp + lBlockTime / 2; uint256 lAgo = block.timestamp - lWantedTimestamp; - int256 lAcc = _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, lAgo); + int256 lAcc = _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, lAgo); // assert Observation memory lNextObs = _pair.observation(lRandomSlot.next()); @@ -244,7 +244,7 @@ contract QueryProcessorTest is BaseTest { (,,, uint16 lIndex) = _pair.getReserves(); // act - int256 lAcc = _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, 0); + int256 lAcc = _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, 0); // assert vm.prank(address(_queryProcessor)); @@ -338,12 +338,12 @@ contract QueryProcessorTest is BaseTest { function testGetInstantValue_NotInitialized(uint256 aIndex) external { // act & assert vm.expectRevert(OracleErrors.OracleNotInitialized.selector); - _queryProcessor.getInstantValue(_pair, Variable.RAW_PRICE, aIndex); + _queryProcessor.getInstantValue(_pair, PriceType.RAW_PRICE, aIndex); } function testGetInstantValue_NotInitialized_BeyondBufferSize(uint8 aVariable, uint16 aIndex) external { // assume - Variable lVar = Variable(bound(aVariable, 0, 1)); + PriceType lVar = PriceType(bound(aVariable, 0, 1)); uint16 lIndex = uint16(bound(aIndex, Buffer.SIZE, type(uint16).max)); // arrange - fill up buffer size @@ -356,7 +356,7 @@ contract QueryProcessorTest is BaseTest { function testGetPastAccumulator_BufferEmpty(uint8 aVariable) external { // assume - Variable lVar = Variable(bound(aVariable, 0, 1)); + PriceType lVar = PriceType(bound(aVariable, 0, 1)); // arrange (,,, uint16 lIndex) = _pair.getReserves(); @@ -383,7 +383,7 @@ contract QueryProcessorTest is BaseTest { // act & assert vm.expectRevert(OracleErrors.InvalidSeconds.selector); - _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, lAgo); + _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, lAgo); } function testGetPastAccumulator_QueryTooOld( @@ -409,7 +409,7 @@ contract QueryProcessorTest is BaseTest { // act & assert vm.expectRevert(OracleErrors.QueryTooOld.selector); - _queryProcessor.getPastAccumulator(_pair, Variable.RAW_PRICE, lIndex, lAgo); + _queryProcessor.getPastAccumulator(_pair, PriceType.RAW_PRICE, lIndex, lAgo); } // technically this should never happen in production as `getPastAccumulator` would have reverted with the @@ -430,6 +430,6 @@ contract QueryProcessorTest is BaseTest { function testGetTimeWeightedAverage_BadSecs() external { // act & assert vm.expectRevert(OracleErrors.BadSecs.selector); - _queryProcessor.getTimeWeightedAverage(_pair, Variable.RAW_PRICE, 0, 0, 0); + _queryProcessor.getTimeWeightedAverage(_pair, PriceType.RAW_PRICE, 0, 0, 0); } } diff --git a/test/unit/libraries/Samples.t.sol b/test/unit/libraries/Samples.t.sol index 692e45d..42d66a3 100644 --- a/test/unit/libraries/Samples.t.sol +++ b/test/unit/libraries/Samples.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { Test, console2, stdError } from "forge-std/Test.sol"; -import { Samples, Observation, Variable } from "src/libraries/Samples.sol"; +import { Samples, Observation, PriceType } from "src/libraries/Samples.sol"; contract SamplesTest is Test { using Samples for Observation; @@ -13,8 +13,8 @@ contract SamplesTest is Test { Observation memory lObs = Observation(-123, -456, 3, 4, 5); // act - int256 lInstantRawPrice = lObs.instant(Variable.RAW_PRICE); - int256 lInstantClampedPrice = lObs.instant(Variable.CLAMPED_PRICE); + int256 lInstantRawPrice = lObs.instant(PriceType.RAW_PRICE); + int256 lInstantClampedPrice = lObs.instant(PriceType.CLAMPED_PRICE); // assert assertEq(lInstantRawPrice, -123); @@ -25,7 +25,7 @@ contract SamplesTest is Test { // would like to test the revert behavior when passing an invalid enum // but solidity has a check to prevent casting a uint that is out of range of the enum vm.expectRevert(stdError.enumConversionError); - Variable(uint256(5)); + PriceType(uint256(5)); } function testAccumulator() external pure { @@ -33,8 +33,8 @@ contract SamplesTest is Test { Observation memory lObs = Observation(-789, -569, -401, -1238, 5); // act - int256 lAccRawPrice = lObs.accumulator(Variable.RAW_PRICE); - int256 lAccClampedPrice = lObs.accumulator(Variable.CLAMPED_PRICE); + int256 lAccRawPrice = lObs.accumulator(PriceType.RAW_PRICE); + int256 lAccClampedPrice = lObs.accumulator(PriceType.CLAMPED_PRICE); // assert assertEq(lAccRawPrice, -401); @@ -45,6 +45,6 @@ contract SamplesTest is Test { // would like to test the revert behavior when passing an invalid enum // but solidity has a check to prevent casting a uint that is out of range of the enum vm.expectRevert(stdError.enumConversionError); - Variable(uint256(5)); + PriceType(uint256(5)); } } diff --git a/test/wrapper/QueryProcessorWrapper.sol b/test/wrapper/QueryProcessorWrapper.sol index 2644fc9..ba8b682 100644 --- a/test/wrapper/QueryProcessorWrapper.sol +++ b/test/wrapper/QueryProcessorWrapper.sol @@ -1,29 +1,29 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.0; -import { QueryProcessor, ReservoirPair, Variable, Observation } from "src/libraries/QueryProcessor.sol"; +import { QueryProcessor, ReservoirPair, PriceType, Observation } from "src/libraries/QueryProcessor.sol"; contract QueryProcessorWrapper { - function getInstantValue(ReservoirPair pair, Variable variable, uint256 index) external view returns (uint256) { - return QueryProcessor.getInstantValue(pair, variable, index); + function getInstantValue(ReservoirPair pair, PriceType priceType, uint256 index) external view returns (uint256) { + return QueryProcessor.getInstantValue(pair, priceType, index); } function getTimeWeightedAverage( ReservoirPair pair, - Variable variable, + PriceType priceType, uint256 secs, uint256 ago, uint16 latestIndex ) external view returns (uint256) { - return QueryProcessor.getTimeWeightedAverage(pair, variable, secs, ago, latestIndex); + return QueryProcessor.getTimeWeightedAverage(pair, priceType, secs, ago, latestIndex); } - function getPastAccumulator(ReservoirPair pair, Variable variable, uint16 latestIndex, uint256 ago) + function getPastAccumulator(ReservoirPair pair, PriceType priceType, uint16 latestIndex, uint256 ago) external view returns (int256) { - return QueryProcessor.getPastAccumulator(pair, variable, latestIndex, ago); + return QueryProcessor.getPastAccumulator(pair, priceType, latestIndex, ago); } function findNearestSample(ReservoirPair pair, uint256 lookUpDate, uint16 offset, uint16 length)