From de00f699561c36eda747f8cf469d306377faf662 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 9 Sep 2024 17:21:00 -0400 Subject: [PATCH 01/15] remove counting tick crossing --- src/interfaces/IQuoter.sol | 22 +--- src/lens/Quoter.sol | 107 ++++++------------ src/libraries/PoolTicksCounter.sol | 115 ------------------- test/Quoter.t.sol | 170 +++++------------------------ 4 files changed, 64 insertions(+), 350 deletions(-) delete mode 100644 src/libraries/PoolTicksCounter.sol diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index 8f113f4c..befc166a 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -7,7 +7,7 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @title Quoter Interface /// @notice Supports quoting the delta amounts for exact input or exact output swaps. -/// @notice For each pool also tells you the number of initialized ticks loaded and the sqrt price of the pool after the swap. +/// @notice For each pool also tells you the sqrt price of the pool after the swap. /// @dev These functions are not marked view because they rely on calling non-view functions and reverting /// to compute the result. They are also not gas efficient and should not be called on-chain. interface IQuoter { @@ -46,10 +46,9 @@ interface IQuoter { /// hookData arbitrary hookData to pass into the associated hooks /// @return deltaAmounts Delta amounts resulted from the swap /// @return sqrtPriceX96After The sqrt price of the pool after the swap - /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After); /// @notice Returns the delta amounts along the swap path for a given exact input swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -58,14 +57,9 @@ interface IQuoter { /// exactAmount The desired input amount /// @return deltaAmounts Delta amounts along the path resulted from the swap /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path - /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path function quoteExactInput(QuoteExactParams memory params) external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ); + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList); /// @notice Returns the delta amounts for a given exact output swap of a single pool /// @param params The params for the quote, encoded as `QuoteExactSingleParams` @@ -76,10 +70,9 @@ interface IQuoter { /// hookData arbitrary hookData to pass into the associated hooks /// @return deltaAmounts Delta amounts resulted from the swap /// @return sqrtPriceX96After The sqrt price of the pool after the swap - /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After); /// @notice Returns the delta amounts along the swap path for a given exact output swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -88,12 +81,7 @@ interface IQuoter { /// exactAmount The desired output amount /// @return deltaAmounts Delta amounts along the path resulted from the swap /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path - /// @return initializedTicksLoadedList List of the initialized ticks that the swap loaded for each pool in the path function quoteExactOutput(QuoteExactParams memory params) external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ); + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList); } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 4f974194..4480ed64 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -8,7 +8,6 @@ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; -import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKey, PathKeyLibrary} from "../libraries/PathKey.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {SafeCallback} from "../base/SafeCallback.sol"; @@ -21,15 +20,14 @@ contract Quoter is IQuoter, SafeCallback { /// @dev cache used to check a safety condition in exact output swaps. uint128 private amountOutCached; - /// @dev min valid reason is 6-words long (192 bytes) + /// @dev min valid reason is 5-words long (160 bytes) /// @dev int128[2] includes 32 bytes for offset, 32 bytes for length, and 32 bytes for each element - /// @dev Plus sqrtPriceX96After padded to 32 bytes and initializedTicksLoaded padded to 32 bytes - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 192; + /// @dev Plus sqrtPriceX96After padded to 32 bytes + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 160; struct QuoteResult { int128[] deltaAmounts; uint160[] sqrtPriceX96AfterList; - uint32[] initializedTicksLoadedList; } struct QuoteCache { @@ -37,8 +35,6 @@ contract Quoter is IQuoter, SafeCallback { uint128 prevAmount; int128 deltaIn; int128 deltaOut; - int24 tickBefore; - int24 tickAfter; Currency prevCurrency; uint160 sqrtPriceX96After; } @@ -54,7 +50,7 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) public - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) { try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { @@ -65,11 +61,7 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInput(QuoteExactParams memory params) external - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) { try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { @@ -80,7 +72,7 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) public - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) { try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { @@ -92,11 +84,7 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) public - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) { try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { @@ -126,44 +114,35 @@ contract Quoter is IQuoter, SafeCallback { function _handleRevertSingle(bytes memory reason) private pure - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) { reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96After, initializedTicksLoaded) = abi.decode(reason, (int128[], uint160, uint32)); + (deltaAmounts, sqrtPriceX96After) = abi.decode(reason, (int128[], uint160)); } - /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, sqrtPriceX96After, and initializedTicksLoaded + /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, and sqrtPriceX96After function _handleRevert(bytes memory reason) private pure - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) { reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList) = - abi.decode(reason, (int128[], uint160[], uint32[])); + (deltaAmounts, sqrtPriceX96AfterList) = abi.decode(reason, (int128[], uint160[])); } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result function _quoteExactInput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - QuoteResult memory result = QuoteResult({ - deltaAmounts: new int128[](pathLength + 1), - sqrtPriceX96AfterList: new uint160[](pathLength), - initializedTicksLoadedList: new uint32[](pathLength) - }); + QuoteResult memory result = + QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); QuoteCache memory cache; for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : cache.prevCurrency); - (, cache.tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = _swap( + (cache.curDeltas, cache.sqrtPriceX96After) = _swap( poolKey, zeroForOne, -int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), @@ -180,21 +159,16 @@ contract Quoter is IQuoter, SafeCallback { cache.prevAmount = zeroForOne ? uint128(cache.curDeltas.amount1()) : uint128(cache.curDeltas.amount0()); cache.prevCurrency = params.path[i].intermediateCurrency; result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; - result.initializedTicksLoadedList[i] = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, cache.tickBefore, cache.tickAfter); } - bytes memory r = - abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); - assembly { - revert(add(0x20, r), mload(r)) + bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); + assembly ("memory-safe") { + revert(add(0x20, encodedResult), mload(encodedResult)) } } /// @dev quote an ExactInput swap on a pool, then revert with the result function _quoteExactInputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { - (, int24 tickBefore,,) = poolManager.getSlot0(params.poolKey.toId()); - - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), @@ -207,11 +181,9 @@ contract Quoter is IQuoter, SafeCallback { deltaAmounts[0] = -deltas.amount0(); deltaAmounts[1] = -deltas.amount1(); - uint32 initializedTicksLoaded = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, params.poolKey, tickBefore, tickAfter); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); - assembly { - revert(add(0x20, result), mload(result)) + bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); + assembly ("memory-safe") { + revert(add(0x20, encodedResult), mload(encodedResult)) } } @@ -219,11 +191,8 @@ contract Quoter is IQuoter, SafeCallback { function _quoteExactOutput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - QuoteResult memory result = QuoteResult({ - deltaAmounts: new int128[](pathLength + 1), - sqrtPriceX96AfterList: new uint160[](pathLength), - initializedTicksLoadedList: new uint32[](pathLength) - }); + QuoteResult memory result = + QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); QuoteCache memory cache; uint128 curAmountOut; @@ -235,9 +204,7 @@ contract Quoter is IQuoter, SafeCallback { params.path[i - 1], i == pathLength ? params.exactCurrency : cache.prevCurrency ); - (, cache.tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - - (cache.curDeltas, cache.sqrtPriceX96After, cache.tickAfter) = + (cache.curDeltas, cache.sqrtPriceX96After) = _swap(poolKey, !oneForZero, int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); // always clear because sqrtPriceLimitX96 is set to 0 always @@ -251,13 +218,10 @@ contract Quoter is IQuoter, SafeCallback { cache.prevAmount = !oneForZero ? uint128(-cache.curDeltas.amount0()) : uint128(-cache.curDeltas.amount1()); cache.prevCurrency = params.path[i - 1].intermediateCurrency; result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; - result.initializedTicksLoadedList[i - 1] = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, cache.tickBefore, cache.tickAfter); } - bytes memory r = - abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList, result.initializedTicksLoadedList); - assembly { - revert(add(0x20, r), mload(r)) + bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); + assembly ("memory-safe") { + revert(add(0x20, encodedResult), mload(encodedResult)) } } @@ -266,8 +230,7 @@ contract Quoter is IQuoter, SafeCallback { // if no price limit has been specified, cache the output amount for comparison inside the _swap function if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - (, int24 tickBefore,,) = poolManager.getSlot0(params.poolKey.toId()); - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( params.poolKey, params.zeroForOne, int256(uint256(params.exactAmount)), @@ -281,11 +244,9 @@ contract Quoter is IQuoter, SafeCallback { deltaAmounts[0] = -deltas.amount0(); deltaAmounts[1] = -deltas.amount1(); - uint32 initializedTicksLoaded = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, params.poolKey, tickBefore, tickAfter); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); - assembly { - revert(add(0x20, result), mload(result)) + bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); + assembly ("memory-safe") { + revert(add(0x20, encodedResult), mload(encodedResult)) } } @@ -297,7 +258,7 @@ contract Quoter is IQuoter, SafeCallback { int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata hookData - ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { + ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After) { deltas = poolManager.swap( poolKey, IPoolManager.SwapParams({ @@ -311,7 +272,7 @@ contract Quoter is IQuoter, SafeCallback { if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? deltas.amount1() : deltas.amount0())) { revert InsufficientAmountOut(); } - (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(poolKey.toId()); + (sqrtPriceX96After,,,) = poolManager.getSlot0(poolKey.toId()); } /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction diff --git a/src/libraries/PoolTicksCounter.sol b/src/libraries/PoolTicksCounter.sol deleted file mode 100644 index c101a22f..00000000 --- a/src/libraries/PoolTicksCounter.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; - -/// @title Pool Ticks Counter -/// @notice Functions for counting the number of initialized ticks between two ticks -library PoolTicksCounter { - using PoolIdLibrary for PoolKey; - using StateLibrary for IPoolManager; - - struct TickCache { - int16 wordPosLower; - int16 wordPosHigher; - uint8 bitPosLower; - uint8 bitPosHigher; - bool tickBeforeInitialized; - bool tickAfterInitialized; - } - - /// @notice Count the number of initialized ticks between two ticks - /// @dev This function counts the number of initialized ticks that would incur a gas cost between tickBefore and tickAfter. - /// When tickBefore and/or tickAfter themselves are initialized, the logic over whether we should count them depends on the - /// direction of the swap. If we are swapping upwards (tickAfter > tickBefore) we don't want to count tickBefore but we do - /// want to count tickAfter. The opposite is true if we are swapping downwards. - /// @param self the IPoolManager - /// @param key the PoolKey of the pool - /// @param tickBefore the tick before the swap - /// @param tickAfter the tick after the swap - /// @return initializedTicksLoaded the number of initialized ticks loaded - function countInitializedTicksLoaded(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) - internal - view - returns (uint32 initializedTicksLoaded) - { - TickCache memory cache; - - { - // Get the key and offset in the tick bitmap of the active tick before and after the swap. - int16 wordPos = int16((tickBefore / key.tickSpacing) >> 8); - uint8 bitPos = uint8(uint24((tickBefore / key.tickSpacing) % 256)); - - int16 wordPosAfter = int16((tickAfter / key.tickSpacing) >> 8); - uint8 bitPosAfter = uint8(uint24((tickAfter / key.tickSpacing) % 256)); - - // In the case where tickAfter is initialized, we only want to count it if we are swapping downwards. - // If the initializable tick after the swap is initialized, our original tickAfter is a - // multiple of tick spacing, and we are swapping downwards we know that tickAfter is initialized - // and we shouldn't count it. - uint256 bmAfter = self.getTickBitmap(key.toId(), wordPosAfter); - cache.tickAfterInitialized = - ((bmAfter & (1 << bitPosAfter)) > 0) && ((tickAfter % key.tickSpacing) == 0) && (tickBefore > tickAfter); - - // In the case where tickBefore is initialized, we only want to count it if we are swapping upwards. - // Use the same logic as above to decide whether we should count tickBefore or not. - uint256 bmBefore = self.getTickBitmap(key.toId(), wordPos); - cache.tickBeforeInitialized = - ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); - - if (wordPos < wordPosAfter || (wordPos == wordPosAfter && bitPos <= bitPosAfter)) { - cache.wordPosLower = wordPos; - cache.bitPosLower = bitPos; - cache.wordPosHigher = wordPosAfter; - cache.bitPosHigher = bitPosAfter; - } else { - cache.wordPosLower = wordPosAfter; - cache.bitPosLower = bitPosAfter; - cache.wordPosHigher = wordPos; - cache.bitPosHigher = bitPos; - } - } - - // Count the number of initialized ticks crossed by iterating through the tick bitmap. - // Our first mask should include the lower tick and everything to its left. - uint256 mask = type(uint256).max << cache.bitPosLower; - while (cache.wordPosLower <= cache.wordPosHigher) { - // If we're on the final tick bitmap page, ensure we only count up to our - // ending tick. - if (cache.wordPosLower == cache.wordPosHigher) { - mask = mask & (type(uint256).max >> (255 - cache.bitPosHigher)); - } - - uint256 bmLower = self.getTickBitmap(key.toId(), cache.wordPosLower); - uint256 masked = bmLower & mask; - initializedTicksLoaded += countOneBits(masked); - cache.wordPosLower++; - // Reset our mask so we consider all bits on the next iteration. - mask = type(uint256).max; - } - - if (cache.tickAfterInitialized) { - initializedTicksLoaded -= 1; - } - - if (cache.tickBeforeInitialized) { - initializedTicksLoaded -= 1; - } - - return initializedTicksLoaded; - } - - /// @notice Count the number of set bits in a uint256 - /// @param x the uint256 to count the bits of - function countOneBits(uint256 x) private pure returns (uint16) { - uint16 bits = 0; - while (x != 0) { - bits++; - x &= (x - 1); - } - return bits; - } -} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index b01d9c88..074ea99d 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -84,8 +84,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, @@ -97,7 +96,6 @@ contract QuoterTest is Test, Deployers { assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksLoaded, 2); } function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { @@ -105,8 +103,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, @@ -118,7 +115,6 @@ contract QuoterTest is Test, Deployers { assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksLoaded, 2); } // nested self-call into unlockCallback reverts @@ -133,15 +129,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 9871); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactInput_0to2_2TicksLoaded_initialiedAfter() public { @@ -152,15 +143,10 @@ contract QuoterTest is Test, Deployers { // -120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 6143); assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactInput_0to2_1TickLoaded() public { @@ -171,15 +157,10 @@ contract QuoterTest is Test, Deployers { // -60 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 3971); assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { @@ -187,15 +168,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); - assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { @@ -204,15 +180,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { @@ -220,15 +191,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactInput_2to0_2TicksLoaded_initialiedAfter() public { @@ -239,15 +205,10 @@ contract QuoterTest is Test, Deployers { // 120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 6190); assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { @@ -257,15 +218,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 198); assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); - assertEq(initializedTicksLoadedList[0], 0); } // 2->0 starting not initialized @@ -274,15 +230,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 101); assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); - assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactInput_2to1() public { @@ -290,14 +241,9 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); - assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactInput_0to2to1() public { @@ -306,22 +252,15 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[2], 9745); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); - assertEq(initializedTicksLoadedList[0], 2); - assertEq(initializedTicksLoadedList[1], 0); } function testQuoter_quoteExactOutputSingle_0to1() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactOutputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, @@ -333,12 +272,10 @@ contract QuoterTest is Test, Deployers { assertEq(deltaAmounts[0], 9981); assertEq(sqrtPriceX96After, SQRT_PRICE_100_102); - assertEq(initializedTicksLoaded, 0); } function testQuoter_quoteExactOutputSingle_1to0() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter - .quoteExactOutputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, @@ -350,7 +287,6 @@ contract QuoterTest is Test, Deployers { assertEq(deltaAmounts[1], 9981); assertEq(sqrtPriceX96After, SQRT_PRICE_102_100); - assertEq(initializedTicksLoaded, 0); } function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { @@ -358,15 +294,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 15273); assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactOutput_0to2_1TickLoaded_initialiedAfter() public { @@ -375,15 +306,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 6200); assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { @@ -392,15 +318,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 4029); assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { @@ -411,15 +332,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 102); assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { @@ -428,15 +344,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 12); assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); - assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { @@ -444,16 +355,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 15273); assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initialiedAfter() public { @@ -462,16 +367,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 6283); assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 2); } function testQuoter_quoteExactOutput_2to0_1TickLoaded() public { @@ -479,16 +378,10 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 6055); assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 1); } function testQuoter_quoteExactOutput_2to1() public { @@ -497,16 +390,10 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 10000); assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); - assertEq(initializedTicksLoadedList.length, 1); - assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactOutput_0to2to1() public { @@ -516,20 +403,13 @@ contract QuoterTest is Test, Deployers { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 10000); assertEq(deltaAmounts[1], 0); assertEq(deltaAmounts[2], -9745); assertEq(sqrtPriceX96AfterList[0], 78461888503179331029803316753); assertEq(sqrtPriceX96AfterList[1], 80007838904387594703933785072); - assertEq(initializedTicksLoadedList.length, 2); - assertEq(initializedTicksLoadedList[0], 2); - assertEq(initializedTicksLoadedList[1], 0); } function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) From fdb6244dd6971a4294beb6dbdc6a179a7b4ebd7e Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 9 Sep 2024 18:22:00 -0400 Subject: [PATCH 02/15] add gas estimate return --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/interfaces/IQuoter.sol | 8 +-- src/lens/Quoter.sol | 34 ++++++++----- test/Quoter.t.sol | 50 +++++++++---------- 16 files changed, 64 insertions(+), 54 deletions(-) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 9d62d41d..491bc614 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -149027 \ No newline at end of file +149138 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 31001264..ceff502d 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -154511 \ No newline at end of file +154622 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 5a1e35c6..05054a55 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -83641 \ No newline at end of file +83747 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index b9c835ee..d1531011 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -88449 \ No newline at end of file +88555 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 39da693d..5a10b4ec 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -128559 \ No newline at end of file +128751 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index efb45ba3..43b84911 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -153480 \ No newline at end of file +153672 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index 5281d2e2..d7e7dc63 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -87503 \ No newline at end of file +87695 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index d8a20ee9..32647ca3 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -212546 \ No newline at end of file +212738 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 43a31e7c..6d67520c 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -148306 \ No newline at end of file +148498 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index 8ba91a98..d8eadd5e 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -178443 \ No newline at end of file +178635 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 31ecb056..471d7f5c 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -148374 \ No newline at end of file +148566 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index d8993e48..8c9d054e 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -125073 \ No newline at end of file +125265 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 238bcf31..7f618829 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -252910 \ No newline at end of file +253102 \ No newline at end of file diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index befc166a..c13676a3 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -48,7 +48,7 @@ interface IQuoter { /// @return sqrtPriceX96After The sqrt price of the pool after the swap function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact input swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -59,7 +59,7 @@ interface IQuoter { /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path function quoteExactInput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList); + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); /// @notice Returns the delta amounts for a given exact output swap of a single pool /// @param params The params for the quote, encoded as `QuoteExactSingleParams` @@ -72,7 +72,7 @@ interface IQuoter { /// @return sqrtPriceX96After The sqrt price of the pool after the swap function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact output swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -83,5 +83,5 @@ interface IQuoter { /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path function quoteExactOutput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList); + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 4480ed64..0967e62f 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -50,45 +50,53 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) public - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) { + uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { - return _handleRevertSingle(reason); + gasEstimate = gasBefore - gasleft(); + return _handleRevertSingle(reason, gasEstimate); } } /// @inheritdoc IQuoter function quoteExactInput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) { + uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { - return _handleRevert(reason); + gasEstimate = gasBefore - gasleft(); + return _handleRevert(reason, gasEstimate); } } /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) public - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) { + uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { + gasEstimate = gasBefore - gasleft(); if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - return _handleRevertSingle(reason); + return _handleRevertSingle(reason, gasEstimate); } } /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) public - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) { + uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { - return _handleRevert(reason); + gasEstimate = gasBefore - gasleft(); + return _handleRevert(reason, gasEstimate); } } @@ -111,23 +119,25 @@ contract Quoter is IQuoter, SafeCallback { } /// @dev parse revert bytes from a single-pool quote - function _handleRevertSingle(bytes memory reason) + function _handleRevertSingle(bytes memory reason, uint256 gasEstimate) private pure - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256) { reason = validateRevertReason(reason); (deltaAmounts, sqrtPriceX96After) = abi.decode(reason, (int128[], uint160)); + return (deltaAmounts, sqrtPriceX96After, gasEstimate); } /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, and sqrtPriceX96After - function _handleRevert(bytes memory reason) + function _handleRevert(bytes memory reason, uint256 gasEstimate) private pure - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) + returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256) { reason = validateRevertReason(reason); (deltaAmounts, sqrtPriceX96AfterList) = abi.decode(reason, (int128[], uint160[])); + return (deltaAmounts, sqrtPriceX96AfterList, gasEstimate); } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 86b57181..552295d8 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -85,7 +85,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, @@ -105,7 +105,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, @@ -132,7 +132,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 9871); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); @@ -146,7 +146,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 6143); assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); @@ -160,7 +160,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -60 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_1TickLoaded"); @@ -173,7 +173,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); @@ -185,7 +185,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); @@ -196,7 +196,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); @@ -210,7 +210,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // 120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_initializedAfter"); @@ -225,7 +225,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_startingInitialized"); @@ -239,7 +239,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 101); assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); @@ -250,7 +250,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); } @@ -261,7 +261,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactInput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_twoHops"); @@ -271,7 +271,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { } function testQuoter_quoteExactOutputSingle_0to1() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactOutputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, @@ -287,7 +287,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { } function testQuoter_quoteExactOutputSingle_1to0() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After) = quoter.quoteExactOutputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, @@ -307,7 +307,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_2TicksLoaded"); @@ -321,7 +321,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); assertEq(deltaAmounts[0], 6200); @@ -334,7 +334,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); assertEq(deltaAmounts[0], 4029); @@ -349,7 +349,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_startingInitialized"); assertEq(deltaAmounts[0], 102); @@ -362,7 +362,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 12); assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); @@ -373,7 +373,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 15273); assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); @@ -385,7 +385,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 6283); assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); @@ -396,7 +396,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 6055); assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); @@ -408,7 +408,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); assertEq(deltaAmounts[0], 10000); assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); @@ -421,7 +421,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList) = quoter.quoteExactOutput(params); + (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_twoHops"); From 40935d7799e0812532540facc65cc0dc11dbc2cf Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 10 Sep 2024 16:45:16 -0400 Subject: [PATCH 03/15] refactor into revert library --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/interfaces/IQuoter.sol | 1 - src/lens/Quoter.sol | 86 +++++-------------- src/libraries/RevertBytes.sol | 46 ++++++++++ 16 files changed, 81 insertions(+), 78 deletions(-) create mode 100644 src/libraries/RevertBytes.sol diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 491bc614..4991aadb 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -149138 \ No newline at end of file +149088 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index ceff502d..e5627475 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -154622 \ No newline at end of file +154572 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 05054a55..06e1cd8c 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -83747 \ No newline at end of file +83716 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index d1531011..d6dbd4be 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -88555 \ No newline at end of file +88524 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 5a10b4ec..7ff1a5b2 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -128751 \ No newline at end of file +128698 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 43b84911..1bbecf1b 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -153672 \ No newline at end of file +153619 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index d7e7dc63..b8ffcb54 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -87695 \ No newline at end of file +87642 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 32647ca3..96514f51 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -212738 \ No newline at end of file +212685 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 6d67520c..13909da9 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -148498 \ No newline at end of file +148445 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index d8eadd5e..d8150087 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -178635 \ No newline at end of file +178582 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 471d7f5c..98d326a2 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -148566 \ No newline at end of file +148513 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 8c9d054e..e14e526a 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -125265 \ No newline at end of file +125212 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 7f618829..7920bbac 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -253102 \ No newline at end of file +253049 \ No newline at end of file diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index c13676a3..01cd6eb7 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -16,7 +16,6 @@ interface IQuoter { error InsufficientAmountOut(); error LockFailure(); error NotSelf(); - error UnexpectedRevertBytes(bytes revertData); struct PoolDeltas { int128 currency0Delta; diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 0967e62f..f36b6058 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -7,24 +7,21 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PathKey, PathKeyLibrary} from "../libraries/PathKey.sol"; -import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; +import {RevertBytes} from "../libraries/RevertBytes.sol"; import {SafeCallback} from "../base/SafeCallback.sol"; contract Quoter is IQuoter, SafeCallback { using PoolIdLibrary for PoolKey; using PathKeyLibrary for PathKey; using StateLibrary for IPoolManager; + using RevertBytes for bytes; /// @dev cache used to check a safety condition in exact output swaps. uint128 private amountOutCached; - /// @dev min valid reason is 5-words long (160 bytes) - /// @dev int128[2] includes 32 bytes for offset, 32 bytes for length, and 32 bytes for each element - /// @dev Plus sqrtPriceX96After padded to 32 bytes - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 160; - struct QuoteResult { int128[] deltaAmounts; uint160[] sqrtPriceX96AfterList; @@ -39,7 +36,8 @@ contract Quoter is IQuoter, SafeCallback { uint160 sqrtPriceX96After; } - /// @dev Only this address may call this function + /// @dev Only this address may call this function. Used to mimic internal functions, using an + /// external call to catch and parse revert reasons modifier selfOnly() { if (msg.sender != address(this)) revert NotSelf(); _; @@ -49,14 +47,14 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) - public + external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - return _handleRevertSingle(reason, gasEstimate); + (deltaAmounts, sqrtPriceX96After) = reason.parseReturnDataSingle(); } } @@ -69,13 +67,13 @@ contract Quoter is IQuoter, SafeCallback { try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - return _handleRevert(reason, gasEstimate); + (deltaAmounts, sqrtPriceX96AfterList) = reason.parseReturnData(); } } /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) - public + external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) { uint256 gasBefore = gasleft(); @@ -83,65 +81,33 @@ contract Quoter is IQuoter, SafeCallback { catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - return _handleRevertSingle(reason, gasEstimate); + (deltaAmounts, sqrtPriceX96After) = reason.parseReturnDataSingle(); } } /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) - public + external returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - return _handleRevert(reason, gasEstimate); + (deltaAmounts, sqrtPriceX96AfterList) = reason.parseReturnData(); } } function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + // Call this contract with the data in question. Each quote path (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); - // if the call failed, bubble up the reason - assembly ("memory-safe") { - revert(add(returnData, 32), mload(returnData)) - } - } - - /// @dev check revert bytes and pass through if considered valid; otherwise revert with different message - function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { - if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { - revert UnexpectedRevertBytes(reason); - } - return reason; - } - - /// @dev parse revert bytes from a single-pool quote - function _handleRevertSingle(bytes memory reason, uint256 gasEstimate) - private - pure - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256) - { - reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96After) = abi.decode(reason, (int128[], uint160)); - return (deltaAmounts, sqrtPriceX96After, gasEstimate); - } - - /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, and sqrtPriceX96After - function _handleRevert(bytes memory reason, uint256 gasEstimate) - private - pure - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256) - { - reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96AfterList) = abi.decode(reason, (int128[], uint160[])); - return (deltaAmounts, sqrtPriceX96AfterList, gasEstimate); + returnData.revertWith(); } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result - function _quoteExactInput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { + function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = @@ -171,13 +137,11 @@ contract Quoter is IQuoter, SafeCallback { result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); - assembly ("memory-safe") { - revert(add(0x20, encodedResult), mload(encodedResult)) - } + encodedResult.revertWith(); } /// @dev quote an ExactInput swap on a pool, then revert with the result - function _quoteExactInputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { + function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( params.poolKey, params.zeroForOne, @@ -192,13 +156,11 @@ contract Quoter is IQuoter, SafeCallback { deltaAmounts[1] = -deltas.amount1(); bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); - assembly ("memory-safe") { - revert(add(0x20, encodedResult), mload(encodedResult)) - } + encodedResult.revertWith(); } /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result - function _quoteExactOutput(QuoteExactParams calldata params) public selfOnly returns (bytes memory) { + function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = @@ -230,13 +192,11 @@ contract Quoter is IQuoter, SafeCallback { result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); - assembly ("memory-safe") { - revert(add(0x20, encodedResult), mload(encodedResult)) - } + encodedResult.revertWith(); } /// @dev quote an ExactOutput swap on a pool, then revert with the result - function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) public selfOnly returns (bytes memory) { + function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { // if no price limit has been specified, cache the output amount for comparison inside the _swap function if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; @@ -255,9 +215,7 @@ contract Quoter is IQuoter, SafeCallback { deltaAmounts[1] = -deltas.amount1(); bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); - assembly ("memory-safe") { - revert(add(0x20, encodedResult), mload(encodedResult)) - } + encodedResult.revertWith(); } /// @dev Execute a swap and return the amounts delta, as well as relevant pool state diff --git a/src/libraries/RevertBytes.sol b/src/libraries/RevertBytes.sol new file mode 100644 index 00000000..e045f57e --- /dev/null +++ b/src/libraries/RevertBytes.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library RevertBytes { + using RevertBytes for bytes; + + /// @notice error thrown when invalid revert bytes are thrown by the quote + error UnexpectedRevertBytes(bytes revertData); + + /// @dev min valid reason is 5-words long (160 bytes) + /// @dev int128[2] includes 32 bytes for offset, 32 bytes for length, and 32 bytes for each element + /// @dev Plus sqrtPriceX96After padded to 32 bytes + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 160; + + /// @notice reverts, where the revert data is the provided bytes + /// @dev called when quoting, at the end of simulating a swap, to revert with the swap information as the revert reason + function revertWith(bytes memory revertData) internal pure { + // mload(revertData): the length of the revert data + // add(revertData, 0x20): a pointer to the start of the revert data + assembly ("memory-safe") { + revert(add(revertData, 0x20), mload(revertData)) + } + } + + /// @notice check revert reasons are of the expected length; otherwise revert with different message + /// @dev called after a swap simulation reverts, to check if the revert was valid encoded quote information, or an internal issue + function validateRevertReason(bytes memory reason) internal pure { + if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { + revert UnexpectedRevertBytes(reason); + } + } + + /// @notice validates a received revert reason from a single-hop swap. + /// Then, if valid, decodes it into the information generated by a quote + function parseReturnDataSingle(bytes memory reason) internal pure returns (int128[] memory, uint160) { + reason.validateRevertReason(); + return abi.decode(reason, (int128[], uint160)); + } + + /// @notice validates a received revert reason from a multi-hop swap. + /// Then, if valid, decodes it into the information generated by a quote + function parseReturnData(bytes memory reason) internal pure returns (int128[] memory, uint160[] memory) { + reason.validateRevertReason(); + return abi.decode(reason, (int128[], uint160[])); + } +} From bb2b9839c2ae7ebdbce7a3c4017ab96fc006c555 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 10 Sep 2024 17:34:54 -0400 Subject: [PATCH 04/15] remove cache struct --- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- foundry.toml | 2 +- src/lens/Quoter.sol | 95 ++++++++----------- 11 files changed, 52 insertions(+), 63 deletions(-) diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 7ff1a5b2..2e4f8e31 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -128698 \ No newline at end of file +128285 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 1bbecf1b..2f3c1bba 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -153619 \ No newline at end of file +153188 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index b8ffcb54..d71ed55c 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -87642 \ No newline at end of file +87211 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 96514f51..433912de 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -212685 \ No newline at end of file +212035 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 13909da9..7869622b 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -148445 \ No newline at end of file +148025 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index d8150087..fbe64408 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -178582 \ No newline at end of file +178162 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 98d326a2..03cb97d1 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -148513 \ No newline at end of file +148093 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index e14e526a..76815c76 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -125212 \ No newline at end of file +124792 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 7920bbac..97851c9b 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -253049 \ No newline at end of file +252373 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 64572ba0..381668e8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,7 +1,7 @@ [profile.default] out = 'foundry-out' solc_version = '0.8.26' -optimizer_runs = 1_000_000 +optimizer_runs = 44444444 via_ir = true ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index f36b6058..ae98a9f4 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -27,15 +27,6 @@ contract Quoter is IQuoter, SafeCallback { uint160[] sqrtPriceX96AfterList; } - struct QuoteCache { - BalanceDelta curDeltas; - uint128 prevAmount; - int128 deltaIn; - int128 deltaOut; - Currency prevCurrency; - uint160 sqrtPriceX96After; - } - /// @dev Only this address may call this function. Used to mimic internal functions, using an /// external call to catch and parse revert reasons modifier selfOnly() { @@ -106,41 +97,38 @@ contract Quoter is IQuoter, SafeCallback { returnData.revertWith(); } - /// @dev quote an ExactInput swap along a path of tokens, then revert with the result + /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); - QuoteCache memory cache; + BalanceDelta swapDelta; + uint128 amountIn = params.exactAmount; + Currency inputCurrency = params.exactCurrency; for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = - params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : cache.prevCurrency); - - (cache.curDeltas, cache.sqrtPriceX96After) = _swap( - poolKey, - zeroForOne, - -int256(int128(i == 0 ? params.exactAmount : cache.prevAmount)), - 0, - params.path[i].hookData - ); - - (cache.deltaIn, cache.deltaOut) = zeroForOne - ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) - : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); - result.deltaAmounts[i] += cache.deltaIn; - result.deltaAmounts[i + 1] += cache.deltaOut; - - cache.prevAmount = zeroForOne ? uint128(cache.curDeltas.amount1()) : uint128(cache.curDeltas.amount0()); - cache.prevCurrency = params.path[i].intermediateCurrency; - result.sqrtPriceX96AfterList[i] = cache.sqrtPriceX96After; + (PoolKey memory poolKey, bool zeroForOne) = params.path[i].getPoolAndSwapDirection(inputCurrency); + + (swapDelta, result.sqrtPriceX96AfterList[i]) = + _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, params.path[i].hookData); + + if (zeroForOne) { + result.deltaAmounts[i] += -swapDelta.amount0(); + result.deltaAmounts[i + 1] += -swapDelta.amount1(); + amountIn = uint128(swapDelta.amount1()); + } else { + result.deltaAmounts[i] += -swapDelta.amount1(); + result.deltaAmounts[i + 1] += -swapDelta.amount0(); + amountIn = uint128(swapDelta.amount0()); + } + inputCurrency = params.path[i].intermediateCurrency; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); encodedResult.revertWith(); } - /// @dev quote an ExactInput swap on a pool, then revert with the result + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( params.poolKey, @@ -159,43 +147,44 @@ contract Quoter is IQuoter, SafeCallback { encodedResult.revertWith(); } - /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result + /// @dev external function called within the _unlockCallback, to simulate an exact output swap, then revert with the result function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; QuoteResult memory result = QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); - QuoteCache memory cache; - uint128 curAmountOut; + BalanceDelta swapDelta; + uint128 amountOut = params.exactAmount; + Currency outputCurrency = params.exactCurrency; for (uint256 i = pathLength; i > 0; i--) { - curAmountOut = i == pathLength ? params.exactAmount : cache.prevAmount; - amountOutCached = curAmountOut; + amountOutCached = amountOut; - (PoolKey memory poolKey, bool oneForZero) = PathKeyLibrary.getPoolAndSwapDirection( - params.path[i - 1], i == pathLength ? params.exactCurrency : cache.prevCurrency - ); + (PoolKey memory poolKey, bool oneForZero) = params.path[i - 1].getPoolAndSwapDirection(outputCurrency); - (cache.curDeltas, cache.sqrtPriceX96After) = - _swap(poolKey, !oneForZero, int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); + (swapDelta, result.sqrtPriceX96AfterList[i - 1]) = + _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, params.path[i - 1].hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; - (cache.deltaIn, cache.deltaOut) = !oneForZero - ? (-cache.curDeltas.amount0(), -cache.curDeltas.amount1()) - : (-cache.curDeltas.amount1(), -cache.curDeltas.amount0()); - result.deltaAmounts[i - 1] += cache.deltaIn; - result.deltaAmounts[i] += cache.deltaOut; - - cache.prevAmount = !oneForZero ? uint128(-cache.curDeltas.amount0()) : uint128(-cache.curDeltas.amount1()); - cache.prevCurrency = params.path[i - 1].intermediateCurrency; - result.sqrtPriceX96AfterList[i - 1] = cache.sqrtPriceX96After; + + if (!oneForZero) { + result.deltaAmounts[i - 1] += -swapDelta.amount0(); + result.deltaAmounts[i] += -swapDelta.amount1(); + amountOut = uint128(-swapDelta.amount0()); + } else { + result.deltaAmounts[i - 1] += -swapDelta.amount1(); + result.deltaAmounts[i] += -swapDelta.amount0(); + amountOut = uint128(-swapDelta.amount1()); + } + + outputCurrency = params.path[i - 1].intermediateCurrency; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); encodedResult.revertWith(); } - /// @dev quote an ExactOutput swap on a pool, then revert with the result + /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { // if no price limit has been specified, cache the output amount for comparison inside the _swap function if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; @@ -218,7 +207,7 @@ contract Quoter is IQuoter, SafeCallback { encodedResult.revertWith(); } - /// @dev Execute a swap and return the amounts delta, as well as relevant pool state + /// @dev Execute a swap and return the amount deltas, as well as the sqrtPrice from the end of the swap /// @notice if amountSpecified < 0, the swap is exactInput, otherwise exactOutput function _swap( PoolKey memory poolKey, From 251c104e35393813445400f3be57e0324002dbd2 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 10 Sep 2024 17:47:18 -0400 Subject: [PATCH 05/15] cache path key --- ...uoter_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactInput_oneHop_initializedAfter.snap | 2 +- ...oteExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...oter_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ...ter_quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...quoteExactOutput_oneHop_initializedAfter.snap | 2 +- ...teExactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/lens/Quoter.sol | 16 ++++++++++------ 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 2e4f8e31..4c00c495 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -128285 \ No newline at end of file +127764 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 2f3c1bba..287ba61d 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -153188 \ No newline at end of file +152656 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index d71ed55c..e4a3fc7d 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -87211 \ No newline at end of file +86679 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 433912de..6656c916 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -212035 \ No newline at end of file +210976 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 7869622b..70adf0b9 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -148025 \ No newline at end of file +147458 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index fbe64408..ac5a8933 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -178162 \ No newline at end of file +177595 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 03cb97d1..012cb790 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -148093 \ No newline at end of file +147526 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 76815c76..cbec599b 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -124792 \ No newline at end of file +124225 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 97851c9b..d8c5ead7 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -252373 \ No newline at end of file +251247 \ No newline at end of file diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index ae98a9f4..c5a75a9a 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -106,12 +106,14 @@ contract Quoter is IQuoter, SafeCallback { BalanceDelta swapDelta; uint128 amountIn = params.exactAmount; Currency inputCurrency = params.exactCurrency; + PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = params.path[i].getPoolAndSwapDirection(inputCurrency); + pathKey = params.path[i]; + (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency); (swapDelta, result.sqrtPriceX96AfterList[i]) = - _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, params.path[i].hookData); + _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, pathKey.hookData); if (zeroForOne) { result.deltaAmounts[i] += -swapDelta.amount0(); @@ -122,7 +124,7 @@ contract Quoter is IQuoter, SafeCallback { result.deltaAmounts[i + 1] += -swapDelta.amount0(); amountIn = uint128(swapDelta.amount0()); } - inputCurrency = params.path[i].intermediateCurrency; + inputCurrency = pathKey.intermediateCurrency; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); encodedResult.revertWith(); @@ -156,14 +158,16 @@ contract Quoter is IQuoter, SafeCallback { BalanceDelta swapDelta; uint128 amountOut = params.exactAmount; Currency outputCurrency = params.exactCurrency; + PathKey calldata pathKey; for (uint256 i = pathLength; i > 0; i--) { + pathKey = params.path[i - 1]; amountOutCached = amountOut; - (PoolKey memory poolKey, bool oneForZero) = params.path[i - 1].getPoolAndSwapDirection(outputCurrency); + (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); (swapDelta, result.sqrtPriceX96AfterList[i - 1]) = - _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, params.path[i - 1].hookData); + _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; @@ -178,7 +182,7 @@ contract Quoter is IQuoter, SafeCallback { amountOut = uint128(-swapDelta.amount1()); } - outputCurrency = params.path[i - 1].intermediateCurrency; + outputCurrency = pathKey.intermediateCurrency; } bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); encodedResult.revertWith(); From 7b665b43ecd178bce125235b222452ea0065f0c4 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 11 Sep 2024 17:32:39 -0400 Subject: [PATCH 06/15] Change return types --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/interfaces/IQuoter.sol | 16 +-- src/lens/Quoter.sol | 72 +++++-------- src/libraries/RevertBytes.sol | 15 ++- test/Quoter.t.sol | 102 +++++++++--------- 17 files changed, 102 insertions(+), 129 deletions(-) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 4991aadb..6bf2a2fc 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -149088 \ No newline at end of file +147574 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index e5627475..3db18e7f 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -154572 \ No newline at end of file +153033 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 06e1cd8c..6c17678a 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -83716 \ No newline at end of file +82272 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index d6dbd4be..fc23876a 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -88524 \ No newline at end of file +87066 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 4c00c495..1037859a 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -127764 \ No newline at end of file +125376 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 287ba61d..4fc87472 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -152656 \ No newline at end of file +150285 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index e4a3fc7d..d6995e55 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -86679 \ No newline at end of file +84308 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 6656c916..383f0ac8 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -210976 \ No newline at end of file +207585 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 70adf0b9..675b2093 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -147458 \ No newline at end of file +145077 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index ac5a8933..e5b91ef0 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -177595 \ No newline at end of file +175214 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 012cb790..7d83e832 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -147526 \ No newline at end of file +145145 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index cbec599b..48826398 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -124225 \ No newline at end of file +121844 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index d8c5ead7..3f1771b0 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -251247 \ No newline at end of file +247853 \ No newline at end of file diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index 01cd6eb7..fc9ea66b 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -43,22 +43,22 @@ interface IQuoter { /// exactAmount The desired input amount /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap /// hookData arbitrary hookData to pass into the associated hooks - /// @return deltaAmounts Delta amounts resulted from the swap + /// @return amountOut The output quote for the exactIn swap /// @return sqrtPriceX96After The sqrt price of the pool after the swap function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate); + returns (uint256 amountOut, uint160 sqrtPriceX96After, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact input swap /// @param params the params for the quote, encoded as 'QuoteExactParams' /// currencyIn The input currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// exactAmount The desired input amount - /// @return deltaAmounts Delta amounts along the path resulted from the swap + /// @return amountOut The output quote for the exactIn swap /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path function quoteExactInput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); + returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); /// @notice Returns the delta amounts for a given exact output swap of a single pool /// @param params The params for the quote, encoded as `QuoteExactSingleParams` @@ -67,20 +67,20 @@ interface IQuoter { /// exactAmount The desired output amount /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap /// hookData arbitrary hookData to pass into the associated hooks - /// @return deltaAmounts Delta amounts resulted from the swap + /// @return amountIn The input quote for the exactOut swap /// @return sqrtPriceX96After The sqrt price of the pool after the swap function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate); + returns (uint256 amountIn, uint160 sqrtPriceX96After, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact output swap /// @param params the params for the quote, encoded as 'QuoteExactParams' /// currencyOut The output currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// exactAmount The desired output amount - /// @return deltaAmounts Delta amounts along the path resulted from the swap + /// @return amountIn The input quote for the exactOut swap /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path function quoteExactOutput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); + returns (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index c5a75a9a..e5ab9e12 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -22,11 +22,6 @@ contract Quoter is IQuoter, SafeCallback { /// @dev cache used to check a safety condition in exact output swaps. uint128 private amountOutCached; - struct QuoteResult { - int128[] deltaAmounts; - uint160[] sqrtPriceX96AfterList; - } - /// @dev Only this address may call this function. Used to mimic internal functions, using an /// external call to catch and parse revert reasons modifier selfOnly() { @@ -39,53 +34,53 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) + returns (uint256 amountOut, uint160 sqrtPriceX96After, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (deltaAmounts, sqrtPriceX96After) = reason.parseReturnDataSingle(); + (amountOut, sqrtPriceX96After) = reason.parseReturnDataSingle(); } } /// @inheritdoc IQuoter function quoteExactInput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) + returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (deltaAmounts, sqrtPriceX96AfterList) = reason.parseReturnData(); + (amountOut, sqrtPriceX96AfterList) = reason.parseReturnData(); } } /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint256 gasEstimate) + returns (uint256 amountIn, uint160 sqrtPriceX96After, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - (deltaAmounts, sqrtPriceX96After) = reason.parseReturnDataSingle(); + (amountIn, sqrtPriceX96After) = reason.parseReturnDataSingle(); } } /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) external - returns (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) + returns (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (deltaAmounts, sqrtPriceX96AfterList) = reason.parseReturnData(); + (amountIn, sqrtPriceX96AfterList) = reason.parseReturnData(); } } @@ -101,8 +96,7 @@ contract Quoter is IQuoter, SafeCallback { function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - QuoteResult memory result = - QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); BalanceDelta swapDelta; uint128 amountIn = params.exactAmount; Currency inputCurrency = params.exactCurrency; @@ -112,21 +106,14 @@ contract Quoter is IQuoter, SafeCallback { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency); - (swapDelta, result.sqrtPriceX96AfterList[i]) = + (swapDelta, sqrtPriceX96AfterList[i]) = _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, pathKey.hookData); - if (zeroForOne) { - result.deltaAmounts[i] += -swapDelta.amount0(); - result.deltaAmounts[i + 1] += -swapDelta.amount1(); - amountIn = uint128(swapDelta.amount1()); - } else { - result.deltaAmounts[i] += -swapDelta.amount1(); - result.deltaAmounts[i + 1] += -swapDelta.amount0(); - amountIn = uint128(swapDelta.amount0()); - } + amountIn = zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); inputCurrency = pathKey.intermediateCurrency; } - bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); + // amountIn after the loop actually holds the amountOut of the trade + bytes memory encodedResult = abi.encode(amountIn, sqrtPriceX96AfterList); encodedResult.revertWith(); } @@ -140,12 +127,10 @@ contract Quoter is IQuoter, SafeCallback { params.hookData ); - int128[] memory deltaAmounts = new int128[](2); - - deltaAmounts[0] = -deltas.amount0(); - deltaAmounts[1] = -deltas.amount1(); + // the output delta of a swap is positive + uint256 amountOut = params.zeroForOne ? uint128(deltas.amount1()) : uint128(deltas.amount0()); - bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); + bytes memory encodedResult = abi.encode(amountOut, sqrtPriceX96After); encodedResult.revertWith(); } @@ -153,8 +138,7 @@ contract Quoter is IQuoter, SafeCallback { function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - QuoteResult memory result = - QuoteResult({deltaAmounts: new int128[](pathLength + 1), sqrtPriceX96AfterList: new uint160[](pathLength)}); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); BalanceDelta swapDelta; uint128 amountOut = params.exactAmount; Currency outputCurrency = params.exactCurrency; @@ -166,25 +150,18 @@ contract Quoter is IQuoter, SafeCallback { (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); - (swapDelta, result.sqrtPriceX96AfterList[i - 1]) = + (swapDelta, sqrtPriceX96AfterList[i - 1]) = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; - if (!oneForZero) { - result.deltaAmounts[i - 1] += -swapDelta.amount0(); - result.deltaAmounts[i] += -swapDelta.amount1(); - amountOut = uint128(-swapDelta.amount0()); - } else { - result.deltaAmounts[i - 1] += -swapDelta.amount1(); - result.deltaAmounts[i] += -swapDelta.amount0(); - amountOut = uint128(-swapDelta.amount1()); - } + amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0()); outputCurrency = pathKey.intermediateCurrency; } - bytes memory encodedResult = abi.encode(result.deltaAmounts, result.sqrtPriceX96AfterList); + // amountOut after the loop exits actually holds the amountIn of the trade + bytes memory encodedResult = abi.encode(amountOut, sqrtPriceX96AfterList); encodedResult.revertWith(); } @@ -202,12 +179,11 @@ contract Quoter is IQuoter, SafeCallback { ); if (amountOutCached != 0) delete amountOutCached; - int128[] memory deltaAmounts = new int128[](2); - deltaAmounts[0] = -deltas.amount0(); - deltaAmounts[1] = -deltas.amount1(); + // the input delta of a swap is negative so we must flip it + uint256 amountIn = params.zeroForOne ? uint128(-deltas.amount0()) : uint128(-deltas.amount1()); - bytes memory encodedResult = abi.encode(deltaAmounts, sqrtPriceX96After); + bytes memory encodedResult = abi.encode(amountIn, sqrtPriceX96After); encodedResult.revertWith(); } diff --git a/src/libraries/RevertBytes.sol b/src/libraries/RevertBytes.sol index e045f57e..3c9a24d6 100644 --- a/src/libraries/RevertBytes.sol +++ b/src/libraries/RevertBytes.sol @@ -7,10 +7,9 @@ library RevertBytes { /// @notice error thrown when invalid revert bytes are thrown by the quote error UnexpectedRevertBytes(bytes revertData); - /// @dev min valid reason is 5-words long (160 bytes) - /// @dev int128[2] includes 32 bytes for offset, 32 bytes for length, and 32 bytes for each element - /// @dev Plus sqrtPriceX96After padded to 32 bytes - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 160; + /// @dev min valid reason is 2-words long (64 bytes) + /// @dev amountUnspecified and sqrtPriceX96After + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 64; /// @notice reverts, where the revert data is the provided bytes /// @dev called when quoting, at the end of simulating a swap, to revert with the swap information as the revert reason @@ -32,15 +31,15 @@ library RevertBytes { /// @notice validates a received revert reason from a single-hop swap. /// Then, if valid, decodes it into the information generated by a quote - function parseReturnDataSingle(bytes memory reason) internal pure returns (int128[] memory, uint160) { + function parseReturnDataSingle(bytes memory reason) internal pure returns (uint256, uint160) { reason.validateRevertReason(); - return abi.decode(reason, (int128[], uint160)); + return abi.decode(reason, (uint256, uint160)); } /// @notice validates a received revert reason from a multi-hop swap. /// Then, if valid, decodes it into the information generated by a quote - function parseReturnData(bytes memory reason) internal pure returns (int128[] memory, uint160[] memory) { + function parseReturnData(bytes memory reason) internal pure returns (uint256, uint160[] memory) { reason.validateRevertReason(); - return abi.decode(reason, (int128[], uint160[])); + return abi.decode(reason, (uint256, uint160[])); } } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 552295d8..3535789d 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -85,7 +85,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( + (uint256 amountOut, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, @@ -96,7 +96,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactInputSingle_zeroForOne_multiplePositions"); - assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); + assertEq(amountOut, expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); } @@ -105,7 +105,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( + (uint256 amountOut, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, @@ -116,7 +116,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactInputSingle_oneForZero_multiplePositions"); - assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); + assertEq(amountOut, expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); } @@ -132,9 +132,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(uint128(-deltaAmounts[1]), 9871); + assertEq(amountOut, 9871); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); } @@ -146,9 +146,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(uint128(-deltaAmounts[1]), 6143); + assertEq(amountOut, 6143); assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); } @@ -160,11 +160,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -60 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_1TickLoaded"); - assertEq(uint128(-deltaAmounts[1]), 3971); + assertEq(amountOut, 3971); assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); } @@ -173,9 +173,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(amountOut, 8); assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); } @@ -185,9 +185,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(amountOut, 8); assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); } @@ -196,9 +196,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(-deltaAmounts[1], 9871); + assertEq(amountOut, 9871); assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); } @@ -210,11 +210,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // 120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_initializedAfter"); - assertEq(-deltaAmounts[1], 6190); + assertEq(amountOut, 6190); assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); } @@ -225,11 +225,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_startingInitialized"); - assertEq(-deltaAmounts[1], 198); + assertEq(amountOut, 198); assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); } @@ -239,9 +239,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(-deltaAmounts[1], 101); + assertEq(amountOut, 101); assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); } @@ -250,8 +250,8 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); - assertEq(-deltaAmounts[1], 9871); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + assertEq(amountOut, 9871); assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); } @@ -261,17 +261,17 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_twoHops"); - assertEq(-deltaAmounts[2], 9745); + assertEq(amountOut, 9745); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); } function testQuoter_quoteExactOutputSingle_0to1() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( + (uint256 amountIn, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, @@ -282,12 +282,12 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactOutputSingle_zeroForOne"); - assertEq(deltaAmounts[0], 9981); + assertEq(amountIn, 9981); assertEq(sqrtPriceX96After, SQRT_PRICE_100_102); } function testQuoter_quoteExactOutputSingle_1to0() public { - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( + (uint256 amountIn, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, @@ -298,7 +298,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactOutputSingle_oneForZero"); - assertEq(deltaAmounts[1], 9981); + assertEq(amountIn, 9981); assertEq(sqrtPriceX96After, SQRT_PRICE_102_100); } @@ -307,11 +307,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_2TicksLoaded"); - assertEq(deltaAmounts[0], 15273); + assertEq(amountIn, 15273); assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); } @@ -321,10 +321,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); - assertEq(deltaAmounts[0], 6200); + assertEq(amountIn, 6200); assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); } @@ -334,10 +334,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); - assertEq(deltaAmounts[0], 4029); + assertEq(amountIn, 4029); assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); } @@ -349,10 +349,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_startingInitialized"); - assertEq(deltaAmounts[0], 102); + assertEq(amountIn, 102); assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); } @@ -362,9 +362,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[0], 12); + assertEq(amountIn, 12); assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); } @@ -373,9 +373,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[0], 15273); + assertEq(amountIn, 15273); assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); } @@ -385,9 +385,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[0], 6283); + assertEq(amountIn, 6283); assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); } @@ -396,9 +396,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[0], 6055); + assertEq(amountIn, 6055); assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); } @@ -408,9 +408,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[0], 10000); + assertEq(amountIn, 10000); assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); } @@ -421,13 +421,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - (int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_twoHops"); - assertEq(deltaAmounts[0], 10000); - assertEq(deltaAmounts[1], 0); - assertEq(deltaAmounts[2], -9745); + assertEq(amountIn, 10000); assertEq(sqrtPriceX96AfterList[0], 78461888503179331029803316753); assertEq(sqrtPriceX96AfterList[1], 80007838904387594703933785072); } From 8a34fcf40f66a81131046bf02c56cece6361e0b3 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 13:37:42 -0400 Subject: [PATCH 07/15] remove sqrt price after logic --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/interfaces/IQuoter.sol | 23 ++---- src/lens/Quoter.sol | 48 ++++++----- src/libraries/RevertBytes.sol | 18 ++--- test/Quoter.t.sol | 79 ++++++------------- 17 files changed, 73 insertions(+), 121 deletions(-) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 6bf2a2fc..b6b6057e 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -147574 \ No newline at end of file +146280 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 3db18e7f..31763e34 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -153033 \ No newline at end of file +151744 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 6c17678a..163288a3 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -82272 \ No newline at end of file +80968 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index fc23876a..edb63336 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -87066 \ No newline at end of file +85762 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 1037859a..d431e401 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -125376 \ No newline at end of file +122806 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 4fc87472..4b69353d 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -150285 \ No newline at end of file +147715 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index d6995e55..eb0a84d7 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -84308 \ No newline at end of file +81738 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 383f0ac8..72203dc7 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -207585 \ No newline at end of file +203573 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 675b2093..0d193697 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -145077 \ No newline at end of file +142513 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index e5b91ef0..2ba3bbed 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -175214 \ No newline at end of file +172650 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 7d83e832..66dc7171 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -145145 \ No newline at end of file +142581 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 48826398..d85a3702 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -121844 \ No newline at end of file +119280 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 3f1771b0..2f7b360b 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -247853 \ No newline at end of file +243850 \ No newline at end of file diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index fc9ea66b..62e08d8e 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -11,17 +11,10 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @dev These functions are not marked view because they rely on calling non-view functions and reverting /// to compute the result. They are also not gas efficient and should not be called on-chain. interface IQuoter { - error InvalidLockCaller(); - error InvalidQuoteBatchParams(); error InsufficientAmountOut(); error LockFailure(); error NotSelf(); - struct PoolDeltas { - int128 currency0Delta; - int128 currency1Delta; - } - struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; @@ -44,10 +37,10 @@ interface IQuoter { /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap /// hookData arbitrary hookData to pass into the associated hooks /// @return amountOut The output quote for the exactIn swap - /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return gasEstimate Estimated gas units used for the swap function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (uint256 amountOut, uint160 sqrtPriceX96After, uint256 gasEstimate); + returns (uint256 amountOut, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact input swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -55,10 +48,10 @@ interface IQuoter { /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// exactAmount The desired input amount /// @return amountOut The output quote for the exactIn swap - /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return gasEstimate Estimated gas units used for the swap function quoteExactInput(QuoteExactParams memory params) external - returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); + returns (uint256 amountOut, uint256 gasEstimate); /// @notice Returns the delta amounts for a given exact output swap of a single pool /// @param params The params for the quote, encoded as `QuoteExactSingleParams` @@ -68,10 +61,10 @@ interface IQuoter { /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap /// hookData arbitrary hookData to pass into the associated hooks /// @return amountIn The input quote for the exactOut swap - /// @return sqrtPriceX96After The sqrt price of the pool after the swap + /// @return gasEstimate Estimated gas units used for the swap function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (uint256 amountIn, uint160 sqrtPriceX96After, uint256 gasEstimate); + returns (uint256 amountIn, uint256 gasEstimate); /// @notice Returns the delta amounts along the swap path for a given exact output swap /// @param params the params for the quote, encoded as 'QuoteExactParams' @@ -79,8 +72,8 @@ interface IQuoter { /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// exactAmount The desired output amount /// @return amountIn The input quote for the exactOut swap - /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path + /// @return gasEstimate Estimated gas units used for the swap function quoteExactOutput(QuoteExactParams memory params) external - returns (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate); + returns (uint256 amountIn, uint256 gasEstimate); } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index e5ab9e12..33560d13 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -34,53 +34,53 @@ contract Quoter is IQuoter, SafeCallback { /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) external - returns (uint256 amountOut, uint160 sqrtPriceX96After, uint256 gasEstimate) + returns (uint256 amountOut, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (amountOut, sqrtPriceX96After) = reason.parseReturnDataSingle(); + amountOut = reason.parseReturnData(); } } /// @inheritdoc IQuoter function quoteExactInput(QuoteExactParams memory params) external - returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) + returns (uint256 amountOut, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (amountOut, sqrtPriceX96AfterList) = reason.parseReturnData(); + amountOut = reason.parseReturnData(); } } /// @inheritdoc IQuoter function quoteExactOutputSingle(QuoteExactSingleParams memory params) external - returns (uint256 amountIn, uint160 sqrtPriceX96After, uint256 gasEstimate) + returns (uint256 amountIn, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - (amountIn, sqrtPriceX96After) = reason.parseReturnDataSingle(); + amountIn = reason.parseReturnData(); } } /// @inheritdoc IQuoter function quoteExactOutput(QuoteExactParams memory params) external - returns (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList, uint256 gasEstimate) + returns (uint256 amountIn, uint256 gasEstimate) { uint256 gasBefore = gasleft(); try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - (amountIn, sqrtPriceX96AfterList) = reason.parseReturnData(); + amountIn = reason.parseReturnData(); } } @@ -96,7 +96,6 @@ contract Quoter is IQuoter, SafeCallback { function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); BalanceDelta swapDelta; uint128 amountIn = params.exactAmount; Currency inputCurrency = params.exactCurrency; @@ -106,20 +105,19 @@ contract Quoter is IQuoter, SafeCallback { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(inputCurrency); - (swapDelta, sqrtPriceX96AfterList[i]) = - _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, pathKey.hookData); + swapDelta = _swap(poolKey, zeroForOne, -int256(int128(amountIn)), 0, pathKey.hookData); amountIn = zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); inputCurrency = pathKey.intermediateCurrency; } // amountIn after the loop actually holds the amountOut of the trade - bytes memory encodedResult = abi.encode(amountIn, sqrtPriceX96AfterList); + bytes memory encodedResult = abi.encode(amountIn); encodedResult.revertWith(); } /// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result function _quoteExactInputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { - (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( + BalanceDelta swapDelta = _swap( params.poolKey, params.zeroForOne, -int256(int128(params.exactAmount)), @@ -128,9 +126,9 @@ contract Quoter is IQuoter, SafeCallback { ); // the output delta of a swap is positive - uint256 amountOut = params.zeroForOne ? uint128(deltas.amount1()) : uint128(deltas.amount0()); + uint256 amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); - bytes memory encodedResult = abi.encode(amountOut, sqrtPriceX96After); + bytes memory encodedResult = abi.encode(amountOut); encodedResult.revertWith(); } @@ -138,7 +136,6 @@ contract Quoter is IQuoter, SafeCallback { function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); BalanceDelta swapDelta; uint128 amountOut = params.exactAmount; Currency outputCurrency = params.exactCurrency; @@ -150,8 +147,7 @@ contract Quoter is IQuoter, SafeCallback { (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); - (swapDelta, sqrtPriceX96AfterList[i - 1]) = - _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); + swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); // always clear because sqrtPriceLimitX96 is set to 0 always delete amountOutCached; @@ -161,7 +157,7 @@ contract Quoter is IQuoter, SafeCallback { outputCurrency = pathKey.intermediateCurrency; } // amountOut after the loop exits actually holds the amountIn of the trade - bytes memory encodedResult = abi.encode(amountOut, sqrtPriceX96AfterList); + bytes memory encodedResult = abi.encode(amountOut); encodedResult.revertWith(); } @@ -170,7 +166,7 @@ contract Quoter is IQuoter, SafeCallback { // if no price limit has been specified, cache the output amount for comparison inside the _swap function if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - (BalanceDelta deltas, uint160 sqrtPriceX96After) = _swap( + BalanceDelta swapDelta = _swap( params.poolKey, params.zeroForOne, int256(uint256(params.exactAmount)), @@ -181,9 +177,9 @@ contract Quoter is IQuoter, SafeCallback { if (amountOutCached != 0) delete amountOutCached; // the input delta of a swap is negative so we must flip it - uint256 amountIn = params.zeroForOne ? uint128(-deltas.amount0()) : uint128(-deltas.amount1()); + uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); - bytes memory encodedResult = abi.encode(amountIn, sqrtPriceX96After); + bytes memory encodedResult = abi.encode(amountIn); encodedResult.revertWith(); } @@ -195,8 +191,8 @@ contract Quoter is IQuoter, SafeCallback { int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata hookData - ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After) { - deltas = poolManager.swap( + ) private returns (BalanceDelta swapDelta) { + swapDelta = poolManager.swap( poolKey, IPoolManager.SwapParams({ zeroForOne: zeroForOne, @@ -206,10 +202,10 @@ contract Quoter is IQuoter, SafeCallback { hookData ); // only exactOut case - if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? deltas.amount1() : deltas.amount0())) { + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? swapDelta.amount1() : swapDelta.amount0())) + { revert InsufficientAmountOut(); } - (sqrtPriceX96After,,,) = poolManager.getSlot0(poolKey.toId()); } /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction diff --git a/src/libraries/RevertBytes.sol b/src/libraries/RevertBytes.sol index 3c9a24d6..c3b98ce9 100644 --- a/src/libraries/RevertBytes.sol +++ b/src/libraries/RevertBytes.sol @@ -7,9 +7,8 @@ library RevertBytes { /// @notice error thrown when invalid revert bytes are thrown by the quote error UnexpectedRevertBytes(bytes revertData); - /// @dev min valid reason is 2-words long (64 bytes) - /// @dev amountUnspecified and sqrtPriceX96After - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 64; + /// @dev min valid reason is 1-word long (32 bytes): amountUnspecified + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 32; /// @notice reverts, where the revert data is the provided bytes /// @dev called when quoting, at the end of simulating a swap, to revert with the swap information as the revert reason @@ -29,17 +28,10 @@ library RevertBytes { } } - /// @notice validates a received revert reason from a single-hop swap. + /// @notice validates a received revert reason from a swap. /// Then, if valid, decodes it into the information generated by a quote - function parseReturnDataSingle(bytes memory reason) internal pure returns (uint256, uint160) { + function parseReturnData(bytes memory reason) internal pure returns (uint256) { reason.validateRevertReason(); - return abi.decode(reason, (uint256, uint160)); - } - - /// @notice validates a received revert reason from a multi-hop swap. - /// Then, if valid, decodes it into the information generated by a quote - function parseReturnData(bytes memory reason) internal pure returns (uint256, uint160[] memory) { - reason.validateRevertReason(); - return abi.decode(reason, (uint256, uint160[])); + return abi.decode(reason, (uint256)); } } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 3535789d..0a6011db 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -83,9 +83,8 @@ contract QuoterTest is Test, Deployers, GasSnapshot { function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; - uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (uint256 amountOut, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( + (uint256 amountOut,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, @@ -97,15 +96,13 @@ contract QuoterTest is Test, Deployers, GasSnapshot { snapLastCall("Quoter_exactInputSingle_zeroForOne_multiplePositions"); assertEq(amountOut, expectedAmountOut); - assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); } function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; - uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (uint256 amountOut, uint160 sqrtPriceX96After,) = quoter.quoteExactInputSingle( + (uint256 amountOut,) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, @@ -117,7 +114,6 @@ contract QuoterTest is Test, Deployers, GasSnapshot { snapLastCall("Quoter_exactInputSingle_oneForZero_multiplePositions"); assertEq(amountOut, expectedAmountOut); - assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); } // nested self-call into unlockCallback reverts @@ -132,10 +128,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 9871); - assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); } function testQuoter_quoteExactInput_0to2_2TicksLoaded_initializedAfter() public { @@ -146,10 +141,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 6143); - assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); } function testQuoter_quoteExactInput_0to2_1TickLoaded() public { @@ -160,12 +154,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -60 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_1TickLoaded"); assertEq(amountOut, 3971); - assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); } function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { @@ -173,10 +166,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 8); - assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); } function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { @@ -185,10 +177,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 8); - assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); } function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { @@ -196,10 +187,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 9871); - assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); } function testQuoter_quoteExactInput_2to0_2TicksLoaded_initializedAfter() public { @@ -210,12 +200,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // 120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_initializedAfter"); assertEq(amountOut, 6190); - assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); } function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { @@ -225,12 +214,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_startingInitialized"); assertEq(amountOut, 198); - assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); } // 2->0 starting not initialized @@ -239,10 +227,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 101); - assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); } function testQuoter_quoteExactInput_2to1() public { @@ -250,9 +237,8 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); assertEq(amountOut, 9871); - assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); } function testQuoter_quoteExactInput_0to2to1() public { @@ -261,17 +247,15 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactInput(params); + (uint256 amountOut,) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_twoHops"); assertEq(amountOut, 9745); - assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); } function testQuoter_quoteExactOutputSingle_0to1() public { - (uint256 amountIn, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( + (uint256 amountIn,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, @@ -283,11 +267,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { snapLastCall("Quoter_exactOutputSingle_zeroForOne"); assertEq(amountIn, 9981); - assertEq(sqrtPriceX96After, SQRT_PRICE_100_102); } function testQuoter_quoteExactOutputSingle_1to0() public { - (uint256 amountIn, uint160 sqrtPriceX96After,) = quoter.quoteExactOutputSingle( + (uint256 amountIn,) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, @@ -299,7 +282,6 @@ contract QuoterTest is Test, Deployers, GasSnapshot { snapLastCall("Quoter_exactOutputSingle_oneForZero"); assertEq(amountIn, 9981); - assertEq(sqrtPriceX96After, SQRT_PRICE_102_100); } function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { @@ -307,12 +289,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_2TicksLoaded"); assertEq(amountIn, 15273); - assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); } function testQuoter_quoteExactOutput_0to2_1TickLoaded_initializedAfter() public { @@ -321,11 +302,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); assertEq(amountIn, 6200); - assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); } function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { @@ -334,11 +314,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); assertEq(amountIn, 4029); - assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); } function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { @@ -349,11 +328,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_startingInitialized"); assertEq(amountIn, 102); - assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); } function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { @@ -362,10 +340,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); assertEq(amountIn, 12); - assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); } function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { @@ -373,10 +350,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); assertEq(amountIn, 15273); - assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); } function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initializedAfter() public { @@ -385,10 +361,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); assertEq(amountIn, 6283); - assertEq(sqrtPriceX96AfterList[0], 79708304437530892332449657932); } function testQuoter_quoteExactOutput_2to0_1TickLoaded() public { @@ -396,10 +371,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); assertEq(amountIn, 6055); - assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); } function testQuoter_quoteExactOutput_2to1() public { @@ -408,10 +382,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); assertEq(amountIn, 10000); - assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); } function testQuoter_quoteExactOutput_0to2to1() public { @@ -421,13 +394,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - (uint256 amountIn, uint160[] memory sqrtPriceX96AfterList,) = quoter.quoteExactOutput(params); + (uint256 amountIn,) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_twoHops"); assertEq(amountIn, 10000); - assertEq(sqrtPriceX96AfterList[0], 78461888503179331029803316753); - assertEq(sqrtPriceX96AfterList[1], 80007838904387594703933785072); } function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) From 6fb40dd96e4e7f5a91cfd99a462072e497d9d8c0 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 13:56:53 -0400 Subject: [PATCH 08/15] Revert with custom error --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/lens/Quoter.sol | 20 +++++------- .../{RevertBytes.sol => QuoterRevert.sol} | 31 ++++++++++++++----- 15 files changed, 44 insertions(+), 33 deletions(-) rename src/libraries/{RevertBytes.sol => QuoterRevert.sol} (54%) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index b6b6057e..9961925c 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -146280 \ No newline at end of file +146153 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 31763e34..cfdcf17d 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -151744 \ No newline at end of file +151617 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 163288a3..94e5aa1f 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -80968 \ No newline at end of file +80841 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index edb63336..550de76d 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -85762 \ No newline at end of file +85635 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index d431e401..cd7517fd 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -122806 \ No newline at end of file +122679 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 4b69353d..e6b48d53 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -147715 \ No newline at end of file +147588 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index eb0a84d7..7490d972 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -81738 \ No newline at end of file +81611 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 72203dc7..2bf5823b 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -203573 \ No newline at end of file +203446 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 0d193697..1106e383 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -142513 \ No newline at end of file +142383 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index 2ba3bbed..ecf14371 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -172650 \ No newline at end of file +172520 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 66dc7171..611e7b1e 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -142581 \ No newline at end of file +142451 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index d85a3702..8b51c0c3 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -119280 \ No newline at end of file +119150 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 2f7b360b..6223a09c 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -243850 \ No newline at end of file +243720 \ No newline at end of file diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 33560d13..ce2a047d 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -10,14 +10,14 @@ import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PathKey, PathKeyLibrary} from "../libraries/PathKey.sol"; -import {RevertBytes} from "../libraries/RevertBytes.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; import {SafeCallback} from "../base/SafeCallback.sol"; contract Quoter is IQuoter, SafeCallback { using PoolIdLibrary for PoolKey; using PathKeyLibrary for PathKey; using StateLibrary for IPoolManager; - using RevertBytes for bytes; + using QuoterRevert for *; /// @dev cache used to check a safety condition in exact output swaps. uint128 private amountOutCached; @@ -89,7 +89,7 @@ contract Quoter is IQuoter, SafeCallback { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); - returnData.revertWith(); + returnData.bubbleReason(); } /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result @@ -111,8 +111,7 @@ contract Quoter is IQuoter, SafeCallback { inputCurrency = pathKey.intermediateCurrency; } // amountIn after the loop actually holds the amountOut of the trade - bytes memory encodedResult = abi.encode(amountIn); - encodedResult.revertWith(); + amountIn.revertQuote(); } /// @dev external function called within the _unlockCallback, to simulate a single-hop exact input swap, then revert with the result @@ -127,9 +126,7 @@ contract Quoter is IQuoter, SafeCallback { // the output delta of a swap is positive uint256 amountOut = params.zeroForOne ? uint128(swapDelta.amount1()) : uint128(swapDelta.amount0()); - - bytes memory encodedResult = abi.encode(amountOut); - encodedResult.revertWith(); + amountOut.revertQuote(); } /// @dev external function called within the _unlockCallback, to simulate an exact output swap, then revert with the result @@ -157,8 +154,7 @@ contract Quoter is IQuoter, SafeCallback { outputCurrency = pathKey.intermediateCurrency; } // amountOut after the loop exits actually holds the amountIn of the trade - bytes memory encodedResult = abi.encode(amountOut); - encodedResult.revertWith(); + amountOut.revertQuote(); } /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result @@ -178,9 +174,7 @@ contract Quoter is IQuoter, SafeCallback { // the input delta of a swap is negative so we must flip it uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); - - bytes memory encodedResult = abi.encode(amountIn); - encodedResult.revertWith(); + amountIn.revertQuote(); } /// @dev Execute a swap and return the amount deltas, as well as the sqrtPrice from the end of the swap diff --git a/src/libraries/RevertBytes.sol b/src/libraries/QuoterRevert.sol similarity index 54% rename from src/libraries/RevertBytes.sol rename to src/libraries/QuoterRevert.sol index c3b98ce9..e9db2b2f 100644 --- a/src/libraries/RevertBytes.sol +++ b/src/libraries/QuoterRevert.sol @@ -1,18 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -library RevertBytes { - using RevertBytes for bytes; +import {ParseBytes} from "@uniswap/v4-core/src/libraries/ParseBytes.sol"; + +library QuoterRevert { + using QuoterRevert for bytes; + using ParseBytes for bytes; /// @notice error thrown when invalid revert bytes are thrown by the quote error UnexpectedRevertBytes(bytes revertData); - /// @dev min valid reason is 1-word long (32 bytes): amountUnspecified - uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 32; + /// @notice error thrown containing the quote as the data, to be caught and parsed later + error QuoteSwap(uint256 amount); /// @notice reverts, where the revert data is the provided bytes /// @dev called when quoting, at the end of simulating a swap, to revert with the swap information as the revert reason - function revertWith(bytes memory revertData) internal pure { + function revertQuote(uint256 amountUnspecified) internal pure { + revert QuoteSwap(amountUnspecified); + } + + /// @notice reverts, where the revert data is the provided bytes + function bubbleReason(bytes memory revertData) internal pure { // mload(revertData): the length of the revert data // add(revertData, 0x20): a pointer to the start of the revert data assembly ("memory-safe") { @@ -23,7 +31,7 @@ library RevertBytes { /// @notice check revert reasons are of the expected length; otherwise revert with different message /// @dev called after a swap simulation reverts, to check if the revert was valid encoded quote information, or an internal issue function validateRevertReason(bytes memory reason) internal pure { - if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { + if (reason.parseSelector() != QuoteSwap.selector) { revert UnexpectedRevertBytes(reason); } } @@ -32,6 +40,15 @@ library RevertBytes { /// Then, if valid, decodes it into the information generated by a quote function parseReturnData(bytes memory reason) internal pure returns (uint256) { reason.validateRevertReason(); - return abi.decode(reason, (uint256)); + return reason.parseAmountUnpecified(); + } + + function parseAmountUnpecified(bytes memory reason) internal pure returns (uint256 amountUnspecified) { + // reason -> reason+0x1f is the length of the reason string + // reason+0x20 -> reason+0x23 is the selector of QuoteSwap + // reason+0x24 -> reason+0x43 is the amountSpecified + assembly ("memory-safe") { + amountUnspecified := mload(add(reason, 0x24)) + } } } From c6c108e5181ab9d2cb4f38cda1e28b535a92c221 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 14:00:35 -0400 Subject: [PATCH 09/15] comments --- src/libraries/QuoterRevert.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/QuoterRevert.sol b/src/libraries/QuoterRevert.sol index e9db2b2f..106d025d 100644 --- a/src/libraries/QuoterRevert.sol +++ b/src/libraries/QuoterRevert.sol @@ -43,10 +43,11 @@ library QuoterRevert { return reason.parseAmountUnpecified(); } + /// @notice extracts the amountUnspecified from an encoded QuoteSwap(amountUnspecified) function parseAmountUnpecified(bytes memory reason) internal pure returns (uint256 amountUnspecified) { // reason -> reason+0x1f is the length of the reason string // reason+0x20 -> reason+0x23 is the selector of QuoteSwap - // reason+0x24 -> reason+0x43 is the amountSpecified + // reason+0x24 -> reason+0x43 is the amountUnspecified assembly ("memory-safe") { amountUnspecified := mload(add(reason, 0x24)) } From d58d388cfc583bfe1bb7a97be2d4420b4a3611da Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 15:22:00 -0400 Subject: [PATCH 10/15] typo --- src/libraries/QuoterRevert.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/QuoterRevert.sol b/src/libraries/QuoterRevert.sol index 106d025d..0c0fc1ce 100644 --- a/src/libraries/QuoterRevert.sol +++ b/src/libraries/QuoterRevert.sol @@ -40,11 +40,11 @@ library QuoterRevert { /// Then, if valid, decodes it into the information generated by a quote function parseReturnData(bytes memory reason) internal pure returns (uint256) { reason.validateRevertReason(); - return reason.parseAmountUnpecified(); + return reason.parseAmountUnspecified(); } /// @notice extracts the amountUnspecified from an encoded QuoteSwap(amountUnspecified) - function parseAmountUnpecified(bytes memory reason) internal pure returns (uint256 amountUnspecified) { + function parseAmountUnspecified(bytes memory reason) internal pure returns (uint256 amountUnspecified) { // reason -> reason+0x1f is the length of the reason string // reason+0x20 -> reason+0x23 is the selector of QuoteSwap // reason+0x24 -> reason+0x43 is the amountUnspecified From 356d0e85cc3663c0d28de330808afe4e63c488a0 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 17:04:23 -0400 Subject: [PATCH 11/15] factor base contract --- src/base/BaseV4Quoter.sol | 62 ++++++++++++++++++++++++++ src/interfaces/IQuoter.sol | 4 -- src/lens/Quoter.sol | 60 ++----------------------- src/libraries/QuoterRevert.sol | 2 +- src/libraries/SqrtPriceLimitHelper.sol | 13 ++++++ test/Quoter.t.sol | 3 +- 6 files changed, 81 insertions(+), 63 deletions(-) create mode 100644 src/base/BaseV4Quoter.sol create mode 100644 src/libraries/SqrtPriceLimitHelper.sol diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol new file mode 100644 index 00000000..438ff205 --- /dev/null +++ b/src/base/BaseV4Quoter.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {QuoterRevert} from "../libraries/QuoterRevert.sol"; +import {SqrtPriceLimitHelper} from "../libraries/SqrtPriceLimitHelper.sol"; +import {SafeCallback} from "../base/SafeCallback.sol"; + +abstract contract BaseV4Quoter is SafeCallback { + using SqrtPriceLimitHelper for uint160; + using QuoterRevert for *; + + error InsufficientAmountOut(); + error LockFailure(); + error NotSelf(); + + constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} + + /// @dev cache used to check a safety condition in exact output swaps. + uint128 internal amountOutCached; + + /// @dev Only this address may call this function. Used to mimic internal functions, using an + /// external call to catch and parse revert reasons + modifier selfOnly() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { + // Call this contract with the data in question. Each quote path + (bool success, bytes memory returnData) = address(this).call(data); + if (success) return returnData; + if (returnData.length == 0) revert LockFailure(); + returnData.bubbleReason(); + } + + /// @dev Execute a swap and return the balance delta + /// @notice if amountSpecified < 0, the swap is exactInput, otherwise exactOutput + function _swap( + PoolKey memory poolKey, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes calldata hookData + ) internal returns (BalanceDelta swapDelta) { + swapDelta = poolManager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: sqrtPriceLimitX96.getSqrtPriceLimit(zeroForOne) + }), + hookData + ); + // only exactOut case + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? swapDelta.amount1() : swapDelta.amount0())) + { + revert InsufficientAmountOut(); + } + } +} diff --git a/src/interfaces/IQuoter.sol b/src/interfaces/IQuoter.sol index 62e08d8e..11acffde 100644 --- a/src/interfaces/IQuoter.sol +++ b/src/interfaces/IQuoter.sol @@ -11,10 +11,6 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @dev These functions are not marked view because they rely on calling non-view functions and reverting /// to compute the result. They are also not gas efficient and should not be called on-chain. interface IQuoter { - error InsufficientAmountOut(); - error LockFailure(); - error NotSelf(); - struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index ce2a047d..5313250b 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -1,35 +1,21 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PathKey, PathKeyLibrary} from "../libraries/PathKey.sol"; import {QuoterRevert} from "../libraries/QuoterRevert.sol"; -import {SafeCallback} from "../base/SafeCallback.sol"; +import {BaseV4Quoter} from "../base/BaseV4Quoter.sol"; -contract Quoter is IQuoter, SafeCallback { - using PoolIdLibrary for PoolKey; +contract Quoter is IQuoter, BaseV4Quoter { using PathKeyLibrary for PathKey; - using StateLibrary for IPoolManager; using QuoterRevert for *; - /// @dev cache used to check a safety condition in exact output swaps. - uint128 private amountOutCached; - - /// @dev Only this address may call this function. Used to mimic internal functions, using an - /// external call to catch and parse revert reasons - modifier selfOnly() { - if (msg.sender != address(this)) revert NotSelf(); - _; - } - - constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} + constructor(IPoolManager _poolManager) BaseV4Quoter(_poolManager) {} /// @inheritdoc IQuoter function quoteExactInputSingle(QuoteExactSingleParams memory params) @@ -84,14 +70,6 @@ contract Quoter is IQuoter, SafeCallback { } } - function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { - // Call this contract with the data in question. Each quote path - (bool success, bytes memory returnData) = address(this).call(data); - if (success) return returnData; - if (returnData.length == 0) revert LockFailure(); - returnData.bubbleReason(); - } - /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; @@ -176,36 +154,4 @@ contract Quoter is IQuoter, SafeCallback { uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); amountIn.revertQuote(); } - - /// @dev Execute a swap and return the amount deltas, as well as the sqrtPrice from the end of the swap - /// @notice if amountSpecified < 0, the swap is exactInput, otherwise exactOutput - function _swap( - PoolKey memory poolKey, - bool zeroForOne, - int256 amountSpecified, - uint160 sqrtPriceLimitX96, - bytes calldata hookData - ) private returns (BalanceDelta swapDelta) { - swapDelta = poolManager.swap( - poolKey, - IPoolManager.SwapParams({ - zeroForOne: zeroForOne, - amountSpecified: amountSpecified, - sqrtPriceLimitX96: _sqrtPriceLimitOrDefault(sqrtPriceLimitX96, zeroForOne) - }), - hookData - ); - // only exactOut case - if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? swapDelta.amount1() : swapDelta.amount0())) - { - revert InsufficientAmountOut(); - } - } - - /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction - function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { - return sqrtPriceLimitX96 == 0 - ? zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 - : sqrtPriceLimitX96; - } } diff --git a/src/libraries/QuoterRevert.sol b/src/libraries/QuoterRevert.sol index 0c0fc1ce..e7b11e15 100644 --- a/src/libraries/QuoterRevert.sol +++ b/src/libraries/QuoterRevert.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import {ParseBytes} from "@uniswap/v4-core/src/libraries/ParseBytes.sol"; diff --git a/src/libraries/SqrtPriceLimitHelper.sol b/src/libraries/SqrtPriceLimitHelper.sol new file mode 100644 index 00000000..eaf393a3 --- /dev/null +++ b/src/libraries/SqrtPriceLimitHelper.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + +library SqrtPriceLimitHelper { + /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction + function getSqrtPriceLimit(uint160 sqrtPriceLimitX96, bool zeroForOne) internal pure returns (uint160) { + return sqrtPriceLimitX96 == 0 + ? zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 + : sqrtPriceLimitX96; + } +} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 0a6011db..d7fbf3a0 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -6,6 +6,7 @@ import {Test} from "forge-std/Test.sol"; import {PathKey} from "../src/libraries/PathKey.sol"; import {IQuoter} from "../src/interfaces/IQuoter.sol"; import {Quoter} from "../src/lens/Quoter.sol"; +import {BaseV4Quoter} from "../src/base/BaseV4Quoter.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; // v4-core @@ -118,7 +119,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // nested self-call into unlockCallback reverts function testQuoter_callUnlockCallback_reverts() public { - vm.expectRevert(IQuoter.LockFailure.selector); + vm.expectRevert(BaseV4Quoter.LockFailure.selector); vm.prank(address(manager)); quoter.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); } From b1effeeda890ba2cb8ca2d5c198c3b4564ea047e Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 17:17:36 -0400 Subject: [PATCH 12/15] move cache into transient storage --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/base/BaseV4Quoter.sol | 17 ++++++++++++--- src/lens/Quoter.sol | 10 ++++----- src/libraries/CacheAmountSpecified.sol | 21 +++++++++++++++++++ 16 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 src/libraries/CacheAmountSpecified.sol diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 9961925c..497eafc6 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -146153 \ No newline at end of file +144148 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index cfdcf17d..6fa583a8 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -151617 \ No newline at end of file +149612 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 94e5aa1f..50d21a77 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -80841 \ No newline at end of file +78941 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index 550de76d..b81c758e 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -85635 \ No newline at end of file +83735 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index cd7517fd..7fc89abd 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -122679 \ No newline at end of file +120674 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index e6b48d53..d515a0dc 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -147588 \ No newline at end of file +145583 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index 7490d972..e1eb3f39 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -81611 \ No newline at end of file +79606 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 2bf5823b..1921fe09 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -203446 \ No newline at end of file +201436 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 1106e383..37535a7b 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -142383 \ No newline at end of file +120261 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index ecf14371..577e306e 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -172520 \ No newline at end of file +150398 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 611e7b1e..9497d424 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -142451 \ No newline at end of file +120329 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 8b51c0c3..ba9dd7a9 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -119150 \ No newline at end of file +97028 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 6223a09c..7e0b7c09 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -243720 \ No newline at end of file +201476 \ No newline at end of file diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol index 438ff205..424d9357 100644 --- a/src/base/BaseV4Quoter.sol +++ b/src/base/BaseV4Quoter.sol @@ -6,6 +6,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {QuoterRevert} from "../libraries/QuoterRevert.sol"; import {SqrtPriceLimitHelper} from "../libraries/SqrtPriceLimitHelper.sol"; import {SafeCallback} from "../base/SafeCallback.sol"; +import {CacheAmountSpecified} from "../libraries/CacheAmountSpecified.sol"; abstract contract BaseV4Quoter is SafeCallback { using SqrtPriceLimitHelper for uint160; @@ -17,9 +18,6 @@ abstract contract BaseV4Quoter is SafeCallback { constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} - /// @dev cache used to check a safety condition in exact output swaps. - uint128 internal amountOutCached; - /// @dev Only this address may call this function. Used to mimic internal functions, using an /// external call to catch and parse revert reasons modifier selfOnly() { @@ -27,6 +25,18 @@ abstract contract BaseV4Quoter is SafeCallback { _; } + function _clearAmountSpecified() internal { + CacheAmountSpecified.set(0); + } + + function _setAmountSpecified(uint256 amountSpecified) internal { + CacheAmountSpecified.set(amountSpecified); + } + + function _getAmountSpecified() internal returns (uint256 amountSpecified) { + return CacheAmountSpecified.get(); + } + function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { // Call this contract with the data in question. Each quote path (bool success, bytes memory returnData) = address(this).call(data); @@ -54,6 +64,7 @@ abstract contract BaseV4Quoter is SafeCallback { hookData ); // only exactOut case + uint256 amountOutCached = _getAmountSpecified(); if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? swapDelta.amount1() : swapDelta.amount0())) { revert InsufficientAmountOut(); diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 5313250b..c96072b2 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -52,7 +52,7 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; + if (params.sqrtPriceLimitX96 == 0) _clearAmountSpecified(); amountIn = reason.parseReturnData(); } } @@ -118,14 +118,14 @@ contract Quoter is IQuoter, BaseV4Quoter { for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; - amountOutCached = amountOut; + _setAmountSpecified(amountOut); (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); // always clear because sqrtPriceLimitX96 is set to 0 always - delete amountOutCached; + _clearAmountSpecified(); amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0()); @@ -138,7 +138,7 @@ contract Quoter is IQuoter, BaseV4Quoter { /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { // if no price limit has been specified, cache the output amount for comparison inside the _swap function - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; + if (params.sqrtPriceLimitX96 == 0) _setAmountSpecified(params.exactAmount); BalanceDelta swapDelta = _swap( params.poolKey, @@ -148,7 +148,7 @@ contract Quoter is IQuoter, BaseV4Quoter { params.hookData ); - if (amountOutCached != 0) delete amountOutCached; + _clearAmountSpecified(); // the input delta of a swap is negative so we must flip it uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); diff --git a/src/libraries/CacheAmountSpecified.sol b/src/libraries/CacheAmountSpecified.sol new file mode 100644 index 00000000..19ec0978 --- /dev/null +++ b/src/libraries/CacheAmountSpecified.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) +/// TODO: This library can be deleted when we have the transient keyword support in solidity. +library CacheAmountSpecified { + // bytes32(uint256(keccak256("AmountSpecified")) - 1) + bytes32 internal constant AMOUNT_SPECIFIED_SLOT = 0x040affe16a79096ebfed488ed052568637be7285d797c191b3a85e60cf8292f3; + + function set(uint256 amountSpecified) internal { + assembly ("memory-safe") { + tstore(AMOUNT_SPECIFIED_SLOT, amountSpecified) + } + } + + function get() internal view returns (uint256 amountSpecified) { + assembly ("memory-safe") { + amountSpecified := tload(AMOUNT_SPECIFIED_SLOT) + } + } +} From a3bc2d369fdce48a655efad62d1f8eaa668eef2f Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 12 Sep 2024 17:37:09 -0400 Subject: [PATCH 13/15] Remove cache entirely --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/base/BaseV4Quoter.sol | 27 ++++++------------- src/lens/Quoter.sol | 13 --------- src/libraries/CacheAmountSpecified.sol | 21 --------------- 16 files changed, 21 insertions(+), 66 deletions(-) delete mode 100644 src/libraries/CacheAmountSpecified.sol diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 497eafc6..f1e2356c 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -144148 \ No newline at end of file +144123 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 6fa583a8..77d6d312 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -149612 \ No newline at end of file +149574 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 50d21a77..adfa46b1 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -78941 \ No newline at end of file +78650 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index b81c758e..2b376aa5 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -83735 \ No newline at end of file +83458 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index 7fc89abd..ac6b0b7e 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -120674 \ No newline at end of file +120586 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index d515a0dc..997dcbc8 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -145583 \ No newline at end of file +145509 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index e1eb3f39..5d5ab28e 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -79606 \ No newline at end of file +79532 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 1921fe09..64e7d230 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -201436 \ No newline at end of file +201274 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 37535a7b..4724d942 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -120261 \ No newline at end of file +119921 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index 577e306e..a7931c62 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -150398 \ No newline at end of file +150058 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index 9497d424..c6edf293 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -120329 \ No newline at end of file +119989 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index ba9dd7a9..28192aa7 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -97028 \ No newline at end of file +96688 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 7e0b7c09..5325febe 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -201476 \ No newline at end of file +200769 \ No newline at end of file diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol index 424d9357..98b92948 100644 --- a/src/base/BaseV4Quoter.sol +++ b/src/base/BaseV4Quoter.sol @@ -6,13 +6,14 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {QuoterRevert} from "../libraries/QuoterRevert.sol"; import {SqrtPriceLimitHelper} from "../libraries/SqrtPriceLimitHelper.sol"; import {SafeCallback} from "../base/SafeCallback.sol"; -import {CacheAmountSpecified} from "../libraries/CacheAmountSpecified.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; abstract contract BaseV4Quoter is SafeCallback { using SqrtPriceLimitHelper for uint160; using QuoterRevert for *; + using PoolIdLibrary for PoolId; - error InsufficientAmountOut(); + error NotEnoughLiquidity(PoolId poolId); error LockFailure(); error NotSelf(); @@ -25,18 +26,6 @@ abstract contract BaseV4Quoter is SafeCallback { _; } - function _clearAmountSpecified() internal { - CacheAmountSpecified.set(0); - } - - function _setAmountSpecified(uint256 amountSpecified) internal { - CacheAmountSpecified.set(amountSpecified); - } - - function _getAmountSpecified() internal returns (uint256 amountSpecified) { - return CacheAmountSpecified.get(); - } - function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { // Call this contract with the data in question. Each quote path (bool success, bytes memory returnData) = address(this).call(data); @@ -63,11 +52,11 @@ abstract contract BaseV4Quoter is SafeCallback { }), hookData ); - // only exactOut case - uint256 amountOutCached = _getAmountSpecified(); - if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? swapDelta.amount1() : swapDelta.amount0())) - { - revert InsufficientAmountOut(); + + // Check that the pool was not illiquid. + int128 amountSpecifiedActual = (zeroForOne == (amountSpecified < 0)) ? swapDelta.amount0() : swapDelta.amount1(); + if (sqrtPriceLimitX96 == 0 && amountSpecifiedActual != amountSpecified) { + revert NotEnoughLiquidity(poolKey.toId()); } } } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index c96072b2..2d284f98 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -52,7 +52,6 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - if (params.sqrtPriceLimitX96 == 0) _clearAmountSpecified(); amountIn = reason.parseReturnData(); } } @@ -73,7 +72,6 @@ contract Quoter is IQuoter, BaseV4Quoter { /// @dev external function called within the _unlockCallback, to simulate an exact input swap, then revert with the result function _quoteExactInput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - BalanceDelta swapDelta; uint128 amountIn = params.exactAmount; Currency inputCurrency = params.exactCurrency; @@ -110,7 +108,6 @@ contract Quoter is IQuoter, BaseV4Quoter { /// @dev external function called within the _unlockCallback, to simulate an exact output swap, then revert with the result function _quoteExactOutput(QuoteExactParams calldata params) external selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - BalanceDelta swapDelta; uint128 amountOut = params.exactAmount; Currency outputCurrency = params.exactCurrency; @@ -118,15 +115,10 @@ contract Quoter is IQuoter, BaseV4Quoter { for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; - _setAmountSpecified(amountOut); - (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(outputCurrency); swapDelta = _swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData); - // always clear because sqrtPriceLimitX96 is set to 0 always - _clearAmountSpecified(); - amountOut = oneForZero ? uint128(-swapDelta.amount1()) : uint128(-swapDelta.amount0()); outputCurrency = pathKey.intermediateCurrency; @@ -137,9 +129,6 @@ contract Quoter is IQuoter, BaseV4Quoter { /// @dev external function called within the _unlockCallback, to simulate a single-hop exact output swap, then revert with the result function _quoteExactOutputSingle(QuoteExactSingleParams calldata params) external selfOnly returns (bytes memory) { - // if no price limit has been specified, cache the output amount for comparison inside the _swap function - if (params.sqrtPriceLimitX96 == 0) _setAmountSpecified(params.exactAmount); - BalanceDelta swapDelta = _swap( params.poolKey, params.zeroForOne, @@ -148,8 +137,6 @@ contract Quoter is IQuoter, BaseV4Quoter { params.hookData ); - _clearAmountSpecified(); - // the input delta of a swap is negative so we must flip it uint256 amountIn = params.zeroForOne ? uint128(-swapDelta.amount0()) : uint128(-swapDelta.amount1()); amountIn.revertQuote(); diff --git a/src/libraries/CacheAmountSpecified.sol b/src/libraries/CacheAmountSpecified.sol deleted file mode 100644 index 19ec0978..00000000 --- a/src/libraries/CacheAmountSpecified.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -/// @notice This is a temporary library that allows us to use transient storage (tstore/tload) -/// TODO: This library can be deleted when we have the transient keyword support in solidity. -library CacheAmountSpecified { - // bytes32(uint256(keccak256("AmountSpecified")) - 1) - bytes32 internal constant AMOUNT_SPECIFIED_SLOT = 0x040affe16a79096ebfed488ed052568637be7285d797c191b3a85e60cf8292f3; - - function set(uint256 amountSpecified) internal { - assembly ("memory-safe") { - tstore(AMOUNT_SPECIFIED_SLOT, amountSpecified) - } - } - - function get() internal view returns (uint256 amountSpecified) { - assembly ("memory-safe") { - amountSpecified := tload(AMOUNT_SPECIFIED_SLOT) - } - } -} From 44d9c73ecd180b0fecf31ac1fcf90dc3a9e4b49e Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 18 Sep 2024 13:29:11 -0400 Subject: [PATCH 14/15] PR comments --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/base/BaseV4Quoter.sol | 3 +- test/Quoter.t.sol | 112 ++++++++++++------ 15 files changed, 91 insertions(+), 50 deletions(-) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index f1e2356c..052e1a40 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -144123 \ No newline at end of file +144101 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 77d6d312..9057e8cd 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -149574 \ No newline at end of file +149552 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index adfa46b1..77a7322d 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -78650 \ No newline at end of file +78628 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index 2b376aa5..6be0052f 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -83458 \ No newline at end of file +83436 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index ac6b0b7e..c1f9fc28 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -120586 \ No newline at end of file +120564 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index 997dcbc8..f70c2947 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -145509 \ No newline at end of file +145487 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index 5d5ab28e..6be85920 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -79532 \ No newline at end of file +79510 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index 64e7d230..e1ade6fd 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -201274 \ No newline at end of file +201252 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 4724d942..1373ee33 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -119921 \ No newline at end of file +119899 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index a7931c62..662f89ad 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -150058 \ No newline at end of file +150036 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index c6edf293..a88bea04 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -119989 \ No newline at end of file +119967 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 28192aa7..746f9cca 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -96688 \ No newline at end of file +96666 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index 5325febe..fec8693a 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -200769 \ No newline at end of file +200747 \ No newline at end of file diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol index 98b92948..46b0763e 100644 --- a/src/base/BaseV4Quoter.sol +++ b/src/base/BaseV4Quoter.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-License-Identifier: UNLICENSED import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; @@ -30,7 +30,6 @@ abstract contract BaseV4Quoter is SafeCallback { // Call this contract with the data in question. Each quote path (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; - if (returnData.length == 0) revert LockFailure(); returnData.bubbleReason(); } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index d7fbf3a0..e4656eeb 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -85,7 +85,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; - (uint256 amountOut,) = quoter.quoteExactInputSingle( + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, @@ -96,6 +96,8 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactInputSingle_zeroForOne_multiplePositions"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, expectedAmountOut); } @@ -103,7 +105,7 @@ contract QuoterTest is Test, Deployers, GasSnapshot { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; - (uint256 amountOut,) = quoter.quoteExactInputSingle( + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, @@ -114,23 +116,20 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactInputSingle_oneForZero_multiplePositions"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, expectedAmountOut); } - // nested self-call into unlockCallback reverts - function testQuoter_callUnlockCallback_reverts() public { - vm.expectRevert(BaseV4Quoter.LockFailure.selector); - vm.prank(address(manager)); - quoter.unlockCallback(abi.encodeWithSelector(quoter.unlockCallback.selector, address(this), "0x")); - } - function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 9871); } @@ -142,8 +141,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 6143); } @@ -155,10 +156,12 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // -60 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_1TickLoaded"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 3971); } @@ -167,8 +170,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 8); } @@ -178,8 +183,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 8); } @@ -188,8 +195,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 9871); } @@ -201,10 +210,12 @@ contract QuoterTest is Test, Deployers, GasSnapshot { // 120 is an initialized tick for this pool. We check that we don't count it. IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_initializedAfter"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 6190); } @@ -215,10 +226,12 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_oneHop_startingInitialized"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 198); } @@ -228,8 +241,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 101); } @@ -238,7 +253,9 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 9871); } @@ -248,15 +265,17 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); - (uint256 amountOut,) = quoter.quoteExactInput(params); + (uint256 amountOut, uint256 gasEstimate) = quoter.quoteExactInput(params); snapLastCall("Quoter_quoteExactInput_twoHops"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountOut, 9745); } function testQuoter_quoteExactOutputSingle_0to1() public { - (uint256 amountIn,) = quoter.quoteExactOutputSingle( + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, @@ -267,11 +286,13 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactOutputSingle_zeroForOne"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 9981); } function testQuoter_quoteExactOutputSingle_1to0() public { - (uint256 amountIn,) = quoter.quoteExactOutputSingle( + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutputSingle( IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, @@ -282,6 +303,8 @@ contract QuoterTest is Test, Deployers, GasSnapshot { ); snapLastCall("Quoter_exactOutputSingle_oneForZero"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 9981); } @@ -290,10 +313,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token2); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_2TicksLoaded"); - + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 15273); } @@ -303,9 +327,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); - (uint256 amountIn,) = quoter.quoteExactOutput(params); - snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + snapLastCall("Quoter_quoteExactOutput_oneHop_initializedAfter"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 6200); } @@ -315,9 +341,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); - (uint256 amountIn,) = quoter.quoteExactOutput(params); - snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + snapLastCall("Quoter_quoteExactOutput_oneHop_1TickLoaded"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 4029); } @@ -329,9 +357,11 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_oneHop_startingInitialized"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 102); } @@ -341,8 +371,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 12); } @@ -351,8 +383,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 15273); } @@ -362,8 +396,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 6283); } @@ -372,8 +408,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 6055); } @@ -383,8 +421,10 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 10000); } @@ -395,10 +435,12 @@ contract QuoterTest is Test, Deployers, GasSnapshot { IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); - (uint256 amountIn,) = quoter.quoteExactOutput(params); + (uint256 amountIn, uint256 gasEstimate) = quoter.quoteExactOutput(params); snapLastCall("Quoter_quoteExactOutput_twoHops"); + assertGt(gasEstimate, 50000); + assertLt(gasEstimate, 400000); assertEq(amountIn, 10000); } From e84c8fcce490fa85f0ab7248bb66903b8fc2f098 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 18 Sep 2024 14:02:08 -0400 Subject: [PATCH 15/15] Refactor QuoterRevert library --- ...utSingle_oneForZero_multiplePositions.snap | 2 +- ...utSingle_zeroForOne_multiplePositions.snap | 2 +- .../Quoter_exactOutputSingle_oneForZero.snap | 2 +- .../Quoter_exactOutputSingle_zeroForOne.snap | 2 +- ...er_quoteExactInput_oneHop_1TickLoaded.snap | 2 +- ...oteExactInput_oneHop_initializedAfter.snap | 2 +- ...ExactInput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactInput_twoHops.snap | 2 +- ...r_quoteExactOutput_oneHop_1TickLoaded.snap | 2 +- ..._quoteExactOutput_oneHop_2TicksLoaded.snap | 2 +- ...teExactOutput_oneHop_initializedAfter.snap | 2 +- ...xactOutput_oneHop_startingInitialized.snap | 2 +- .../Quoter_quoteExactOutput_twoHops.snap | 2 +- src/base/BaseV4Quoter.sol | 7 ++-- src/lens/Quoter.sol | 12 ++++--- src/libraries/QuoterRevert.sol | 32 ++++++++----------- 16 files changed, 38 insertions(+), 39 deletions(-) diff --git a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap index 052e1a40..91241f69 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_oneForZero_multiplePositions.snap @@ -1 +1 @@ -144101 \ No newline at end of file +144072 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap index 9057e8cd..291cd4e2 100644 --- a/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap +++ b/.forge-snapshots/Quoter_exactInputSingle_zeroForOne_multiplePositions.snap @@ -1 +1 @@ -149552 \ No newline at end of file +149523 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap index 77a7322d..b490579a 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_oneForZero.snap @@ -1 +1 @@ -78628 \ No newline at end of file +78599 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap index 6be0052f..1c1a8332 100644 --- a/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap +++ b/.forge-snapshots/Quoter_exactOutputSingle_zeroForOne.snap @@ -1 +1 @@ -83436 \ No newline at end of file +83407 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap index c1f9fc28..312e60c3 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -120564 \ No newline at end of file +120493 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap index f70c2947..f2733a99 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_initializedAfter.snap @@ -1 +1 @@ -145487 \ No newline at end of file +145416 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap index 6be85920..fc994571 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_oneHop_startingInitialized.snap @@ -1 +1 @@ -79510 \ No newline at end of file +79439 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap index e1ade6fd..bb203fa9 100644 --- a/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactInput_twoHops.snap @@ -1 +1 @@ -201252 \ No newline at end of file +201179 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap index 1373ee33..c726cbe4 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_1TickLoaded.snap @@ -1 +1 @@ -119899 \ No newline at end of file +119828 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap index 662f89ad..28f6f8af 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_2TicksLoaded.snap @@ -1 +1 @@ -150036 \ No newline at end of file +149965 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap index a88bea04..9020efa6 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_initializedAfter.snap @@ -1 +1 @@ -119967 \ No newline at end of file +119896 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap index 746f9cca..3866f855 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_oneHop_startingInitialized.snap @@ -1 +1 @@ -96666 \ No newline at end of file +96595 \ No newline at end of file diff --git a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap index fec8693a..517efc01 100644 --- a/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap +++ b/.forge-snapshots/Quoter_quoteExactOutput_twoHops.snap @@ -1 +1 @@ -200747 \ No newline at end of file +200674 \ No newline at end of file diff --git a/src/base/BaseV4Quoter.sol b/src/base/BaseV4Quoter.sol index 46b0763e..29acf356 100644 --- a/src/base/BaseV4Quoter.sol +++ b/src/base/BaseV4Quoter.sol @@ -14,8 +14,8 @@ abstract contract BaseV4Quoter is SafeCallback { using PoolIdLibrary for PoolId; error NotEnoughLiquidity(PoolId poolId); - error LockFailure(); error NotSelf(); + error UnexpectedCallSuccess(); constructor(IPoolManager _poolManager) SafeCallback(_poolManager) {} @@ -27,9 +27,10 @@ abstract contract BaseV4Quoter is SafeCallback { } function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { - // Call this contract with the data in question. Each quote path (bool success, bytes memory returnData) = address(this).call(data); - if (success) return returnData; + // Every quote path gathers a quote, and then reverts either with QuoteSwap(quoteAmount) or alternative error + if (success) revert UnexpectedCallSuccess(); + // Bubble the revert string, whether a valid quote or an alternative error returnData.bubbleReason(); } diff --git a/src/lens/Quoter.sol b/src/lens/Quoter.sol index 2d284f98..06c5d2e4 100644 --- a/src/lens/Quoter.sol +++ b/src/lens/Quoter.sol @@ -26,7 +26,8 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactInputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - amountOut = reason.parseReturnData(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); } } @@ -39,7 +40,8 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactInput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - amountOut = reason.parseReturnData(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountOut = reason.parseQuoteAmount(); } } @@ -52,7 +54,8 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactOutputSingle, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - amountIn = reason.parseReturnData(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); } } @@ -65,7 +68,8 @@ contract Quoter is IQuoter, BaseV4Quoter { try poolManager.unlock(abi.encodeCall(this._quoteExactOutput, (params))) {} catch (bytes memory reason) { gasEstimate = gasBefore - gasleft(); - amountIn = reason.parseReturnData(); + // Extract the quote from QuoteSwap error, or throw if the quote failed + amountIn = reason.parseQuoteAmount(); } } diff --git a/src/libraries/QuoterRevert.sol b/src/libraries/QuoterRevert.sol index e7b11e15..bb0eda90 100644 --- a/src/libraries/QuoterRevert.sol +++ b/src/libraries/QuoterRevert.sol @@ -14,12 +14,14 @@ library QuoterRevert { error QuoteSwap(uint256 amount); /// @notice reverts, where the revert data is the provided bytes - /// @dev called when quoting, at the end of simulating a swap, to revert with the swap information as the revert reason - function revertQuote(uint256 amountUnspecified) internal pure { - revert QuoteSwap(amountUnspecified); + /// @dev called when quoting, to record the quote amount in an error + /// @dev QuoteSwap is used to differentiate this error from other errors thrown when simulating the swap + function revertQuote(uint256 quoteAmount) internal pure { + revert QuoteSwap(quoteAmount); } - /// @notice reverts, where the revert data is the provided bytes + /// @notice reverts using the revertData as the reason + /// @dev to bubble up both the valid QuoteSwap(amount) error, or an alternative error thrown during simulation function bubbleReason(bytes memory revertData) internal pure { // mload(revertData): the length of the revert data // add(revertData, 0x20): a pointer to the start of the revert data @@ -28,28 +30,20 @@ library QuoterRevert { } } - /// @notice check revert reasons are of the expected length; otherwise revert with different message - /// @dev called after a swap simulation reverts, to check if the revert was valid encoded quote information, or an internal issue - function validateRevertReason(bytes memory reason) internal pure { + /// @notice validates whether a revert reason is a valid swap quote or not + /// if valid, it decodes the quote to return. Otherwise it reverts. + function parseQuoteAmount(bytes memory reason) internal pure returns (uint256 quoteAmount) { + // If the error doesnt start with QuoteSwap, we know this isnt a valid quote to parse + // Instead it is another revert that was triggered somewhere in the simulation if (reason.parseSelector() != QuoteSwap.selector) { revert UnexpectedRevertBytes(reason); } - } - - /// @notice validates a received revert reason from a swap. - /// Then, if valid, decodes it into the information generated by a quote - function parseReturnData(bytes memory reason) internal pure returns (uint256) { - reason.validateRevertReason(); - return reason.parseAmountUnspecified(); - } - /// @notice extracts the amountUnspecified from an encoded QuoteSwap(amountUnspecified) - function parseAmountUnspecified(bytes memory reason) internal pure returns (uint256 amountUnspecified) { // reason -> reason+0x1f is the length of the reason string // reason+0x20 -> reason+0x23 is the selector of QuoteSwap - // reason+0x24 -> reason+0x43 is the amountUnspecified + // reason+0x24 -> reason+0x43 is the quoteAmount assembly ("memory-safe") { - amountUnspecified := mload(add(reason, 0x24)) + quoteAmount := mload(add(reason, 0x24)) } } }