From 5ca6a8a910fa8b114a4ddaca4119a5c7ba97c209 Mon Sep 17 00:00:00 2001 From: "A.L" Date: Mon, 10 Jun 2024 02:18:46 +0800 Subject: [PATCH] feat: resolve vaults' underlying * chore: import ERC4626 interface * lint: fix issues * feat: resolve base asset * gas: update snapshot --- .gas-snapshot | 119 ++++++++++++++------------- src/ReservoirPriceOracle.sol | 32 +++++-- test/mock/MockFallbackOracle.sol | 6 +- test/mock/StubERC4626.sol | 32 +++++++ test/unit/ReservoirPriceOracle.t.sol | 18 ++++ 5 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 test/mock/StubERC4626.sol diff --git a/.gas-snapshot b/.gas-snapshot index fc6fccc..2c51c92 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,76 +1,77 @@ 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, μ: 69058566, ~: 77743481) -QueryProcessorTest:testFindNearestSample_CanFindIntermediateValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 72812141, ~: 81208062) +FlagsLibTest:testPackSimplePrice(int8,uint256) (runs: 256, μ: 7791, ~: 7555) +QueryProcessorTest:testFindNearestSample_CanFindExactValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 65405445, ~: 73998750) +QueryProcessorTest:testFindNearestSample_CanFindIntermediateValue(uint32,uint256,uint256,uint256) (runs: 256, μ: 64633234, ~: 73958177) QueryProcessorTest:testFindNearestSample_NotInitialized() (gas: 8937393461068805977) -QueryProcessorTest:testFindNearestSample_OneSample(uint256) (runs: 256, μ: 80315, ~: 80360) +QueryProcessorTest:testFindNearestSample_OneSample(uint256) (runs: 256, μ: 80327, ~: 80360) QueryProcessorTest:testGetInstantValue() (gas: 124248) QueryProcessorTest:testGetInstantValue_NotInitialized(uint256) (runs: 256, μ: 19397, ~: 19397) -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:testGetInstantValue_NotInitialized_BeyondBufferSize(uint8,uint16) (runs: 256, μ: 68389672, ~: 68389600) +QueryProcessorTest:testGetPastAccumulator_BufferEmpty(uint8) (runs: 256, μ: 27030, ~: 27087) +QueryProcessorTest:testGetPastAccumulator_ExactMatch(uint32,uint256,uint256,uint16) (runs: 256, μ: 71734889, ~: 79217705) +QueryProcessorTest:testGetPastAccumulator_ExactMatch_LatestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 69735120, ~: 78064819) +QueryProcessorTest:testGetPastAccumulator_ExactMatch_OldestAccumulator(uint32,uint256,uint256) (runs: 256, μ: 69765138, ~: 78096419) +QueryProcessorTest:testGetPastAccumulator_ExtrapolatesBeyondLatest(uint32,uint256,uint256,uint256) (runs: 256, μ: 64606927, ~: 73929867) +QueryProcessorTest:testGetPastAccumulator_InterpolatesBetweenPastAccumulators(uint32,uint256,uint256,uint256) (runs: 256, μ: 64641047, ~: 73964185) +QueryProcessorTest:testGetPastAccumulator_InvalidAgo(uint32,uint256,uint256,uint256) (runs: 256, μ: 64598512, ~: 73921235) +QueryProcessorTest:testGetPastAccumulator_QueryTooOld(uint32,uint256,uint256,uint256) (runs: 256, μ: 64610136, ~: 73931119) +QueryProcessorTest:testGetTimeWeightedAverage(uint32,uint256,uint256,uint256,uint256) (runs: 256, μ: 103846182, ~: 110904990) QueryProcessorTest:testGetTimeWeightedAverage_BadSecs() (gas: 10995) -ReservoirPriceOracleTest:testClearRoute() (gas: 52231) -ReservoirPriceOracleTest:testClearRoute_AllWordsCleared() (gas: 155316) -ReservoirPriceOracleTest:testDesignatePair() (gas: 29135) -ReservoirPriceOracleTest:testDesignatePair_IncorrectPair() (gas: 21200) -ReservoirPriceOracleTest:testDesignatePair_NotOwner() (gas: 17531) -ReservoirPriceOracleTest:testDesignatePair_TokenOrderReversed() (gas: 30796) -ReservoirPriceOracleTest:testGasBountyAvailable(uint256) (runs: 256, μ: 9883, ~: 9881) +ReservoirPriceOracleTest:testClearRoute() (gas: 52319) +ReservoirPriceOracleTest:testClearRoute_AllWordsCleared() (gas: 155404) +ReservoirPriceOracleTest:testDesignatePair() (gas: 29102) +ReservoirPriceOracleTest:testDesignatePair_IncorrectPair() (gas: 21222) +ReservoirPriceOracleTest:testDesignatePair_NotOwner() (gas: 17553) +ReservoirPriceOracleTest:testDesignatePair_TokenOrderReversed() (gas: 30740) +ReservoirPriceOracleTest:testGasBountyAvailable(uint256) (runs: 256, μ: 9885, ~: 9881) ReservoirPriceOracleTest:testGasBountyAvailable_Zero() (gas: 8939) ReservoirPriceOracleTest:testGetLargestSafeQueryWindow() (gas: 8412) -ReservoirPriceOracleTest:testGetLatest(uint32) (runs: 256, μ: 92782, ~: 92731) -ReservoirPriceOracleTest:testGetLatest_Inverted() (gas: 96786) -ReservoirPriceOracleTest:testGetPastAccumulators() (gas: 196383) -ReservoirPriceOracleTest:testGetPastAccumulators_Inverted() (gas: 156794) -ReservoirPriceOracleTest:testGetQuote(uint256,uint256) (runs: 256, μ: 35817, ~: 35927) -ReservoirPriceOracleTest:testGetQuote_AmountInTooLarge() (gas: 13030) -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: 127429) -ReservoirPriceOracleTest:testGetQuote_NoFallbackOracle() (gas: 13914) -ReservoirPriceOracleTest:testGetQuote_PriceZero() (gas: 16564) -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: 35304) -ReservoirPriceOracleTest:testGetQuote_ZeroIn() (gas: 39390) -ReservoirPriceOracleTest:testGetQuotes(uint256,uint256) (runs: 256, μ: 33350, ~: 33460) -ReservoirPriceOracleTest:testGetTimeWeightedAverage() (gas: 141958) -ReservoirPriceOracleTest:testGetTimeWeightedAverage_Inverted() (gas: 121129) +ReservoirPriceOracleTest:testGetLatest(uint32) (runs: 256, μ: 92853, ~: 92787) +ReservoirPriceOracleTest:testGetLatest_Inverted() (gas: 96864) +ReservoirPriceOracleTest:testGetPastAccumulators() (gas: 196417) +ReservoirPriceOracleTest:testGetPastAccumulators_Inverted() (gas: 156850) +ReservoirPriceOracleTest:testGetQuote(uint256,uint256) (runs: 256, μ: 35789, ~: 35904) +ReservoirPriceOracleTest:testGetQuote_AmountInTooLarge() (gas: 12985) +ReservoirPriceOracleTest:testGetQuote_BaseIsVault(uint256) (runs: 256, μ: 363876, ~: 363634) +ReservoirPriceOracleTest:testGetQuote_ComplicatedDecimals() (gas: 10353280) +ReservoirPriceOracleTest:testGetQuote_Inverse(uint256,uint256) (runs: 256, μ: 37975, ~: 38148) +ReservoirPriceOracleTest:testGetQuote_MultipleHops() (gas: 114609) +ReservoirPriceOracleTest:testGetQuote_MultipleHops_Inverse() (gas: 114842) +ReservoirPriceOracleTest:testGetQuote_MultipleHops_PriceZero() (gas: 127427) +ReservoirPriceOracleTest:testGetQuote_NoFallbackOracle() (gas: 16112) +ReservoirPriceOracleTest:testGetQuote_PriceZero() (gas: 16519) +ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_1HopRoute(uint256,uint256,address,address,uint8,uint8) (runs: 256, μ: 5328071, ~: 5328117) +ReservoirPriceOracleTest:testGetQuote_RandomizeAllParam_2HopRoute(uint256,uint256,uint256,address,address,address,uint8,uint8,uint8) (runs: 256, μ: 10493941, ~: 10493960) +ReservoirPriceOracleTest:testGetQuote_SameBaseQuote(uint256,address) (runs: 256, μ: 8985, ~: 8985) +ReservoirPriceOracleTest:testGetQuote_UseFallback() (gas: 37825) +ReservoirPriceOracleTest:testGetQuote_ZeroIn() (gas: 39322) +ReservoirPriceOracleTest:testGetQuotes(uint256,uint256) (runs: 256, μ: 33345, ~: 33460) +ReservoirPriceOracleTest:testGetTimeWeightedAverage() (gas: 142014) +ReservoirPriceOracleTest:testGetTimeWeightedAverage_Inverted() (gas: 121185) ReservoirPriceOracleTest:testSetFallbackOracle_NotOwner() (gas: 11003) -ReservoirPriceOracleTest:testSetRoute() (gas: 58892) +ReservoirPriceOracleTest:testSetRoute() (gas: 58936) ReservoirPriceOracleTest:testSetRoute_InvalidRoute() (gas: 18049) ReservoirPriceOracleTest:testSetRoute_InvalidRouteLength() (gas: 17655) -ReservoirPriceOracleTest:testSetRoute_MultipleHops() (gas: 196245) +ReservoirPriceOracleTest:testSetRoute_MultipleHops() (gas: 196333) ReservoirPriceOracleTest:testSetRoute_NotSorted() (gas: 12117) -ReservoirPriceOracleTest:testSetRoute_OverwriteExisting() (gas: 162666) +ReservoirPriceOracleTest:testSetRoute_OverwriteExisting() (gas: 162732) ReservoirPriceOracleTest:testSetRoute_SameToken() (gas: 12070) -ReservoirPriceOracleTest:testUndesignatePair() (gas: 30307) -ReservoirPriceOracleTest:testUndesignatePair_NotOwner() (gas: 15266) -ReservoirPriceOracleTest:testUpdatePriceDeviationThreshold(uint256) (runs: 256, μ: 21392, ~: 21107) -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:testUndesignatePair() (gas: 30318) +ReservoirPriceOracleTest:testUndesignatePair_NotOwner() (gas: 15288) +ReservoirPriceOracleTest:testUpdatePriceDeviationThreshold(uint256) (runs: 256, μ: 21398, ~: 21152) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold() (gas: 216785) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_InsufficientReward(uint256) (runs: 256, μ: 205839, ~: 206053) +ReservoirPriceOracleTest:testUpdatePrice_BeyondThreshold_ZeroRecipient() (gas: 198455) +ReservoirPriceOracleTest:testUpdatePrice_FirstUpdate() (gas: 205996) +ReservoirPriceOracleTest:testUpdatePrice_IntermediateRoutes() (gas: 15872304) +ReservoirPriceOracleTest:testUpdatePrice_PriceOutOfRange() (gas: 5355619) +ReservoirPriceOracleTest:testUpdatePrice_WithinThreshold() (gas: 207028) +ReservoirPriceOracleTest:testUpdateRewardGasAmount() (gas: 19033) ReservoirPriceOracleTest:testUpdateRewardGasAmount_NotOwner() (gas: 11006) -ReservoirPriceOracleTest:testUpdateTwapPeriod(uint256) (runs: 256, μ: 21787, ~: 21892) -ReservoirPriceOracleTest:testUpdateTwapPeriod_InvalidTwapPeriod(uint256) (runs: 256, μ: 17918, ~: 18208) -ReservoirPriceOracleTest:testWritePriceCache(uint256) (runs: 256, μ: 29981, ~: 29777) +ReservoirPriceOracleTest:testUpdateTwapPeriod(uint256) (runs: 256, μ: 21823, ~: 21892) +ReservoirPriceOracleTest:testUpdateTwapPeriod_InvalidTwapPeriod(uint256) (runs: 256, μ: 17891, ~: 18147) +ReservoirPriceOracleTest:testWritePriceCache(uint256) (runs: 256, μ: 30063, ~: 29821) SamplesTest:testAccumulator() (gas: 3959) SamplesTest:testAccumulator_BadVariableRequest() (gas: 3523) SamplesTest:testInstant() (gas: 3909) diff --git a/src/ReservoirPriceOracle.sol b/src/ReservoirPriceOracle.sol index e3bb6f9..c1c05e0 100644 --- a/src/ReservoirPriceOracle.sol +++ b/src/ReservoirPriceOracle.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import { IERC20 } from "forge-std/interfaces/IERC20.sol"; +import { IERC4626 } from "forge-std/interfaces/IERC4626.sol"; import { OracleErrors } from "src/libraries/OracleErrors.sol"; import { @@ -34,6 +35,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. event DesignatePair(address token0, address token1, ReservoirPair pair); event FallbackOracleSet(address fallbackOracle); event PriceDeviationThreshold(uint256 newThreshold); + event ResolvedVaultSet(address vault, address asset); event RewardGasAmount(uint256 newAmount); event Route(address token0, address token1, address[] route); event Price(address token0, address token1, uint256 price); @@ -68,6 +70,9 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. /// @notice Designated pairs to serve as price feed for a certain token0 and token1 mapping(address token0 => mapping(address token1 => ReservoirPair pair)) public pairs; + /// @notice ERC4626 vaults resolved using internal pricing (`convertToAssets`). + mapping(address vault => address asset) public resolvedVaults; + /////////////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTOR, FALLBACKS // /////////////////////////////////////////////////////////////////////////////////////////////// @@ -381,7 +386,7 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. } } - function _getQuotes(uint256 aAmount, address aBase, address aQuote, bool isGetQuotes) + function _getQuotes(uint256 aAmount, address aBase, address aQuote, bool aIsGetQuotes) internal view returns (uint256 rBidOut, uint256 rAskOut) @@ -395,11 +400,14 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. // route does not exist on our oracle, attempt querying the fallback if (lRoute.length == 0) { - if (fallbackOracle == address(0)) revert OracleErrors.NoPath(); + address lBaseAsset = resolvedVaults[aBase]; + + if (lBaseAsset != address(0)) { + uint256 lResolvedAmountIn = IERC4626(aBase).convertToAssets(aAmount); + return _getQuotes(lResolvedAmountIn, lBaseAsset, aQuote, aIsGetQuotes); + } - // We do not catch errors here so the fallback oracle will revert if it doesn't support the query. - if (isGetQuotes) (rBidOut, rAskOut) = IPriceOracle(fallbackOracle).getQuotes(aAmount, aBase, aQuote); - else rBidOut = rAskOut = IPriceOracle(fallbackOracle).getQuote(aAmount, aBase, aQuote); + return _useFallbackOracle(aAmount, aBase, aQuote, aIsGetQuotes); } else if (lRoute.length == 2) { if (lPrice == 0) revert OracleErrors.PriceZero(); rBidOut = rAskOut = _calcAmtOut(aAmount, lPrice, lDecimalDiff, lRoute[0] != aBase); @@ -453,6 +461,14 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. } } + function _useFallbackOracle(uint256 aAmount, address aBase, address aQuote, bool aIsGetQuotes) internal view returns (uint256 rBidOut, uint256 rAskOut) { + if (fallbackOracle == address(0)) revert OracleErrors.NoPath(); + + // We do not catch errors here so the fallback oracle will revert if it doesn't support the query. + if (aIsGetQuotes) (rBidOut, rAskOut) = IPriceOracle(fallbackOracle).getQuotes(aAmount, aBase, aQuote); + else rBidOut = rAskOut = IPriceOracle(fallbackOracle).getQuote(aAmount, aBase, aQuote); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // ADMIN FUNCTIONS // /////////////////////////////////////////////////////////////////////////////////////////////// @@ -505,6 +521,12 @@ contract ReservoirPriceOracle is IPriceOracle, IReservoirPriceOracle, Owned(msg. emit SetPriceType(aType); } + function setResolvedVault(address aVault, bool aSet) external onlyOwner { + address lAsset = aSet ? IERC4626(aVault).asset() : address(0); + resolvedVaults[aVault] = lAsset; + emit ResolvedVaultSet(aVault, lAsset); + } + /// @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/test/mock/MockFallbackOracle.sol b/test/mock/MockFallbackOracle.sol index e75bdf3..3138cd7 100644 --- a/test/mock/MockFallbackOracle.sol +++ b/test/mock/MockFallbackOracle.sol @@ -4,17 +4,17 @@ pragma solidity ^0.8.0; import { IPriceOracle } from "src/interfaces/IPriceOracle.sol"; contract MockFallbackOracle is IPriceOracle { - function name() external view returns (string memory) { + function name() external pure returns (string memory) { return "MOCK"; } - function getQuote(uint256 amount, address, address) external view returns (uint256 out) { + function getQuote(uint256 amount, address, address) external pure returns (uint256 out) { out = amount; } function getQuotes(uint256 amount, address, address) external - view + pure returns (uint256 bidOut, uint256 askOut) { (bidOut, askOut) = (amount, amount); diff --git a/test/mock/StubERC4626.sol b/test/mock/StubERC4626.sol new file mode 100644 index 0000000..3a200b4 --- /dev/null +++ b/test/mock/StubERC4626.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +contract StubERC4626 { + address public asset; + uint256 private rate; + string revertMsg = "oops"; + bool doRevert; + + constructor(address _asset, uint256 _rate) { + asset = _asset; + rate = _rate; + } + + function setRevert(bool _doRevert) external { + doRevert = _doRevert; + } + + function setRate(uint256 _rate) external { + rate = _rate; + } + + function convertToAssets(uint256 shares) external view returns (uint256) { + if (doRevert) revert(revertMsg); + return shares * rate / 1e18; + } + + function convertToShares(uint256 assets) external view returns (uint256) { + if (doRevert) revert(revertMsg); + return assets * 1e18 / rate; + } +} diff --git a/test/unit/ReservoirPriceOracle.t.sol b/test/unit/ReservoirPriceOracle.t.sol index 4e20f79..fbfd4e3 100644 --- a/test/unit/ReservoirPriceOracle.t.sol +++ b/test/unit/ReservoirPriceOracle.t.sol @@ -20,6 +20,7 @@ import { Bytes32Lib } from "amm-core/libraries/Bytes32.sol"; import { EnumerableSetLib } from "lib/solady/src/utils/EnumerableSetLib.sol"; import { Constants } from "src/libraries/Constants.sol"; import { MockFallbackOracle } from "test/mock/MockFallbackOracle.sol"; +import { StubERC4626 } from "test/mock/StubERC4626.sol"; contract ReservoirPriceOracleTest is BaseTest { using Utils for *; @@ -486,6 +487,23 @@ contract ReservoirPriceOracleTest is BaseTest { assertGt(lAskOut, 0); } + function testGetQuote_BaseIsVault(uint256 aRate) external { + // assume + uint256 lRate = bound(aRate, 1, 1e36); + + // arrange + uint256 lAmtIn = 5e18; + StubERC4626 lVault = new StubERC4626(address(_tokenA), lRate); + _oracle.setResolvedVault(address(lVault), true); + _writePriceCache(address(_tokenA), address(_tokenB), 1e18); + + // act + uint256 lAmtOut = _oracle.getQuote(lAmtIn, address(lVault), address(_tokenB)); + + // assert + assertEq(lAmtOut / 1e12, lAmtIn * lRate / 1e18); + } + function testUpdatePriceDeviationThreshold(uint256 aNewThreshold) external { // assume uint64 lNewThreshold = uint64(bound(aNewThreshold, 0, 0.1e18));