From 6313eb5916e7ff78186a579043a9a337f2c7f633 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Sun, 5 Nov 2023 22:18:44 -0500 Subject: [PATCH 01/80] add PoolTicksCounter library --- contracts/libraries/PoolTicksCounter.sol | 100 +++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 contracts/libraries/PoolTicksCounter.sol diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol new file mode 100644 index 00000000..86d61eee --- /dev/null +++ b/contracts/libraries/PoolTicksCounter.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.8.20; + +import {PoolGetters} from "./PoolGetters.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; + +library PoolTicksCounter { + using PoolIdLibrary for PoolKey; + + /// @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. + function countInitializedTicksCrossed(IPoolManager self, PoolKey calldata key, int24 tickBefore, int24 tickAfter) + internal + view + returns (uint32 initializedTicksCrossed) + { + int16 wordPosLower; + int16 wordPosHigher; + uint8 bitPosLower; + uint8 bitPosHigher; + bool tickBeforeInitialized; + bool tickAfterInitialized; + + { + // 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 = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); + 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 = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); + tickBeforeInitialized = + ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); + + if (wordPos < wordPosAfter || (wordPos == wordPosAfter && bitPos <= bitPosAfter)) { + wordPosLower = wordPos; + bitPosLower = bitPos; + wordPosHigher = wordPosAfter; + bitPosHigher = bitPosAfter; + } else { + wordPosLower = wordPosAfter; + bitPosLower = bitPosAfter; + wordPosHigher = wordPos; + 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 << bitPosLower; + while (wordPosLower <= wordPosHigher) { + // If we're on the final tick bitmap page, ensure we only count up to our + // ending tick. + if (wordPosLower == wordPosHigher) { + mask = mask & (type(uint256).max >> (255 - bitPosHigher)); + } + + uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); + uint256 masked = bmLower & mask; + initializedTicksCrossed += countOneBits(masked); + wordPosLower++; + // Reset our mask so we consider all bits on the next iteration. + mask = type(uint256).max; + } + + if (tickAfterInitialized) { + initializedTicksCrossed -= 1; + } + + if (tickBeforeInitialized) { + initializedTicksCrossed -= 1; + } + + return initializedTicksCrossed; + } + + function countOneBits(uint256 x) private pure returns (uint16) { + uint16 bits = 0; + while (x != 0) { + bits++; + x &= (x - 1); + } + return bits; + } +} From 664c4ff866782c8427da47ce435a1cf9cbc3c53f Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 7 Nov 2023 17:26:20 -0500 Subject: [PATCH 02/80] quoter exact input single --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/interfaces/IQuoter.sol | 49 +++++ contracts/lens/Quoter.sol | 187 ++++++++++++++++++ contracts/libraries/LiquidityAmounts.sol | 2 +- contracts/libraries/PoolTicksCounter.sol | 2 +- contracts/libraries/SwapIntention.sol | 60 ++++++ foundry.toml | 3 +- 36 files changed, 330 insertions(+), 33 deletions(-) create mode 100644 contracts/interfaces/IQuoter.sol create mode 100644 contracts/lens/Quoter.sol create mode 100644 contracts/libraries/SwapIntention.sol diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 9463411b..a08fb8e1 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2687 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 638f8744..bb219663 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -21068 \ No newline at end of file +22933 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 1bc3059d..6eb59a1d 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -21318 \ No newline at end of file +23180 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index a5bb2393..94c197e9 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2076 \ No newline at end of file +2738 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index db768f3a..75080690 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -20164 \ No newline at end of file +21892 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index c04b75bb..9b54c31b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -20458 \ No newline at end of file +22191 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 7f966954..2a55d550 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 2d5250a5..990a22fa 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -412696 \ No newline at end of file +431353 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 032a6a3b..3d96bed8 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -206962 \ No newline at end of file +225332 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 9d59ac16..b1094056 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -154763 \ No newline at end of file +174871 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index e0b3ab13..e6875555 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -879542 \ No newline at end of file +1077037 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 920384a4..9b3066bd 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200095 \ No newline at end of file +220999 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 5ee38978..25da1231 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379287 \ No newline at end of file +396109 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 436848b5..bc2fb863 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -112303 \ No newline at end of file +130538 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index d48620c7..3a5a8708 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -153038 \ No newline at end of file +173088 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 61763356..f484e31f 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -233028 \ No newline at end of file +254660 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 4f1264df..83917a8d 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -223717 \ No newline at end of file +245360 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 3d85d6d7..8f98b8b1 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -32886 \ No newline at end of file +54869 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index bc6dc069..ee2ae68d 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -23586 \ No newline at end of file +45569 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index da81ec04..1e8b26e0 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -51411 \ No newline at end of file +72316 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index f61a3565..a695bf26 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -5571 \ No newline at end of file +6492 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 7f966954..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 7f966954..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 41599c5d..5265bba3 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -75965 \ No newline at end of file +86878 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 7f966954..2a55d550 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index 9463411b..a08fb8e1 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2687 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index 0b1caa8d..d0974c4f 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -5746 \ No newline at end of file +6684 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index bee097af..05796bbf 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -5277 \ No newline at end of file +6193 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a51f76e9..ed8dd329 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -2615 \ No newline at end of file +3382 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 9adc49a6..203df22d 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123576 \ No newline at end of file +145807 \ No newline at end of file diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol new file mode 100644 index 00000000..26ac10ed --- /dev/null +++ b/contracts/interfaces/IQuoter.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {ExactInputSingleParams} from "../libraries/SwapIntention.sol"; + +interface IQuoter { + error InvalidQuoteType(); + error UnexpectedRevertBytes(); + //function quoteExactInput(Path.PathKey calldata path, uint256 amountIn) + // external + // returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksList); + + // enum QuoteType { + // ExactInput, + // ExactInputSingle, + // ExactOutput, + // ExactOutputSingle + // } + + // struct QuoteInfo { + // QuoteType quoteType; + // address swapper; + // bytes params; + // } + + // struct QuoteCallBackData { + // address swapper; + // PoolKey key; + // IPoolManager.SwapParams params; + // bytes hookData; + // } + + // struct QuoteExactInputSingleParams { + // address swapper; + // Currency tokenIn; + // Currency tokenOut; + // PoolKey poolKey; + // uint256 amountIn; + // uint160 sqrtPriceLimitX96; + // bytes hookData; + // } + + function quoteExactInputSingle(ExactInputSingleParams calldata params) + external + returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); +} diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol new file mode 100644 index 00000000..e61751ec --- /dev/null +++ b/contracts/lens/Quoter.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.20; + +import {IQuoter} from "../interfaces/IQuoter.sol"; +import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; +import {SwapInfo, SwapType, ExactInputSingleParams} from "../libraries/SwapIntention.sol"; +import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; +import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; + +contract Quoter is IQuoter { + using PoolIdLibrary for PoolKey; + using SafeCast for *; + using Hooks for IHooks; + + // v4 Singleton contract + IPoolManager poolManager; + + constructor(address _poolManager) { + poolManager = IPoolManager(_poolManager); + } + + function fillSlot0(PoolId id) private view returns (Pool.Slot0 memory slot0) { + //TODO: extsload when storage is stable + (slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(id); + + return slot0; + } + + /* + struct QuoteCallBackData { + address swapper; + PoolKey key; + IPoolManager.SwapParams params; + bytes hookData; + } + struct QuoteExactInputSingleParams { + address swapper; + Currency tokenIn; + Currency tokenOut; + PoolKey poolKey; + uint256 amountIn; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + struct SwapParams { + int24 tickSpacing; + bool zeroForOne; + int256 amountSpecified; + uint160 sqrtPriceLimitX96; + } + */ + function parseRevertReason(bytes memory reason) + private + pure + returns (uint256 amount, uint160 sqrtPriceX96After, int24 tickAfter) + { + if (reason.length != 96) { + if (reason.length < 68) { + revert UnexpectedRevertBytes(); + } + assembly { + reason := add(reason, 0x04) + } + revert(abi.decode(reason, (string))); + } + return abi.decode(reason, (uint256, uint160, int24)); + } + + function handleRevert(bytes memory reason, PoolKey memory poolKey) + private + view + returns (uint256 amount, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + { + int24 tickBefore; + int24 tickAfter; + (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); + (amount, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); + + initializedTicksCrossed = + PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); + + return (amount, sqrtPriceX96After, initializedTicksCrossed); + } + + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { + require(msg.sender == address(poolManager)); + + SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); + + if (swapInfo.swapType == SwapType.ExactInputSingle) { + _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + } else { + revert InvalidQuoteType(); + } + } + + /* + struct SwapInfo { + SwapType swapType; + address swapper; + bytes params; + } + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + */ + function quoteExactInputSingle(ExactInputSingleParams memory params) + external + override + returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + { + try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} + catch (bytes memory reason) { + return handleRevert(reason, params.poolKey); + } + } + + /* + struct PathKey { + Currency intermediateCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + bytes hookData; + } + + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + + struct IPoolManager.SwapParams { + bool zeroForOne; + int256 amountSpecified; + uint160 sqrtPriceLimitX96; + } + */ + function _quoteExactInputSingle(ExactInputSingleParams memory params) + private + returns (uint256 amountOut, uint160 sqrtPriceX96After, int24 tickAfter) + { + BalanceDelta delta = poolManager.swap( + params.poolKey, + IPoolManager.SwapParams({ + zeroForOne: params.zeroForOne, + amountSpecified: int256(int128(params.amountIn)), + sqrtPriceLimitX96: params.sqrtPriceLimitX96 == 0 + ? params.zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + : params.sqrtPriceLimitX96 + }), + params.hookData + ); + + (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); + if (params.zeroForOne) { + amountOut = uint128(-delta.amount1()); + } else { + amountOut = uint128(-delta.amount0()); + } + + assembly { + let ptr := mload(0x40) + mstore(ptr, amountOut) + mstore(add(ptr, 0x20), sqrtPriceX96After) + mstore(add(ptr, 0x40), tickAfter) + revert(ptr, 96) + } + } +} diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol index b2c8b54c..6a908ad0 100644 --- a/contracts/libraries/LiquidityAmounts.sol +++ b/contracts/libraries/LiquidityAmounts.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import "@uniswap/v4-core/contracts/libraries/FullMath.sol"; import "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index 86d61eee..c327fa9b 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -13,7 +13,7 @@ library PoolTicksCounter { /// 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. - function countInitializedTicksCrossed(IPoolManager self, PoolKey calldata key, int24 tickBefore, int24 tickAfter) + function countInitializedTicksCrossed(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) internal view returns (uint32 initializedTicksCrossed) diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol new file mode 100644 index 00000000..85afc98f --- /dev/null +++ b/contracts/libraries/SwapIntention.sol @@ -0,0 +1,60 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; + +enum SwapType { + ExactInput, + ExactInputSingle, + ExactOutput, + ExactOutputSingle +} + +struct SwapInfo { + SwapType swapType; + bytes params; +} + +struct PathKey { + Currency intermediateCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + bytes hookData; +} + +struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint160 sqrtPriceLimitX96; + bytes hookData; +} + +struct ExactInputParams { + Currency currencyIn; + PathKey[] path; + address recipient; + uint128 amountIn; +} + +struct ExactOutputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountOut; + uint160 sqrtPriceLimitX96; + bytes hookData; +} + +struct ExactOutputParams { + Currency currencyOut; + PathKey[] path; + address recipient; + uint128 amountOut; + uint160 sqrtPriceLimitX96; +} diff --git a/foundry.toml b/foundry.toml index b3132187..4db6363c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,8 @@ src = 'contracts' out = 'foundry-out' solc_version = '0.8.20' -optimizer_runs = 800 +via_ir = true +optimizer_runs = 1000000 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] From d67f59721c7f306b6e4dfb245855ab73ab2e2b8a Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 7 Nov 2023 17:27:04 -0500 Subject: [PATCH 03/80] quoter test --- test/Quoter.t.sol | 184 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 test/Quoter.t.sol diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol new file mode 100644 index 00000000..d458fb84 --- /dev/null +++ b/test/Quoter.t.sol @@ -0,0 +1,184 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import "forge-std/console.sol"; +import {Test} from "forge-std/Test.sol"; +import {ExactInputSingleParams} from "../contracts/libraries/SwapIntention.sol"; +import {Quoter} from "../contracts/lens/Quoter.sol"; +import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; + +contract QuoterTest is Test, Deployers { + using SafeCast for *; + + // Min tick for full range with tick spacing of 60 + int24 internal constant MIN_TICK = -887220; + // Max tick for full range with tick spacing of 60 + int24 internal constant MAX_TICK = -MIN_TICK; + + Quoter quoter; + + PoolManager manager; + PoolModifyPositionTest positionManager; + + MockERC20 token0; + MockERC20 token1; + MockERC20 token2; + + PoolKey key01; + PoolKey key02; + + function setUp() public { + manager = new PoolManager(500000); + quoter = new Quoter(address(manager)); + positionManager = new PoolModifyPositionTest(manager); + + token0 = new MockERC20("Test0", "0", 18); + token0.mint(address(this), 2 ** 128); + token1 = new MockERC20("Test1", "1", 18); + token1.mint(address(this), 2 ** 128); + token2 = new MockERC20("Test2", "2", 18); + token2.mint(address(this), 2 ** 128); + + key01 = createPoolKey(token0, token1, address(0)); + key02 = createPoolKey(token0, token2, address(0)); + setupPool(key01); + setupPoolMultiplePositions(key02); + } + + function testQuoter_noHook_quoteExactInputSingle_zeroForOne_SinglePosition() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + // uint256 prevBalance0 = token0.balanceOf(address(this)); + // uint256 prevBalance1 = token1.balanceOf(address(this)); + + ExactInputSingleParams memory params = ExactInputSingleParams({ + poolKey: key01, + zeroForOne: true, + recipient: address(this), + amountIn: uint128(amountIn), + sqrtPriceLimitX96: 0, + hookData: ZERO_BYTES + }); + + (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = + quoter.quoteExactInputSingle(params); + + console.log(sqrtPriceX96After); + assertEq(amountOut, expectedAmountOut); + assertEq(initializedTicksCrossed, 0); + } + + function testQuoter_noHook_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; + + (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + ExactInputSingleParams({ + poolKey: key02, + zeroForOne: true, + recipient: address(this), + amountIn: uint128(amountIn), + sqrtPriceLimitX96: 0, + hookData: ZERO_BYTES + }) + ); + + assertEq(amountOut, expectedAmountOut); + assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); + assertEq(initializedTicksCrossed, 2); + } + + function testQuoter_noHook_quoteExactInputSingle_OneForZero_MultiplePositions() public { + uint256 amountIn = 10000; + uint256 expectedAmountOut = 9871; + uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; + + (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + ExactInputSingleParams({ + poolKey: key02, + zeroForOne: false, + recipient: address(this), + amountIn: uint128(amountIn), + sqrtPriceLimitX96: 0, + hookData: ZERO_BYTES + }) + ); + + assertEq(amountOut, expectedAmountOut); + assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); + assertEq(initializedTicksCrossed, 2); + } + + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) + internal + pure + returns (PoolKey memory) + { + if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); + return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); + } + + function setupPool(PoolKey memory poolKey) internal { + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, IPoolManager.ModifyPositionParams(MIN_TICK, MAX_TICK, 200 ether), ZERO_BYTES + ); + } + + function setupPoolMultiplePositions(PoolKey memory poolKey) internal { + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -60, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -60, 60, 100, 100).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -120, 120, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 120, 100, 100).toInt256() + ), + ZERO_BYTES + ); + } + + function calculateLiquidityFromAmounts( + uint160 sqrtRatioX96, + int24 tickLower, + int24 tickUpper, + uint256 amount0, + uint256 amount1 + ) internal pure returns (uint128 liquidity) { + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); + liquidity = + LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); + } +} From ee84418fbcd619af8b5a3dd16dfc7ccd17289a5f Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 10 Nov 2023 18:50:46 -0500 Subject: [PATCH 04/80] return deltas instead --- contracts/interfaces/IQuoter.sol | 2 +- contracts/lens/Quoter.sol | 50 ++++++++++++++++++-------------- test/Quoter.t.sol | 14 +++++---- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 26ac10ed..227b7524 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -45,5 +45,5 @@ interface IQuoter { function quoteExactInputSingle(ExactInputSingleParams calldata params) external - returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); + returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index e61751ec..7c6a8988 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "forge-std/console.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {SwapInfo, SwapType, ExactInputSingleParams} from "../libraries/SwapIntention.sol"; @@ -60,10 +61,11 @@ contract Quoter is IQuoter { */ function parseRevertReason(bytes memory reason) private - pure - returns (uint256 amount, uint160 sqrtPriceX96After, int24 tickAfter) + view + returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, int24 tickAfter) { - if (reason.length != 96) { + if (reason.length != 128) { + // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes if (reason.length < 68) { revert UnexpectedRevertBytes(); } @@ -72,23 +74,25 @@ contract Quoter is IQuoter { } revert(abi.decode(reason, (string))); } - return abi.decode(reason, (uint256, uint160, int24)); + console.logBytes(reason); + return abi.decode(reason, (int128, int128, uint160, int24)); } function handleRevert(bytes memory reason, PoolKey memory poolKey) private view - returns (uint256 amount, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { int24 tickBefore; int24 tickAfter; (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - (amount, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); + (amount0Delta, amount1Delta, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); + console.log("after parse"); initializedTicksCrossed = PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); - return (amount, sqrtPriceX96After, initializedTicksCrossed); + return (amount0Delta, amount1Delta, sqrtPriceX96After, initializedTicksCrossed); } function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { @@ -121,7 +125,7 @@ contract Quoter is IQuoter { function quoteExactInputSingle(ExactInputSingleParams memory params) external override - returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { @@ -153,10 +157,7 @@ contract Quoter is IQuoter { uint160 sqrtPriceLimitX96; } */ - function _quoteExactInputSingle(ExactInputSingleParams memory params) - private - returns (uint256 amountOut, uint160 sqrtPriceX96After, int24 tickAfter) - { + function _quoteExactInputSingle(ExactInputSingleParams memory params) private { BalanceDelta delta = poolManager.swap( params.poolKey, IPoolManager.SwapParams({ @@ -169,19 +170,24 @@ contract Quoter is IQuoter { params.hookData ); - (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); - if (params.zeroForOne) { - amountOut = uint128(-delta.amount1()); - } else { - amountOut = uint128(-delta.amount0()); - } + (uint160 sqrtPriceX96After, int24 tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); + int128 amount0Delta = delta.amount0(); + int128 amount1Delta = delta.amount1(); + console.logInt(amount0Delta); + console.logInt(amount1Delta); + // if (params.zeroForOne) { + // amountOut = uint128(-delta.amount1()); + // } else { + // amountOut = uint128(-delta.amount0()); + // } assembly { let ptr := mload(0x40) - mstore(ptr, amountOut) - mstore(add(ptr, 0x20), sqrtPriceX96After) - mstore(add(ptr, 0x40), tickAfter) - revert(ptr, 96) + mstore(ptr, amount0Delta) + mstore(add(ptr, 0x20), amount1Delta) + mstore(add(ptr, 0x40), sqrtPriceX96After) + mstore(add(ptr, 0x60), tickAfter) + revert(ptr, 128) } } } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index d458fb84..fcbed150 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -72,11 +72,11 @@ contract QuoterTest is Test, Deployers { hookData: ZERO_BYTES }); - (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = + (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle(params); console.log(sqrtPriceX96After); - assertEq(amountOut, expectedAmountOut); + assertEq(uint128(-amount1Delta), expectedAmountOut); assertEq(initializedTicksCrossed, 0); } @@ -85,7 +85,8 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: true, @@ -96,7 +97,7 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(amountOut, expectedAmountOut); + assertEq(uint128(-amount1Delta), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } @@ -106,7 +107,8 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: false, @@ -117,7 +119,7 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(amountOut, expectedAmountOut); + assertEq(uint128(-amount1Delta), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } From d68e4646bfab4619d474ae64fd2e9eb5acd65207 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 10 Nov 2023 19:03:02 -0500 Subject: [PATCH 05/80] safe casting to correct types --- contracts/lens/Quoter.sol | 17 +++++++++-------- test/Quoter.t.sol | 5 ++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 7c6a8988..686aa947 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -61,7 +61,7 @@ contract Quoter is IQuoter { */ function parseRevertReason(bytes memory reason) private - view + pure returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, int24 tickAfter) { if (reason.length != 128) { @@ -74,8 +74,12 @@ contract Quoter is IQuoter { } revert(abi.decode(reason, (string))); } - console.logBytes(reason); - return abi.decode(reason, (int128, int128, uint160, int24)); + (int256 _amount0Delta, int256 _amount1Delta, uint160 _sqrtPriceX96After, int24 _tickAfter) = + abi.decode(reason, (int128, int128, uint160, int24)); + amount0Delta = _amount0Delta.toInt128(); + amount1Delta = _amount1Delta.toInt128(); + sqrtPriceX96After = _sqrtPriceX96After; + tickAfter = _tickAfter; } function handleRevert(bytes memory reason, PoolKey memory poolKey) @@ -87,7 +91,6 @@ contract Quoter is IQuoter { int24 tickAfter; (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); (amount0Delta, amount1Delta, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); - console.log("after parse"); initializedTicksCrossed = PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); @@ -171,10 +174,8 @@ contract Quoter is IQuoter { ); (uint160 sqrtPriceX96After, int24 tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); - int128 amount0Delta = delta.amount0(); - int128 amount1Delta = delta.amount1(); - console.logInt(amount0Delta); - console.logInt(amount1Delta); + int256 amount0Delta = int256(delta.amount0()); + int256 amount1Delta = int256(delta.amount1()); // if (params.zeroForOne) { // amountOut = uint128(-delta.amount1()); // } else { diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index fcbed150..fade9889 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -107,8 +107,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter - .quoteExactInputSingle( + (int128 amount0Delta,, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: false, @@ -119,7 +118,7 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(uint128(-amount1Delta), expectedAmountOut); + assertEq(uint128(-amount0Delta), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } From bc381b03052e6988d6a8e035961ff3fb85c23fda Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Sat, 11 Nov 2023 10:51:56 -0500 Subject: [PATCH 06/80] QuoteExactInput skeleton --- contracts/lens/Quoter.sol | 53 ++++++++++++++++----------- contracts/libraries/SwapIntention.sol | 15 ++++++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 686aa947..88c39532 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; import "forge-std/console.sol"; +import "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; -import {SwapInfo, SwapType, ExactInputSingleParams} from "../libraries/SwapIntention.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; @@ -105,26 +105,13 @@ contract Quoter is IQuoter { if (swapInfo.swapType == SwapType.ExactInputSingle) { _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + } else if (swapInfo.swapType == SwapType.ExactInput) { + _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); } else { revert InvalidQuoteType(); } } - /* - struct SwapInfo { - SwapType swapType; - address swapper; - bytes params; - } - struct ExactInputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountIn; - uint160 sqrtPriceLimitX96; - bytes hookData; - } - */ function quoteExactInputSingle(ExactInputSingleParams memory params) external override @@ -136,6 +123,35 @@ contract Quoter is IQuoter { } } + function quoteExactInput(ExactInputParams memory params) + external + override + returns (int128[] deltaAmounts, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList) + { + try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + catch (bytes memory reason) { + return handleRevert(reason, params.poolKey); + } + } + + function _quoteExactInput(ExactInputParams memory params) private { + uint256 pathLength = params.path.length; + BalanceDelta memory deltas; + for (uint256 i = 0; i < pathLength; i++) { + (PoolKey memory poolKey, bool zeroForOne) = + SwapIntention.getPoolAndSwapDirection(params.path[i], params.currencyIn); + ExactInputSingleParams singleParams = ExactInputSingleParams({ + poolKey: poolKey, + zeroForOne: zeroForOne, + recipient: params.recipient, + amountIn: params.amountIn, + sqrtPriceLimitX96: 0, + hookData: params.path[i].hookData + }); + deltas = _quoteExactInputSingle(params); + } + } + /* struct PathKey { Currency intermediateCurrency; @@ -176,11 +192,6 @@ contract Quoter is IQuoter { (uint160 sqrtPriceX96After, int24 tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); int256 amount0Delta = int256(delta.amount0()); int256 amount1Delta = int256(delta.amount1()); - // if (params.zeroForOne) { - // amountOut = uint128(-delta.amount1()); - // } else { - // amountOut = uint128(-delta.amount0()); - // } assembly { let ptr := mload(0x40) diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index 85afc98f..bf4106ba 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -58,3 +58,18 @@ struct ExactOutputParams { uint128 amountOut; uint160 sqrtPriceLimitX96; } + +library SwapIntention { + function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) + private + pure + returns (PoolKey memory poolKey, bool zeroForOne) + { + (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency + ? (currencyIn, params.intermediateCurrency) + : (params.intermediateCurrency, currencyIn); + + zeroForOne = currencyIn == currency0; + poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + } +} From 3bd47d96b96bf8c80d075a23861f55b43e589272 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Sat, 11 Nov 2023 17:18:33 -0500 Subject: [PATCH 07/80] multiple entries --- contracts/interfaces/IQuoter.sol | 3 +- contracts/lens/Quoter.sol | 114 ++++++++++++++----------------- test/Quoter.t.sol | 14 ++-- 3 files changed, 62 insertions(+), 69 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 227b7524..42603956 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; @@ -45,5 +46,5 @@ interface IQuoter { function quoteExactInputSingle(ExactInputSingleParams calldata params) external - returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 88c39532..e7740921 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -36,35 +36,12 @@ contract Quoter is IQuoter { return slot0; } - /* - struct QuoteCallBackData { - address swapper; - PoolKey key; - IPoolManager.SwapParams params; - bytes hookData; - } - struct QuoteExactInputSingleParams { - address swapper; - Currency tokenIn; - Currency tokenOut; - PoolKey poolKey; - uint256 amountIn; - uint160 sqrtPriceLimitX96; - bytes hookData; - } - struct SwapParams { - int24 tickSpacing; - bool zeroForOne; - int256 amountSpecified; - uint160 sqrtPriceLimitX96; - } - */ function parseRevertReason(bytes memory reason) private pure - returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, int24 tickAfter) + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - if (reason.length != 128) { + if (reason.length != 96) { // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes if (reason.length < 68) { revert UnexpectedRevertBytes(); @@ -74,28 +51,21 @@ contract Quoter is IQuoter { } revert(abi.decode(reason, (string))); } - (int256 _amount0Delta, int256 _amount1Delta, uint160 _sqrtPriceX96After, int24 _tickAfter) = - abi.decode(reason, (int128, int128, uint160, int24)); - amount0Delta = _amount0Delta.toInt128(); - amount1Delta = _amount1Delta.toInt128(); - sqrtPriceX96After = _sqrtPriceX96After; - tickAfter = _tickAfter; + return abi.decode(reason, (BalanceDelta, uint160, int24)); } function handleRevert(bytes memory reason, PoolKey memory poolKey) private view - returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { int24 tickBefore; int24 tickAfter; (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - (amount0Delta, amount1Delta, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); + (deltas, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); initializedTicksCrossed = PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); - - return (amount0Delta, amount1Delta, sqrtPriceX96After, initializedTicksCrossed); } function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { @@ -104,9 +74,18 @@ contract Quoter is IQuoter { SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInputSingle) { - _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); - } else if (swapInfo.swapType == SwapType.ExactInput) { - _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = + _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + console.logInt(deltas.amount0()); + assembly { + let ptr := mload(0x40) + mstore(ptr, deltas) + mstore(add(ptr, 0x20), sqrtPriceX96After) + mstore(add(ptr, 0x40), tickAfter) + revert(ptr, 96) + } + // } else if (swapInfo.swapType == SwapType.ExactInput) { + // _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); } else { revert InvalidQuoteType(); } @@ -115,11 +94,11 @@ contract Quoter is IQuoter { function quoteExactInputSingle(ExactInputSingleParams memory params) external override - returns (int128 amount0Delta, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { - return handleRevert(reason, params.poolKey); + return handleRevert(reason, SwapType.ExactInSingle, params.poolKey); } } @@ -134,21 +113,42 @@ contract Quoter is IQuoter { } } - function _quoteExactInput(ExactInputParams memory params) private { + function _quoteExactInput(ExactInputParams memory params) + private + returns ( + BalanceDelta[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) + { uint256 pathLength = params.path.length; - BalanceDelta memory deltas; + BalanceDelta prevDeltas; + boolean prevZeroForOne; + + deltaAmounts = new BalanceDelta[](pathLength); + sqrtPriceX96AfterList = new uint160[](pathLength); + initializedTicksCrossedList = new uint32[](pathLength); + for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = - SwapIntention.getPoolAndSwapDirection(params.path[i], params.currencyIn); - ExactInputSingleParams singleParams = ExactInputSingleParams({ + (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection( + params.path[i], i == 0 ? params.currencyIn : params.path[i - 1].intermediateCurrency + ); + + int128 curAmountIn = + i == 0 ? params.amountIn : (prevZeroForOne ? -prevDeltas.amount1() : -prevDeltas.amount0()); + + ExactInputSingleParams memory singleParams = ExactInputSingleParams({ poolKey: poolKey, zeroForOne: zeroForOne, recipient: params.recipient, - amountIn: params.amountIn, + amountIn: cureAmountIn, sqrtPriceLimitX96: 0, hookData: params.path[i].hookData }); - deltas = _quoteExactInputSingle(params); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(params); + + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + tickAfterList } } @@ -176,8 +176,11 @@ contract Quoter is IQuoter { uint160 sqrtPriceLimitX96; } */ - function _quoteExactInputSingle(ExactInputSingleParams memory params) private { - BalanceDelta delta = poolManager.swap( + function _quoteExactInputSingle(ExactInputSingleParams memory params) + private + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) + { + deltas = poolManager.swap( params.poolKey, IPoolManager.SwapParams({ zeroForOne: params.zeroForOne, @@ -189,17 +192,6 @@ contract Quoter is IQuoter { params.hookData ); - (uint160 sqrtPriceX96After, int24 tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); - int256 amount0Delta = int256(delta.amount0()); - int256 amount1Delta = int256(delta.amount1()); - - assembly { - let ptr := mload(0x40) - mstore(ptr, amount0Delta) - mstore(add(ptr, 0x20), amount1Delta) - mstore(add(ptr, 0x40), sqrtPriceX96After) - mstore(add(ptr, 0x60), tickAfter) - revert(ptr, 128) - } + (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); } } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index fade9889..0f06ebb4 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -8,6 +8,7 @@ import {ExactInputSingleParams} from "../contracts/libraries/SwapIntention.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -72,11 +73,11 @@ contract QuoterTest is Test, Deployers { hookData: ZERO_BYTES }); - (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = + (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle(params); console.log(sqrtPriceX96After); - assertEq(uint128(-amount1Delta), expectedAmountOut); + assertEq(uint128(-deltas.amount1()), expectedAmountOut); assertEq(initializedTicksCrossed, 0); } @@ -85,8 +86,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (, int128 amount1Delta, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter - .quoteExactInputSingle( + (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: true, @@ -97,7 +97,7 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(uint128(-amount1Delta), expectedAmountOut); + assertEq(uint128(-deltas.amount1()), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } @@ -107,7 +107,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (int128 amount0Delta,, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: false, @@ -118,7 +118,7 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(uint128(-amount0Delta), expectedAmountOut); + assertEq(uint128(-deltas.amount0()), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } From f75b87aba950c2d8fc89fbdcc5d347a9b6b03c3c Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Sat, 11 Nov 2023 21:07:28 -0500 Subject: [PATCH 08/80] break handleRevert by type --- contracts/interfaces/IQuoter.sol | 1 + contracts/lens/Quoter.sol | 115 +++++++++++++++++-------------- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 42603956..0b250036 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -9,6 +9,7 @@ import {ExactInputSingleParams} from "../libraries/SwapIntention.sol"; interface IQuoter { error InvalidQuoteType(); + error InvalidQuoteTypeInRevert(); error UnexpectedRevertBytes(); //function quoteExactInput(Path.PathKey calldata path, uint256 amountIn) // external diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index e7740921..e8c669a8 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -54,7 +54,19 @@ contract Quoter is IQuoter { return abi.decode(reason, (BalanceDelta, uint160, int24)); } - function handleRevert(bytes memory reason, PoolKey memory poolKey) + function handleRevert(bytes memory reason, SwapType swapType, PoolKey memory poolKey) + private + view + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + { + if (swapType == SwapType.ExactInputSingle) { + return _handleRevertExactInputSingle(reason, poolKey); + } else { + revert InvalidQuoteTypeInRevert(); + } + } + + function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) private view returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) @@ -76,7 +88,6 @@ contract Quoter is IQuoter { if (swapInfo.swapType == SwapType.ExactInputSingle) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); - console.logInt(deltas.amount0()); assembly { let ptr := mload(0x40) mstore(ptr, deltas) @@ -98,59 +109,59 @@ contract Quoter is IQuoter { { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { - return handleRevert(reason, SwapType.ExactInSingle, params.poolKey); + return handleRevert(reason, SwapType.ExactInputSingle, params.poolKey); } } - function quoteExactInput(ExactInputParams memory params) - external - override - returns (int128[] deltaAmounts, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList) - { - try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} - catch (bytes memory reason) { - return handleRevert(reason, params.poolKey); - } - } - - function _quoteExactInput(ExactInputParams memory params) - private - returns ( - BalanceDelta[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList - ) - { - uint256 pathLength = params.path.length; - BalanceDelta prevDeltas; - boolean prevZeroForOne; - - deltaAmounts = new BalanceDelta[](pathLength); - sqrtPriceX96AfterList = new uint160[](pathLength); - initializedTicksCrossedList = new uint32[](pathLength); - - for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection( - params.path[i], i == 0 ? params.currencyIn : params.path[i - 1].intermediateCurrency - ); - - int128 curAmountIn = - i == 0 ? params.amountIn : (prevZeroForOne ? -prevDeltas.amount1() : -prevDeltas.amount0()); - - ExactInputSingleParams memory singleParams = ExactInputSingleParams({ - poolKey: poolKey, - zeroForOne: zeroForOne, - recipient: params.recipient, - amountIn: cureAmountIn, - sqrtPriceLimitX96: 0, - hookData: params.path[i].hookData - }); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(params); - - sqrtPriceX96AfterList[i] = sqrtPriceX96After; - tickAfterList - } - } + // function quoteExactInput(ExactInputParams memory params) + // external + // override + // returns (int128[] deltaAmounts, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList) + // { + // try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + // catch (bytes memory reason) { + // return handleRevert(reason, SwapType.ExactInput, params.poolKey); + // } + // } + + // function _quoteExactInput(ExactInputParams memory params) + // private + // returns ( + // BalanceDelta[] memory deltaAmounts, + // uint160[] memory sqrtPriceX96AfterList, + // uint32[] memory initializedTicksCrossedList + // ) + // { + // uint256 pathLength = params.path.length; + // BalanceDelta prevDeltas; + // boolean prevZeroForOne; + + // deltaAmounts = new BalanceDelta[](pathLength); + // sqrtPriceX96AfterList = new uint160[](pathLength); + // initializedTicksCrossedList = new uint32[](pathLength); + + // for (uint256 i = 0; i < pathLength; i++) { + // (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection( + // params.path[i], i == 0 ? params.currencyIn : params.path[i - 1].intermediateCurrency + // ); + + // int128 curAmountIn = + // i == 0 ? params.amountIn : (prevZeroForOne ? -prevDeltas.amount1() : -prevDeltas.amount0()); + + // ExactInputSingleParams memory singleParams = ExactInputSingleParams({ + // poolKey: poolKey, + // zeroForOne: zeroForOne, + // recipient: params.recipient, + // amountIn: cureAmountIn, + // sqrtPriceLimitX96: 0, + // hookData: params.path[i].hookData + // }); + // (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(params); + + // sqrtPriceX96AfterList[i] = sqrtPriceX96After; + // tickAfterList + // } + // } /* struct PathKey { From a69c7823d6489b986f6a98f422008979f90963ae Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 10:11:31 -0500 Subject: [PATCH 09/80] quoteExactInput and unit tests --- contracts/interfaces/IQuoter.sol | 17 +- contracts/lens/Quoter.sol | 256 +++++++++++++++++--------- contracts/libraries/SwapIntention.sol | 2 +- test/Quoter.t.sol | 220 ++++++++++++++++++++-- 4 files changed, 394 insertions(+), 101 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 0b250036..8b9341ef 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "../libraries/SwapIntention.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {ExactInputSingleParams} from "../libraries/SwapIntention.sol"; interface IQuoter { error InvalidQuoteType(); @@ -45,7 +45,20 @@ interface IQuoter { // bytes hookData; // } + struct NonZeroDeltaCurrency { + Currency currency; + int128 deltaAmount; + } + function quoteExactInputSingle(ExactInputSingleParams calldata params) external - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); + + function quoteExactInput(ExactInputParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index e8c669a8..7a7da00c 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console.sol"; +import "forge-std/console2.sol"; import "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; @@ -36,12 +36,8 @@ contract Quoter is IQuoter { return slot0; } - function parseRevertReason(bytes memory reason) - private - pure - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) - { - if (reason.length != 96) { + function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { + if (reason.length < 96) { // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes if (reason.length < 68) { revert UnexpectedRevertBytes(); @@ -51,35 +47,42 @@ contract Quoter is IQuoter { } revert(abi.decode(reason, (string))); } - return abi.decode(reason, (BalanceDelta, uint160, int24)); - } - - function handleRevert(bytes memory reason, SwapType swapType, PoolKey memory poolKey) - private - view - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) - { - if (swapType == SwapType.ExactInputSingle) { - return _handleRevertExactInputSingle(reason, poolKey); - } else { - revert InvalidQuoteTypeInRevert(); - } + return reason; } function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) private view - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { int24 tickBefore; int24 tickAfter; + BalanceDelta deltas; + deltaAmounts = new int128[](2); (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - (deltas, sqrtPriceX96After, tickAfter) = parseRevertReason(reason); + reason = validateRevertReason(reason); + (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); initializedTicksCrossed = PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); } + function _handleRevertExactInput(bytes memory reason) + private + pure + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) + { + reason = validateRevertReason(reason); + (deltaAmounts, sqrtPriceX96AfterList, initializedTicksCrossedList) = + abi.decode(reason, (int128[], uint160[], uint32[])); + } + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { require(msg.sender == address(poolManager)); @@ -95,8 +98,72 @@ contract Quoter is IQuoter { mstore(add(ptr, 0x40), tickAfter) revert(ptr, 96) } - // } else if (swapInfo.swapType == SwapType.ExactInput) { - // _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + } else if (swapInfo.swapType == SwapType.ExactInput) { + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + + assembly { + // function storeArray(offset, length) -> result { + // switch exponent + // case 0 { result := 1 } + // case 1 { result := base } + // default { + // result := power(mul(base, base), div(exponent, 2)) + // switch mod(exponent, 2) + // case 1 { result := mul(base, result) } + // } + // } + + let originalPtr := mload(0x40) + let ptr := mload(0x40) + + let deltaLength := mload(deltaAmounts) + let sqrtPriceLength := mload(sqrtPriceX96AfterList) + let initializedTicksLength := mload(initializedTicksCrossedList) + + let deltaOffset := 0x60 + let sqrtPriceOffset := add(deltaOffset, add(0x20, mul(0x20, deltaLength))) + let initializedTicksOffset := add(sqrtPriceOffset, add(0x20, mul(0x20, sqrtPriceLength))) + + // storing offsets to dynamic arrays + mstore(ptr, deltaOffset) + ptr := add(ptr, 0x20) + mstore(ptr, sqrtPriceOffset) + ptr := add(ptr, 0x20) + mstore(ptr, initializedTicksOffset) + ptr := add(ptr, 0x20) + + // storing length + contents of dynamic arrays + mstore(ptr, deltaLength) + ptr := add(ptr, 0x20) + for { let i := 0 } lt(i, deltaLength) { i := add(i, 1) } { + let value := mload(add(deltaAmounts, add(mul(i, 0x20), 0x20))) + mstore(ptr, value) + ptr := add(ptr, 0x20) + } + + mstore(ptr, sqrtPriceLength) + ptr := add(ptr, 0x20) + for { let i := 0 } lt(i, sqrtPriceLength) { i := add(i, 1) } { + let value := mload(add(sqrtPriceX96AfterList, add(mul(i, 0x20), 0x20))) + mstore(ptr, value) + ptr := add(ptr, 0x20) + } + + mstore(ptr, initializedTicksLength) + ptr := add(ptr, 0x20) + for { let i := 0 } lt(i, initializedTicksLength) { i := add(i, 1) } { + let value := mload(add(initializedTicksCrossedList, add(mul(i, 0x20), 0x20))) + mstore(ptr, value) + ptr := add(ptr, 0x20) + } + + revert(originalPtr, sub(ptr, originalPtr)) + //revert(ptr, add(mul(0x20, add(add(deltaLength, sqrtPriceLength), initializedTicksLength)), 0x60)) + } } else { revert InvalidQuoteType(); } @@ -105,63 +172,69 @@ contract Quoter is IQuoter { function quoteExactInputSingle(ExactInputSingleParams memory params) external override - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { - return handleRevert(reason, SwapType.ExactInputSingle, params.poolKey); + return _handleRevertExactInputSingle(reason, params.poolKey); } } - // function quoteExactInput(ExactInputParams memory params) - // external - // override - // returns (int128[] deltaAmounts, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList) - // { - // try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} - // catch (bytes memory reason) { - // return handleRevert(reason, SwapType.ExactInput, params.poolKey); - // } - // } - - // function _quoteExactInput(ExactInputParams memory params) - // private - // returns ( - // BalanceDelta[] memory deltaAmounts, - // uint160[] memory sqrtPriceX96AfterList, - // uint32[] memory initializedTicksCrossedList - // ) - // { - // uint256 pathLength = params.path.length; - // BalanceDelta prevDeltas; - // boolean prevZeroForOne; - - // deltaAmounts = new BalanceDelta[](pathLength); - // sqrtPriceX96AfterList = new uint160[](pathLength); - // initializedTicksCrossedList = new uint32[](pathLength); - - // for (uint256 i = 0; i < pathLength; i++) { - // (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection( - // params.path[i], i == 0 ? params.currencyIn : params.path[i - 1].intermediateCurrency - // ); - - // int128 curAmountIn = - // i == 0 ? params.amountIn : (prevZeroForOne ? -prevDeltas.amount1() : -prevDeltas.amount0()); - - // ExactInputSingleParams memory singleParams = ExactInputSingleParams({ - // poolKey: poolKey, - // zeroForOne: zeroForOne, - // recipient: params.recipient, - // amountIn: cureAmountIn, - // sqrtPriceLimitX96: 0, - // hookData: params.path[i].hookData - // }); - // (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(params); - - // sqrtPriceX96AfterList[i] = sqrtPriceX96After; - // tickAfterList - // } - // } + function quoteExactInput(ExactInputParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) + { + try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + catch (bytes memory reason) { + return _handleRevertExactInput(reason); + } + } + + function _quoteExactInput(ExactInputParams memory params) + private + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) + { + uint256 pathLength = params.path.length; + + //mapping(Currency currency => int128) memory currencyDeltas; + + deltaAmounts = new int128[](pathLength + 1); + sqrtPriceX96AfterList = new uint160[](pathLength); + initializedTicksCrossedList = new uint32[](pathLength); + + for (uint256 i = 0; i < pathLength; i++) { + (PoolKey memory poolKey, bool zeroForOne) = + SwapIntention.getPoolAndSwapDirection(params.path[i], params.currencyIn); + (, int24 tickBefore,,) = poolManager.getSlot0(poolKey.toId()); + + ExactInputSingleParams memory singleParams = ExactInputSingleParams({ + poolKey: poolKey, + zeroForOne: zeroForOne, + recipient: params.recipient, + amountIn: params.amountIn, + sqrtPriceLimitX96: 0, + hookData: params.path[i].hookData + }); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(singleParams); + + deltaAmounts[i] += zeroForOne ? curDeltas.amount0() : curDeltas.amount1(); + deltaAmounts[i + 1] += zeroForOne ? curDeltas.amount1() : curDeltas.amount0(); + + params.amountIn = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); + params.currencyIn = params.path[i].intermediateCurrency; + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + initializedTicksCrossedList[i] = + PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); + } + } /* struct PathKey { @@ -191,18 +264,33 @@ contract Quoter is IQuoter { private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - deltas = poolManager.swap( + return _quoteExact( params.poolKey, - IPoolManager.SwapParams({ - zeroForOne: params.zeroForOne, - amountSpecified: int256(int128(params.amountIn)), - sqrtPriceLimitX96: params.sqrtPriceLimitX96 == 0 - ? params.zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 - : params.sqrtPriceLimitX96 - }), + params.zeroForOne, + int256(int128(params.amountIn)), + params.sqrtPriceLimitX96, params.hookData ); + } - (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(params.poolKey.toId()); + function _quoteExact( + PoolKey memory poolKey, + bool zeroForOne, + int256 amountSpecified, + uint160 sqrtPriceLimitX96, + bytes memory hookData + ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { + deltas = poolManager.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: zeroForOne, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: sqrtPriceLimitX96 == 0 + ? zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + : sqrtPriceLimitX96 + }), + hookData + ); + (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(poolKey.toId()); } } diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index bf4106ba..fc270b76 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -61,7 +61,7 @@ struct ExactOutputParams { library SwapIntention { function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) - private + internal pure returns (PoolKey memory poolKey, bool zeroForOne) { diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 0f06ebb4..d4b6f8e0 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.20; import "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; -import {ExactInputSingleParams} from "../contracts/libraries/SwapIntention.sol"; +import "../contracts/libraries/SwapIntention.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; @@ -38,6 +38,9 @@ contract QuoterTest is Test, Deployers { PoolKey key01; PoolKey key02; + PoolKey key12; + + MockERC20[] tokenPath; function setUp() public { manager = new PoolManager(500000); @@ -53,17 +56,16 @@ contract QuoterTest is Test, Deployers { key01 = createPoolKey(token0, token1, address(0)); key02 = createPoolKey(token0, token2, address(0)); + key12 = createPoolKey(token1, token2, address(0)); setupPool(key01); + setupPool(key12); setupPoolMultiplePositions(key02); } - function testQuoter_noHook_quoteExactInputSingle_zeroForOne_SinglePosition() public { + function testQuoter_quoteExactInputSingle_zeroForOne_SinglePosition() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - // uint256 prevBalance0 = token0.balanceOf(address(this)); - // uint256 prevBalance1 = token1.balanceOf(address(this)); - ExactInputSingleParams memory params = ExactInputSingleParams({ poolKey: key01, zeroForOne: true, @@ -73,20 +75,21 @@ contract QuoterTest is Test, Deployers { hookData: ZERO_BYTES }); - (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle(params); - console.log(sqrtPriceX96After); - assertEq(uint128(-deltas.amount1()), expectedAmountOut); + assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); + assertEq(sqrtPriceX96After, 78835169195823159145205102899); assertEq(initializedTicksCrossed, 0); } - function testQuoter_noHook_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { + function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: true, @@ -97,17 +100,18 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(uint128(-deltas.amount1()), expectedAmountOut); + assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } - function testQuoter_noHook_quoteExactInputSingle_OneForZero_MultiplePositions() public { + function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (BalanceDelta deltas, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter.quoteExactInputSingle( + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, zeroForOne: false, @@ -118,11 +122,140 @@ contract QuoterTest is Test, Deployers { }) ); - assertEq(uint128(-deltas.amount0()), expectedAmountOut); + assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); assertEq(initializedTicksCrossed, 2); } + function testQuoter_quoteExactInput_1hop_SinglePosition() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(token0); + tokenPath.push(token1); + + ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); + assertEq(sqrtPriceX96AfterList[0], 78835169195823159145205102899); + assertEq(initializedTicksCrossedList[0], 0); + } + + function testQuoter_quoteExactInput_2Hops_0TickCrossed() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 984211133872795298; + + tokenPath.push(token0); + tokenPath.push(token1); + tokenPath.push(token2); + ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[2]), expectedAmountOut); + assertEq(sqrtPriceX96AfterList[0], 78835169195823159145205102899); + assertEq(sqrtPriceX96AfterList[1], 79619976852750192506445279985); + assertEq(initializedTicksCrossedList[0], 0); + assertEq(initializedTicksCrossedList[1], 0); + } + + function testQuoter_quoteExactInput_0to2_2TicksCrossed() public { + tokenPath.push(token0); + tokenPath.push(token2); + ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 9871); + assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); + assertEq(initializedTicksCrossedList[0], 2); + } + + function testQuoter_quoteExactInput_0to2_2TicksCrossed_initialiedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -120. + // -120 is an initialized tick for this pool. We check that we don't count it. + ExactInputParams memory params = getExactInputParams(tokenPath, 6200); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 6143); + assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); + assertEq(initializedTicksCrossedList[0], 1); + } + + function testQuoter_quoteExactInput_0to2_1TicksCrossed() public { + tokenPath.push(token0); + tokenPath.push(token2); + + // The swap amount is set such that the active tick after the swap is -60. + // -60 is an initialized tick for this pool. We check that we don't count it. + ExactInputParams memory params = getExactInputParams(tokenPath, 4000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 3971); + assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); + assertEq(initializedTicksCrossedList[0], 1); + } + + function testQuoter_quoteExactInput_0to2_0TicksCrossed_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + ExactInputParams memory params = getExactInputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); + assertEq(initializedTicksCrossedList[0], 0); + } + + function testQuoter_quoteExactInput_0to2_0TicksCrossed_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + ExactInputParams memory params = getExactInputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(uint128(-deltaAmounts[1]), 8); + assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); + assertEq(initializedTicksCrossedList[0], 1); + } + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) internal pure @@ -170,6 +303,35 @@ contract QuoterTest is Test, Deployers { ); } + function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + 0, 60, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, 0, 60, 100, 100).toInt256() + ), + ZERO_BYTES + ); + positionManager.modifyPosition( + poolKey, + IPoolManager.ModifyPositionParams( + -120, 0, calculateLiquidityFromAmounts(SQRT_RATIO_1_1, -120, 0, 100, 100).toInt256() + ), + ZERO_BYTES + ); + } + function calculateLiquidityFromAmounts( uint160 sqrtRatioX96, int24 tickLower, @@ -182,4 +344,34 @@ contract QuoterTest is Test, Deployers { liquidity = LiquidityAmounts.getLiquidityForAmounts(sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1); } + + function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) + internal + view + returns (ExactInputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); + } + + params.currencyIn = Currency.wrap(address(_tokenPath[0])); + params.path = path; + params.recipient = address(this); + params.amountIn = uint128(amountIn); + } + + function logTicksCrossed(uint32[] memory num) private view { + console.logString("=== Num Ticks Crossed ==="); + for (uint256 i = 0; i < num.length; i++) { + console.logUint(num[i]); + } + } + + function logSqrtPrices(uint160[] memory prices) private view { + console.logString("=== Sqrt Prices After ==="); + for (uint256 i = 0; i < prices.length; i++) { + console.logUint(prices[i]); + } + } } From 4f9519f977676c66660fe8176578afa7815dd562 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 17:17:09 -0500 Subject: [PATCH 10/80] more QuoteExactInput tests --- contracts/lens/Quoter.sol | 24 ----- test/Quoter.t.sol | 210 ++++++++++++++++++++++++++------------ 2 files changed, 142 insertions(+), 92 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 7a7da00c..475b231b 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -236,30 +236,6 @@ contract Quoter is IQuoter { } } - /* - struct PathKey { - Currency intermediateCurrency; - uint24 fee; - int24 tickSpacing; - IHooks hooks; - bytes hookData; - } - - struct ExactInputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountIn; - uint160 sqrtPriceLimitX96; - bytes hookData; - } - - struct IPoolManager.SwapParams { - bool zeroForOne; - int256 amountSpecified; - uint160 sqrtPriceLimitX96; - } - */ function _quoteExactInputSingle(ExactInputSingleParams memory params) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index d4b6f8e0..73768a9f 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -13,6 +13,7 @@ import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; @@ -21,6 +22,7 @@ import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; contract QuoterTest is Test, Deployers { using SafeCast for *; + using PoolIdLibrary for PoolKey; // Min tick for full range with tick spacing of 60 int24 internal constant MIN_TICK = -887220; @@ -47,9 +49,12 @@ contract QuoterTest is Test, Deployers { quoter = new Quoter(address(manager)); positionManager = new PoolModifyPositionTest(manager); - token0 = new MockERC20("Test0", "0", 18); + // salts are chose so that address(token0) < address(token2) && address(1) < address(token2) + bytes32 salt1 = "ffff"; + bytes32 salt2 = "gm"; + token0 = new MockERC20{salt: salt1}("Test0", "0", 18); token0.mint(address(this), 2 ** 128); - token1 = new MockERC20("Test1", "1", 18); + token1 = new MockERC20{salt: salt2}("Test1", "1", 18); token1.mint(address(this), 2 ** 128); token2 = new MockERC20("Test2", "2", 18); token2.mint(address(this), 2 ** 128); @@ -62,27 +67,6 @@ contract QuoterTest is Test, Deployers { setupPoolMultiplePositions(key02); } - function testQuoter_quoteExactInputSingle_zeroForOne_SinglePosition() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - ExactInputSingleParams memory params = ExactInputSingleParams({ - poolKey: key01, - zeroForOne: true, - recipient: address(this), - amountIn: uint128(amountIn), - sqrtPriceLimitX96: 0, - hookData: ZERO_BYTES - }); - - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = - quoter.quoteExactInputSingle(params); - - assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); - assertEq(sqrtPriceX96After, 78835169195823159145205102899); - assertEq(initializedTicksCrossed, 0); - } - function testQuoter_quoteExactInputSingle_ZeroForOne_MultiplePositions() public { uint256 amountIn = 10000; uint256 expectedAmountOut = 9871; @@ -127,48 +111,6 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksCrossed, 2); } - function testQuoter_quoteExactInput_1hop_SinglePosition() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 992054607780215625; - - tokenPath.push(token0); - tokenPath.push(token1); - - ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); - assertEq(sqrtPriceX96AfterList[0], 78835169195823159145205102899); - assertEq(initializedTicksCrossedList[0], 0); - } - - function testQuoter_quoteExactInput_2Hops_0TickCrossed() public { - uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 984211133872795298; - - tokenPath.push(token0); - tokenPath.push(token1); - tokenPath.push(token2); - ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); - - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList - ) = quoter.quoteExactInput(params); - - assertEq(uint128(-deltaAmounts[2]), expectedAmountOut); - assertEq(sqrtPriceX96AfterList[0], 78835169195823159145205102899); - assertEq(sqrtPriceX96AfterList[1], 79619976852750192506445279985); - assertEq(initializedTicksCrossedList[0], 0); - assertEq(initializedTicksCrossedList[1], 0); - } - function testQuoter_quoteExactInput_0to2_2TicksCrossed() public { tokenPath.push(token0); tokenPath.push(token2); @@ -252,10 +194,124 @@ contract QuoterTest is Test, Deployers { ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); - assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); + assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); assertEq(initializedTicksCrossedList[0], 1); } + function testQuoter_quoteExactInput_2to0_2TicksCrossed() public { + tokenPath.push(token2); + tokenPath.push(token0); + ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 9871); + assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); + assertEq(initializedTicksCrossedList[0], 2); + } + + function testQuoter_quoteExactInput_2to0_2TicksCrossed_initialiedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + // The swap amount is set such that the active tick after the swap is 120. + // 120 is an initialized tick for this pool. We check that we don't count it. + ExactInputParams memory params = getExactInputParams(tokenPath, 6250); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 6190); + assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); + assertEq(initializedTicksCrossedList[0], 2); + } + + function testQuoter_quoteExactInput_2to0_0TicksCrossed_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token2); + tokenPath.push(token0); + ExactInputParams memory params = getExactInputParams(tokenPath, 200); + + // Tick 0 initialized. Tick after = 1 + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 198); + assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); + assertEq(initializedTicksCrossedList[0], 0); + } + + // 2->0 starting not initialized + function testQuoter_quoteExactInput_2to0_0TicksCrossed_startingNotInitialized() public { + tokenPath.push(token2); + tokenPath.push(token0); + ExactInputParams memory params = getExactInputParams(tokenPath, 103); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + assertEq(-deltaAmounts[1], 101); + assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); + assertEq(initializedTicksCrossedList[0], 0); + } + + function testQuoter_quoteExactInput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + console.logString("===== testQuoter_quoteExactInput_2to1 ======"); + console.logString("Token2: "); + console.logAddress(address(token2)); + console.logString("Token1: "); + console.logAddress(address(token1)); + ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + logDeltas(deltaAmounts); + assertEq(-deltaAmounts[1], 9871); + logSqrtPrices(sqrtPriceX96AfterList); + assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); + assertEq(initializedTicksCrossedList[0], 0); + } + + function testQuoter_quoteExactInput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList + ) = quoter.quoteExactInput(params); + + logDeltas(deltaAmounts); + logSqrtPrices(sqrtPriceX96AfterList); + + assertEq(-deltaAmounts[2], 9745); + assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); + assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); + assertEq(initializedTicksCrossedList[0], 2); + assertEq(initializedTicksCrossedList[1], 0); + } + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) internal pure @@ -270,7 +326,13 @@ contract QuoterTest is Test, Deployers { MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyPosition( - poolKey, IPoolManager.ModifyPositionParams(MIN_TICK, MAX_TICK, 200 ether), ZERO_BYTES + poolKey, + IPoolManager.ModifyPositionParams( + MIN_TICK, + MAX_TICK, + calculateLiquidityFromAmounts(SQRT_RATIO_1_1, MIN_TICK, MAX_TICK, 1000000, 1000000).toInt256() + ), + ZERO_BYTES ); } @@ -304,7 +366,12 @@ contract QuoterTest is Test, Deployers { } function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + PoolId poolId = poolKey.toId(); + (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); + if (sqrtPriceX96 == 0) { + manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + } + MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyPosition( @@ -374,4 +441,11 @@ contract QuoterTest is Test, Deployers { console.logUint(prices[i]); } } + + function logDeltas(int128[] memory deltas) private view { + console.logString("=== Delta Amounts ==="); + for (uint256 i = 0; i < deltas.length; i++) { + console.logInt(deltas[i]); + } + } } From 7823167483945c69ba51bdcbccc5f64f0c5b5393 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 17:25:44 -0500 Subject: [PATCH 11/80] remove lgos --- contracts/lens/Quoter.sol | 20 +++++++++----------- test/Quoter.t.sol | 10 ---------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 475b231b..4d908454 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -30,7 +30,7 @@ contract Quoter is IQuoter { } function fillSlot0(PoolId id) private view returns (Pool.Slot0 memory slot0) { - //TODO: extsload when storage is stable + //TODO: extsload when storage is stable? (slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(id); return slot0; @@ -106,14 +106,13 @@ contract Quoter is IQuoter { ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); assembly { - // function storeArray(offset, length) -> result { - // switch exponent - // case 0 { result := 1 } - // case 1 { result := base } - // default { - // result := power(mul(base, base), div(exponent, 2)) - // switch mod(exponent, 2) - // case 1 { result := mul(base, result) } + // function storeArray(offset, length, array) { + // mstore(offset, length) + // offset := add(offset, 0x20) + // for { let i := 0 } lt(i, length) { i := add(i, 1) } { + // let value := mload(add(array, add(mul(i, 0x20), 0x20))) + // mstore(offset, value) + // offset := add(offset, 0x20) // } // } @@ -136,7 +135,7 @@ contract Quoter is IQuoter { mstore(ptr, initializedTicksOffset) ptr := add(ptr, 0x20) - // storing length + contents of dynamic arrays + //storing length + contents of dynamic arrays mstore(ptr, deltaLength) ptr := add(ptr, 0x20) for { let i := 0 } lt(i, deltaLength) { i := add(i, 1) } { @@ -162,7 +161,6 @@ contract Quoter is IQuoter { } revert(originalPtr, sub(ptr, originalPtr)) - //revert(ptr, add(mul(0x20, add(add(deltaLength, sqrtPriceLength), initializedTicksLength)), 0x60)) } } else { revert InvalidQuoteType(); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 73768a9f..73b1d4ff 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -271,11 +271,6 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to1() public { tokenPath.push(token2); tokenPath.push(token1); - console.logString("===== testQuoter_quoteExactInput_2to1 ======"); - console.logString("Token2: "); - console.logAddress(address(token2)); - console.logString("Token1: "); - console.logAddress(address(token1)); ExactInputParams memory params = getExactInputParams(tokenPath, 10000); ( @@ -283,9 +278,7 @@ contract QuoterTest is Test, Deployers { uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList ) = quoter.quoteExactInput(params); - logDeltas(deltaAmounts); assertEq(-deltaAmounts[1], 9871); - logSqrtPrices(sqrtPriceX96AfterList); assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); assertEq(initializedTicksCrossedList[0], 0); } @@ -302,9 +295,6 @@ contract QuoterTest is Test, Deployers { uint32[] memory initializedTicksCrossedList ) = quoter.quoteExactInput(params); - logDeltas(deltaAmounts); - logSqrtPrices(sqrtPriceX96AfterList); - assertEq(-deltaAmounts[2], 9745); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); From e2d0c22c10e4e378c1242a92ee71026c7ac91723 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 17:34:30 -0500 Subject: [PATCH 12/80] remove commented out struct --- contracts/interfaces/IQuoter.sol | 33 -------------------------------- 1 file changed, 33 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 8b9341ef..7e4068d5 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -11,39 +11,6 @@ interface IQuoter { error InvalidQuoteType(); error InvalidQuoteTypeInRevert(); error UnexpectedRevertBytes(); - //function quoteExactInput(Path.PathKey calldata path, uint256 amountIn) - // external - // returns (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksList); - - // enum QuoteType { - // ExactInput, - // ExactInputSingle, - // ExactOutput, - // ExactOutputSingle - // } - - // struct QuoteInfo { - // QuoteType quoteType; - // address swapper; - // bytes params; - // } - - // struct QuoteCallBackData { - // address swapper; - // PoolKey key; - // IPoolManager.SwapParams params; - // bytes hookData; - // } - - // struct QuoteExactInputSingleParams { - // address swapper; - // Currency tokenIn; - // Currency tokenOut; - // PoolKey poolKey; - // uint256 amountIn; - // uint160 sqrtPriceLimitX96; - // bytes hookData; - // } struct NonZeroDeltaCurrency { Currency currency; From 6ba70f4c9fb765b28891576f8120b4eb732738ff Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 18:03:08 -0500 Subject: [PATCH 13/80] via-ir in ci --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index 4db6363c..b6685766 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,5 +9,6 @@ fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] [profile.ci] fuzz_runs = 100000 +via_ir = true # See more config options https://github.com/foundry-rs/foundry/tree/master/config From b093a788eb528809aa79b68d8ec66d1b37d1f290 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 10:47:26 -0500 Subject: [PATCH 14/80] remove unused imports/functions --- contracts/interfaces/IQuoter.sol | 3 - contracts/lens/Quoter.sol | 106 ++++++++++++++----------------- foundry.toml | 1 - 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 7e4068d5..3b3d38f1 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -2,10 +2,7 @@ pragma solidity ^0.8.20; import "../libraries/SwapIntention.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; interface IQuoter { error InvalidQuoteType(); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 4d908454..c81d54f1 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -19,68 +19,38 @@ import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; - using SafeCast for *; using Hooks for IHooks; // v4 Singleton contract - IPoolManager poolManager; + IPoolManager immutable poolManager; constructor(address _poolManager) { poolManager = IPoolManager(_poolManager); } - function fillSlot0(PoolId id) private view returns (Pool.Slot0 memory slot0) { - //TODO: extsload when storage is stable? - (slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(id); - - return slot0; - } - - function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { - if (reason.length < 96) { - // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes - if (reason.length < 68) { - revert UnexpectedRevertBytes(); - } - assembly { - reason := add(reason, 0x04) - } - revert(abi.decode(reason, (string))); - } - return reason; - } - - function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) - private - view + function quoteExactInputSingle(ExactInputSingleParams memory params) + external + override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { - int24 tickBefore; - int24 tickAfter; - BalanceDelta deltas; - deltaAmounts = new int128[](2); - (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); - reason = validateRevertReason(reason); - (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); - deltaAmounts[0] = deltas.amount0(); - deltaAmounts[1] = deltas.amount1(); - - initializedTicksCrossed = - PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); + try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} + catch (bytes memory reason) { + return _handleRevertExactInputSingle(reason, params.poolKey); + } } - function _handleRevertExactInput(bytes memory reason) - private - pure + function quoteExactInput(ExactInputParams memory params) + external returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList ) { - reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96AfterList, initializedTicksCrossedList) = - abi.decode(reason, (int128[], uint160[], uint32[])); + try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + catch (bytes memory reason) { + return _handleRevertExactInput(reason); + } } function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { @@ -167,29 +137,51 @@ contract Quoter is IQuoter { } } - function quoteExactInputSingle(ExactInputSingleParams memory params) - external - override + function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { + if (reason.length < 96) { + // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes + if (reason.length < 68) { + revert UnexpectedRevertBytes(); + } + assembly { + reason := add(reason, 0x04) + } + revert(abi.decode(reason, (string))); + } + return reason; + } + + function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) + private + view returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) { - try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} - catch (bytes memory reason) { - return _handleRevertExactInputSingle(reason, params.poolKey); - } + int24 tickBefore; + int24 tickAfter; + BalanceDelta deltas; + deltaAmounts = new int128[](2); + (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); + reason = validateRevertReason(reason); + (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); + + initializedTicksCrossed = + PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); } - function quoteExactInput(ExactInputParams memory params) - external + function _handleRevertExactInput(bytes memory reason) + private + pure returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList ) { - try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} - catch (bytes memory reason) { - return _handleRevertExactInput(reason); - } + reason = validateRevertReason(reason); + (deltaAmounts, sqrtPriceX96AfterList, initializedTicksCrossedList) = + abi.decode(reason, (int128[], uint160[], uint32[])); } function _quoteExactInput(ExactInputParams memory params) diff --git a/foundry.toml b/foundry.toml index b6685766..4db6363c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,5 @@ fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] [profile.ci] fuzz_runs = 100000 -via_ir = true # See more config options https://github.com/foundry-rs/foundry/tree/master/config From 69c2c5bb801ce75458847b6a9e374aa60bdb3ae5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 10:55:32 -0500 Subject: [PATCH 15/80] store iteration params locally instead of editing function input --- contracts/lens/Quoter.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index c81d54f1..490e3724 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -194,32 +194,34 @@ contract Quoter is IQuoter { { uint256 pathLength = params.path.length; - //mapping(Currency currency => int128) memory currencyDeltas; - deltaAmounts = new int128[](pathLength + 1); sqrtPriceX96AfterList = new uint160[](pathLength); initializedTicksCrossedList = new uint32[](pathLength); + Currency prevCurrencyOut; + uint128 prevAmountOut; for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = - SwapIntention.getPoolAndSwapDirection(params.path[i], params.currencyIn); + SwapIntention.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); (, int24 tickBefore,,) = poolManager.getSlot0(poolKey.toId()); ExactInputSingleParams memory singleParams = ExactInputSingleParams({ poolKey: poolKey, zeroForOne: zeroForOne, recipient: params.recipient, - amountIn: params.amountIn, + amountIn: i == 0 ? params.amountIn : prevAmountOut, sqrtPriceLimitX96: 0, hookData: params.path[i].hookData }); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(singleParams); - deltaAmounts[i] += zeroForOne ? curDeltas.amount0() : curDeltas.amount1(); - deltaAmounts[i + 1] += zeroForOne ? curDeltas.amount1() : curDeltas.amount0(); + (int128 deltaIn, int128 deltaOut) = + zeroForOne ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); + deltaAmounts[i] += deltaIn; + deltaAmounts[i + 1] += deltaOut; - params.amountIn = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); - params.currencyIn = params.path[i].intermediateCurrency; + prevAmountOut = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); + prevCurrencyOut = params.path[i].intermediateCurrency; sqrtPriceX96AfterList[i] = sqrtPriceX96After; initializedTicksCrossedList[i] = PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); From 20af595d1c85bc99b2e65adb98c7b9076a47844b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 11:01:29 -0500 Subject: [PATCH 16/80] pull out sqrtPriceLimit to its own function --- contracts/lens/Quoter.sol | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 490e3724..6805462b 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -253,12 +253,16 @@ contract Quoter is IQuoter { IPoolManager.SwapParams({ zeroForOne: zeroForOne, amountSpecified: amountSpecified, - sqrtPriceLimitX96: sqrtPriceLimitX96 == 0 - ? zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 - : sqrtPriceLimitX96 + sqrtPriceLimitX96: _sqrtPriceLimitOrDefault(sqrtPriceLimitX96, zeroForOne) }), hookData ); (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(poolKey.toId()); } + + function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { + return sqrtPriceLimitX96 == 0 + ? zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + : sqrtPriceLimitX96; + } } From e3f6c7e1876536c1ec7ddbbef0cbbccfd156ed01 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 11:14:26 -0500 Subject: [PATCH 17/80] PathKey to its own library --- contracts/libraries/PathKey.sol | 14 ++++++++++++++ contracts/libraries/SwapIntention.sol | 9 +-------- 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 contracts/libraries/PathKey.sol diff --git a/contracts/libraries/PathKey.sol b/contracts/libraries/PathKey.sol new file mode 100644 index 00000000..4c0186ab --- /dev/null +++ b/contracts/libraries/PathKey.sol @@ -0,0 +1,14 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity ^0.8.20; + +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; + +struct PathKey { + Currency intermediateCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + bytes hookData; +} diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index fc270b76..7ec42ab0 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; +import {PathKey} from "./PathKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; @@ -18,14 +19,6 @@ struct SwapInfo { bytes params; } -struct PathKey { - Currency intermediateCurrency; - uint24 fee; - int24 tickSpacing; - IHooks hooks; - bytes hookData; -} - struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; From a8a0ed1ffb7c90af1a1c51719d526aac36bc44e0 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 11:21:11 -0500 Subject: [PATCH 18/80] rename initializedTicksCrossed to initializedTicksLoaded --- contracts/interfaces/IQuoter.sol | 4 +- contracts/lens/Quoter.sol | 49 ++++++++-------- contracts/libraries/PoolTicksCounter.sol | 2 +- test/Quoter.t.sol | 74 ++++++++++++------------ 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 3b3d38f1..03d51076 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -16,13 +16,13 @@ interface IQuoter { function quoteExactInputSingle(ExactInputSingleParams calldata params) external - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed); + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); function quoteExactInput(ExactInputParams memory params) external returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 6805462b..645df2cf 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -31,7 +31,7 @@ contract Quoter is IQuoter { function quoteExactInputSingle(ExactInputSingleParams memory params) external override - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { @@ -44,7 +44,7 @@ contract Quoter is IQuoter { returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) { try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} @@ -72,26 +72,29 @@ contract Quoter is IQuoter { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + // bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + // uint256 resultLength = result.length; assembly { - // function storeArray(offset, length, array) { - // mstore(offset, length) - // offset := add(offset, 0x20) - // for { let i := 0 } lt(i, length) { i := add(i, 1) } { - // let value := mload(add(array, add(mul(i, 0x20), 0x20))) - // mstore(offset, value) - // offset := add(offset, 0x20) - // } - // } + //revert(result, resultLength) + function storeArray(offset, length, array) { + mstore(offset, length) + offset := add(offset, 0x20) + for { let i := 0 } lt(i, length) { i := add(i, 1) } { + let value := mload(add(array, add(mul(i, 0x20), 0x20))) + mstore(offset, value) + offset := add(offset, 0x20) + } + } let originalPtr := mload(0x40) let ptr := mload(0x40) let deltaLength := mload(deltaAmounts) let sqrtPriceLength := mload(sqrtPriceX96AfterList) - let initializedTicksLength := mload(initializedTicksCrossedList) + let initializedTicksLength := mload(initializedTicksLoadedList) let deltaOffset := 0x60 let sqrtPriceOffset := add(deltaOffset, add(0x20, mul(0x20, deltaLength))) @@ -125,7 +128,7 @@ contract Quoter is IQuoter { mstore(ptr, initializedTicksLength) ptr := add(ptr, 0x20) for { let i := 0 } lt(i, initializedTicksLength) { i := add(i, 1) } { - let value := mload(add(initializedTicksCrossedList, add(mul(i, 0x20), 0x20))) + let value := mload(add(initializedTicksLoadedList, add(mul(i, 0x20), 0x20))) mstore(ptr, value) ptr := add(ptr, 0x20) } @@ -154,7 +157,7 @@ contract Quoter is IQuoter { function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) private view - returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { int24 tickBefore; int24 tickAfter; @@ -166,8 +169,8 @@ contract Quoter is IQuoter { deltaAmounts[0] = deltas.amount0(); deltaAmounts[1] = deltas.amount1(); - initializedTicksCrossed = - PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); + initializedTicksLoaded = + PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, tickBefore, tickAfter); } function _handleRevertExactInput(bytes memory reason) @@ -176,11 +179,11 @@ contract Quoter is IQuoter { returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) { reason = validateRevertReason(reason); - (deltaAmounts, sqrtPriceX96AfterList, initializedTicksCrossedList) = + (deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList) = abi.decode(reason, (int128[], uint160[], uint32[])); } @@ -189,14 +192,14 @@ contract Quoter is IQuoter { returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) { uint256 pathLength = params.path.length; deltaAmounts = new int128[](pathLength + 1); sqrtPriceX96AfterList = new uint160[](pathLength); - initializedTicksCrossedList = new uint32[](pathLength); + initializedTicksLoadedList = new uint32[](pathLength); Currency prevCurrencyOut; uint128 prevAmountOut; @@ -223,8 +226,8 @@ contract Quoter is IQuoter { prevAmountOut = zeroForOne ? uint128(-curDeltas.amount1()) : uint128(-curDeltas.amount0()); prevCurrencyOut = params.path[i].intermediateCurrency; sqrtPriceX96AfterList[i] = sqrtPriceX96After; - initializedTicksCrossedList[i] = - PoolTicksCounter.countInitializedTicksCrossed(poolManager, poolKey, tickBefore, tickAfter); + initializedTicksLoadedList[i] = + PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, tickBefore, tickAfter); } } diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index c327fa9b..6c3c2780 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -13,7 +13,7 @@ library PoolTicksCounter { /// 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. - function countInitializedTicksCrossed(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) + function countInitializedTicksLoaded(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) internal view returns (uint32 initializedTicksCrossed) diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 73b1d4ff..c55a9454 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -72,7 +72,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 78461846509168490764501028180; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, @@ -86,7 +86,7 @@ contract QuoterTest is Test, Deployers { assertEq(uint128(-deltaAmounts[1]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksCrossed, 2); + assertEq(initializedTicksLoaded, 2); } function testQuoter_quoteExactInputSingle_OneForZero_MultiplePositions() public { @@ -94,7 +94,7 @@ contract QuoterTest is Test, Deployers { uint256 expectedAmountOut = 9871; uint160 expectedSqrtPriceX96After = 80001962924147897865541384515; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed) = quoter + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( ExactInputSingleParams({ poolKey: key02, @@ -108,10 +108,10 @@ contract QuoterTest is Test, Deployers { assertEq(uint128(-deltaAmounts[0]), expectedAmountOut); assertEq(sqrtPriceX96After, expectedSqrtPriceX96After); - assertEq(initializedTicksCrossed, 2); + assertEq(initializedTicksLoaded, 2); } - function testQuoter_quoteExactInput_0to2_2TicksCrossed() public { + function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); ExactInputParams memory params = getExactInputParams(tokenPath, 10000); @@ -119,15 +119,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 9871); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(initializedTicksCrossedList[0], 2); + assertEq(initializedTicksLoadedList[0], 2); } - function testQuoter_quoteExactInput_0to2_2TicksCrossed_initialiedAfter() public { + function testQuoter_quoteExactInput_0to2_2TicksLoaded_initialiedAfter() public { tokenPath.push(token0); tokenPath.push(token2); @@ -138,15 +138,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 6143); assertEq(sqrtPriceX96AfterList[0], 78757224507315167622282810783); - assertEq(initializedTicksCrossedList[0], 1); + assertEq(initializedTicksLoadedList[0], 1); } - function testQuoter_quoteExactInput_0to2_1TicksCrossed() public { + function testQuoter_quoteExactInput_0to2_1TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); @@ -157,15 +157,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 3971); assertEq(sqrtPriceX96AfterList[0], 78926452400586371254602774705); - assertEq(initializedTicksCrossedList[0], 1); + assertEq(initializedTicksLoadedList[0], 1); } - function testQuoter_quoteExactInput_0to2_0TicksCrossed_startingNotInitialized() public { + function testQuoter_quoteExactInput_0to2_0TicksLoaded_startingNotInitialized() public { tokenPath.push(token0); tokenPath.push(token2); ExactInputParams memory params = getExactInputParams(tokenPath, 10); @@ -173,15 +173,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227483487511329217250071027); - assertEq(initializedTicksCrossedList[0], 0); + assertEq(initializedTicksLoadedList[0], 0); } - function testQuoter_quoteExactInput_0to2_0TicksCrossed_startingInitialized() public { + function testQuoter_quoteExactInput_0to2_0TicksLoaded_startingInitialized() public { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token0); tokenPath.push(token2); @@ -190,15 +190,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(uint128(-deltaAmounts[1]), 8); assertEq(sqrtPriceX96AfterList[0], 79227817515327498931091950511); - assertEq(initializedTicksCrossedList[0], 1); + assertEq(initializedTicksLoadedList[0], 1); } - function testQuoter_quoteExactInput_2to0_2TicksCrossed() public { + function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { tokenPath.push(token2); tokenPath.push(token0); ExactInputParams memory params = getExactInputParams(tokenPath, 10000); @@ -206,15 +206,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); - assertEq(initializedTicksCrossedList[0], 2); + assertEq(initializedTicksLoadedList[0], 2); } - function testQuoter_quoteExactInput_2to0_2TicksCrossed_initialiedAfter() public { + function testQuoter_quoteExactInput_2to0_2TicksLoaded_initialiedAfter() public { tokenPath.push(token2); tokenPath.push(token0); @@ -225,15 +225,15 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 6190); assertEq(sqrtPriceX96AfterList[0], 79705728824507063507279123685); - assertEq(initializedTicksCrossedList[0], 2); + assertEq(initializedTicksLoadedList[0], 2); } - function testQuoter_quoteExactInput_2to0_0TicksCrossed_startingInitialized() public { + function testQuoter_quoteExactInput_2to0_0TicksLoaded_startingInitialized() public { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token2); tokenPath.push(token0); @@ -243,16 +243,16 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 198); assertEq(sqrtPriceX96AfterList[0], 79235729830182478001034429156); - assertEq(initializedTicksCrossedList[0], 0); + assertEq(initializedTicksLoadedList[0], 0); } // 2->0 starting not initialized - function testQuoter_quoteExactInput_2to0_0TicksCrossed_startingNotInitialized() public { + function testQuoter_quoteExactInput_2to0_0TicksLoaded_startingNotInitialized() public { tokenPath.push(token2); tokenPath.push(token0); ExactInputParams memory params = getExactInputParams(tokenPath, 103); @@ -260,12 +260,12 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 101); assertEq(sqrtPriceX96AfterList[0], 79235858216754624215638319723); - assertEq(initializedTicksCrossedList[0], 0); + assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactInput_2to1() public { @@ -276,11 +276,11 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[1], 9871); assertEq(sqrtPriceX96AfterList[0], 80018067294531553039351583520); - assertEq(initializedTicksCrossedList[0], 0); + assertEq(initializedTicksLoadedList[0], 0); } function testQuoter_quoteExactInput_0to2to1() public { @@ -292,14 +292,14 @@ contract QuoterTest is Test, Deployers { ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksCrossedList + uint32[] memory initializedTicksLoadedList ) = quoter.quoteExactInput(params); assertEq(-deltaAmounts[2], 9745); assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); assertEq(sqrtPriceX96AfterList[1], 80007846861567212939802016351); - assertEq(initializedTicksCrossedList[0], 2); - assertEq(initializedTicksCrossedList[1], 0); + assertEq(initializedTicksLoadedList[0], 2); + assertEq(initializedTicksLoadedList[1], 0); } function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) @@ -418,7 +418,7 @@ contract QuoterTest is Test, Deployers { params.amountIn = uint128(amountIn); } - function logTicksCrossed(uint32[] memory num) private view { + function logTicksLoaded(uint32[] memory num) private view { console.logString("=== Num Ticks Crossed ==="); for (uint256 i = 0; i < num.length; i++) { console.logUint(num[i]); From bd22b11c8e2a817896dc45171cf0a78bbd1a5e5e Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 11:46:57 -0500 Subject: [PATCH 19/80] remove manual abi encoding in yul :p --- contracts/lens/Quoter.sol | 68 +++------------------------------------ 1 file changed, 5 insertions(+), 63 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 645df2cf..f7d36895 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -61,12 +61,10 @@ contract Quoter is IQuoter { if (swapInfo.swapType == SwapType.ExactInputSingle) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + + bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); assembly { - let ptr := mload(0x40) - mstore(ptr, deltas) - mstore(add(ptr, 0x20), sqrtPriceX96After) - mstore(add(ptr, 0x40), tickAfter) - revert(ptr, 96) + revert(add(0x20, result), mload(result)) } } else if (swapInfo.swapType == SwapType.ExactInput) { ( @@ -75,65 +73,9 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); - // bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - // uint256 resultLength = result.length; + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); assembly { - //revert(result, resultLength) - function storeArray(offset, length, array) { - mstore(offset, length) - offset := add(offset, 0x20) - for { let i := 0 } lt(i, length) { i := add(i, 1) } { - let value := mload(add(array, add(mul(i, 0x20), 0x20))) - mstore(offset, value) - offset := add(offset, 0x20) - } - } - - let originalPtr := mload(0x40) - let ptr := mload(0x40) - - let deltaLength := mload(deltaAmounts) - let sqrtPriceLength := mload(sqrtPriceX96AfterList) - let initializedTicksLength := mload(initializedTicksLoadedList) - - let deltaOffset := 0x60 - let sqrtPriceOffset := add(deltaOffset, add(0x20, mul(0x20, deltaLength))) - let initializedTicksOffset := add(sqrtPriceOffset, add(0x20, mul(0x20, sqrtPriceLength))) - - // storing offsets to dynamic arrays - mstore(ptr, deltaOffset) - ptr := add(ptr, 0x20) - mstore(ptr, sqrtPriceOffset) - ptr := add(ptr, 0x20) - mstore(ptr, initializedTicksOffset) - ptr := add(ptr, 0x20) - - //storing length + contents of dynamic arrays - mstore(ptr, deltaLength) - ptr := add(ptr, 0x20) - for { let i := 0 } lt(i, deltaLength) { i := add(i, 1) } { - let value := mload(add(deltaAmounts, add(mul(i, 0x20), 0x20))) - mstore(ptr, value) - ptr := add(ptr, 0x20) - } - - mstore(ptr, sqrtPriceLength) - ptr := add(ptr, 0x20) - for { let i := 0 } lt(i, sqrtPriceLength) { i := add(i, 1) } { - let value := mload(add(sqrtPriceX96AfterList, add(mul(i, 0x20), 0x20))) - mstore(ptr, value) - ptr := add(ptr, 0x20) - } - - mstore(ptr, initializedTicksLength) - ptr := add(ptr, 0x20) - for { let i := 0 } lt(i, initializedTicksLength) { i := add(i, 1) } { - let value := mload(add(initializedTicksLoadedList, add(mul(i, 0x20), 0x20))) - mstore(ptr, value) - ptr := add(ptr, 0x20) - } - - revert(originalPtr, sub(ptr, originalPtr)) + revert(add(0x20, result), mload(result)) } } else { revert InvalidQuoteType(); From 1e0277bb790993625a441ec06d5d4dcee897e70d Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 12:08:32 -0500 Subject: [PATCH 20/80] fix linter warnings for Quoter --- contracts/interfaces/IQuoter.sol | 1 + contracts/lens/Quoter.sol | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 03d51076..654a20a9 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -7,6 +7,7 @@ import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; interface IQuoter { error InvalidQuoteType(); error InvalidQuoteTypeInRevert(); + error InvalidLockAcquiredSender(); error UnexpectedRevertBytes(); struct NonZeroDeltaCurrency { diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index f7d36895..4c39edd6 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,31 +1,27 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; +import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; using Hooks for IHooks; // v4 Singleton contract - IPoolManager immutable poolManager; + IPoolManager private immutable MANAGER; constructor(address _poolManager) { - poolManager = IPoolManager(_poolManager); + MANAGER = IPoolManager(_poolManager); } function quoteExactInputSingle(ExactInputSingleParams memory params) @@ -33,7 +29,7 @@ contract Quoter is IQuoter { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} + try MANAGER.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { return _handleRevertExactInputSingle(reason, params.poolKey); } @@ -47,14 +43,16 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) { - try poolManager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + try MANAGER.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} catch (bytes memory reason) { return _handleRevertExactInput(reason); } } function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { - require(msg.sender == address(poolManager)); + if (msg.sender != address(MANAGER)) { + revert InvalidLockAcquiredSender(); + } SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); @@ -105,14 +103,13 @@ contract Quoter is IQuoter { int24 tickAfter; BalanceDelta deltas; deltaAmounts = new int128[](2); - (, tickBefore,,) = poolManager.getSlot0(poolKey.toId()); + (, tickBefore,,) = MANAGER.getSlot0(poolKey.toId()); reason = validateRevertReason(reason); (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); deltaAmounts[0] = deltas.amount0(); deltaAmounts[1] = deltas.amount1(); - initializedTicksLoaded = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, tickBefore, tickAfter); + initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(MANAGER, poolKey, tickBefore, tickAfter); } function _handleRevertExactInput(bytes memory reason) @@ -148,7 +145,7 @@ contract Quoter is IQuoter { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); - (, int24 tickBefore,,) = poolManager.getSlot0(poolKey.toId()); + (, int24 tickBefore,,) = MANAGER.getSlot0(poolKey.toId()); ExactInputSingleParams memory singleParams = ExactInputSingleParams({ poolKey: poolKey, @@ -169,7 +166,7 @@ contract Quoter is IQuoter { prevCurrencyOut = params.path[i].intermediateCurrency; sqrtPriceX96AfterList[i] = sqrtPriceX96After; initializedTicksLoadedList[i] = - PoolTicksCounter.countInitializedTicksLoaded(poolManager, poolKey, tickBefore, tickAfter); + PoolTicksCounter.countInitializedTicksLoaded(MANAGER, poolKey, tickBefore, tickAfter); } } @@ -193,7 +190,7 @@ contract Quoter is IQuoter { uint160 sqrtPriceLimitX96, bytes memory hookData ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - deltas = poolManager.swap( + deltas = MANAGER.swap( poolKey, IPoolManager.SwapParams({ zeroForOne: zeroForOne, @@ -202,7 +199,7 @@ contract Quoter is IQuoter { }), hookData ); - (sqrtPriceX96After, tickAfter,,) = poolManager.getSlot0(poolKey.toId()); + (sqrtPriceX96After, tickAfter,,) = MANAGER.getSlot0(poolKey.toId()); } function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { From ca62ec2c9445aaceba6fbca9e9852c28540ebdd9 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Thu, 16 Nov 2023 12:25:07 -0500 Subject: [PATCH 21/80] natspec for IQuoter --- .solhint.json | 9 +++++++++ contracts/interfaces/IQuoter.sol | 25 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .solhint.json diff --git a/.solhint.json b/.solhint.json new file mode 100644 index 00000000..669c0615 --- /dev/null +++ b/.solhint.json @@ -0,0 +1,9 @@ +{ + "extends": "solhint:recommended", + "rules": { + "no-inline-assembly": "off", + "no-global-import": "off", + "no-empty-blocks": "off", + "func-visibility": ["warn", { "ignoreConstructors": true }] + } +} diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 654a20a9..1692f963 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.20; import "../libraries/SwapIntention.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +/// @title Quoter Interface +/// @notice Supports quoting the delta amounts from 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. +/// @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 InvalidQuoteType(); error InvalidQuoteTypeInRevert(); @@ -14,11 +19,31 @@ interface IQuoter { Currency currency; int128 deltaAmount; } + /// @notice Returns the delta amounts for a given exact input but for a swap of a single pool + /// @param params The params for the quote, encoded as `ExactInputSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// recipient The indented recipient of the output tokens + /// amountIn 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 sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded function quoteExactInputSingle(ExactInputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + /// @notice Returns the delta amounts along the swap path for a given exact input swap + /// @param params the params for the quote, encoded as 'ExactInputParams' + /// currencyIn The input currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// recipient The indented recipient of the output tokens + /// amountIn 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(ExactInputParams memory params) external returns ( From d526f7593c9e7e18dbe0bb7f20b610775c49db01 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Thu, 16 Nov 2023 16:07:02 -0500 Subject: [PATCH 22/80] feat: update v4-core This commit updates v4 core to latest and fixes integration issues --- .env | 9 ++++ contracts/BaseHook.sol | 10 ++-- contracts/hooks/examples/FullRange.sol | 28 +++++------ contracts/hooks/examples/GeomeanOracle.sol | 10 ++-- contracts/hooks/examples/LimitOrder.sol | 42 +++++++++-------- contracts/hooks/examples/TWAMM.sol | 24 +++++----- contracts/hooks/examples/VolatilityOracle.sol | 16 +++---- contracts/interfaces/ITWAMM.sol | 10 ++-- contracts/libraries/LiquidityAmounts.sol | 4 +- contracts/libraries/PoolGetters.sol | 8 ++-- contracts/libraries/TWAMM/TwammMath.sol | 6 +-- contracts/libraries/TransferHelper.sol | 2 +- lib/v4-core | 2 +- test/FullRange.t.sol | 46 +++++++------------ test/GeomeanOracle.t.sol | 30 +++++------- test/LimitOrder.t.sol | 34 +++++--------- test/TWAMM.t.sol | 40 ++++++---------- .../FullRangeImplementation.sol | 4 +- .../GeomeanOracleImplementation.sol | 4 +- .../LimitOrderImplementation.sol | 4 +- .../implementation/TWAMMImplementation.sol | 4 +- 21 files changed, 152 insertions(+), 185 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 00000000..721bb082 --- /dev/null +++ b/.env @@ -0,0 +1,9 @@ +FOUNDRY_FUZZ_SEED=0x4444 + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-static-linux" +elif [[ "$OSTYPE" == "darwin"* ]]; then + export FOUNDRY_SOLC="./lib/v4-core/bin/solc-mac" +fi + +# FOUNDRY_SOLC="./bin/solc-static-linux" diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index 8d463807..a16ab91d 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; abstract contract BaseHook is IHooks { error NotPoolManager(); diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index 6c5b08ec..eaa7539b 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {UniswapV4ERC20} from "../../libraries/UniswapV4ERC20.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 5c78e785..8ae1c640 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../../libraries/Oracle.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; /// @notice A hook for a pool that allows a Uniswap pool to act as an oracle. Pools that use this hook must have full range /// tick spacing and liquidity is always permanently locked in these pools. This is the suggested configuration diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 8eff6c68..d2654f49 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; type Epoch is uint232; @@ -204,8 +204,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.mint(key.currency0, address(this), amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.mint(key.currency1, address(this), amount1 = uint128(-delta.amount1())); + } } function place(PoolKey calldata key, int24 tickLower, bool zeroForOne, uint128 liquidity) @@ -352,8 +356,12 @@ contract LimitOrder is BaseHook { ZERO_BYTES ); - if (delta.amount0() < 0) poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); - if (delta.amount1() < 0) poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + if (delta.amount0() < 0) { + poolManager.take(key.currency0, to, amount0 = uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + poolManager.take(key.currency1, to, amount1 = uint128(-delta.amount1())); + } } function withdraw(Epoch epoch, address to) external returns (uint256 amount0, uint256 amount1) { @@ -391,15 +399,11 @@ contract LimitOrder is BaseHook { address to ) external selfOnly { if (token0Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency0))), token0Amount, "" - ); + poolManager.burn(currency0, token0Amount); poolManager.take(currency0, to, token0Amount); } if (token1Amount > 0) { - poolManager.safeTransferFrom( - address(this), address(poolManager), uint256(uint160(Currency.unwrap(currency1))), token1Amount, "" - ); + poolManager.burn(currency1, token1Amount); poolManager.take(currency1, to, token1Amount); } } diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 55d44888..a6635e94 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -1,24 +1,24 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickBitmap} from "@uniswap/v4-core/contracts/libraries/TickBitmap.sol"; -import {SqrtPriceMath} from "@uniswap/v4-core/contracts/libraries/SqrtPriceMath.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol"; +import {SqrtPriceMath} from "@uniswap/v4-core/src/libraries/SqrtPriceMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {ITWAMM} from "../../interfaces/ITWAMM.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {TransferHelper} from "../../libraries/TransferHelper.sol"; import {TwammMath} from "../../libraries/TWAMM/TwammMath.sol"; import {OrderPool} from "../../libraries/TWAMM/OrderPool.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolGetters} from "../../libraries/PoolGetters.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract TWAMM is BaseHook, ITWAMM { using TransferHelper for IERC20Minimal; diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 0a7e696d..65485e11 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IDynamicFeeManager} from "@uniswap/v4-core/contracts/interfaces/IDynamicFeeManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {FeeLibrary} from "@uniswap/v4-core/contracts/libraries/FeeLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IDynamicFeeManager} from "@uniswap/v4-core/src/interfaces/IDynamicFeeManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {FeeLibrary} from "@uniswap/v4-core/src/libraries/FeeLibrary.sol"; import {BaseHook} from "../../BaseHook.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; contract VolatilityOracle is BaseHook, IDynamicFeeManager { using FeeLibrary for uint24; @@ -15,11 +15,7 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { uint32 deployTimestamp; - function getFee(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) - external - view - returns (uint24) - { + function getFee(address, PoolKey calldata) external view returns (uint24) { uint24 startingFee = 3000; uint32 lapsed = _blockTimestamp() - deployTimestamp; return startingFee + (uint24(lapsed) * 100) / 60; // 100 bps a minute diff --git a/contracts/interfaces/ITWAMM.sol b/contracts/interfaces/ITWAMM.sol index 570617b6..3b932d3c 100644 --- a/contracts/interfaces/ITWAMM.sol +++ b/contracts/interfaces/ITWAMM.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; interface ITWAMM { /// @notice Thrown when account other than owner attempts to interact with an order diff --git a/contracts/libraries/LiquidityAmounts.sol b/contracts/libraries/LiquidityAmounts.sol index b2c8b54c..845cc6e0 100644 --- a/contracts/libraries/LiquidityAmounts.sol +++ b/contracts/libraries/LiquidityAmounts.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; +import "@uniswap/v4-core/src/libraries/FullMath.sol"; +import "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; /// @title Liquidity amount functions /// @notice Provides functions for computing liquidity amounts from token amounts and prices diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol index d2c7fbf2..78f34c87 100644 --- a/contracts/libraries/PoolGetters.sol +++ b/contracts/libraries/PoolGetters.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Pool} from "@uniswap/v4-core/contracts/libraries/Pool.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {BitMath} from "@uniswap/v4-core/contracts/libraries/BitMath.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Pool} from "@uniswap/v4-core/src/libraries/Pool.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; /// @title Helper functions to access pool information library PoolGetters { diff --git a/contracts/libraries/TWAMM/TwammMath.sol b/contracts/libraries/TWAMM/TwammMath.sol index 133a68c7..a5994b51 100644 --- a/contracts/libraries/TWAMM/TwammMath.sol +++ b/contracts/libraries/TWAMM/TwammMath.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.15; import {ABDKMathQuad} from "./ABDKMathQuad.sol"; -import {FixedPoint96} from "@uniswap/v4-core/contracts/libraries/FixedPoint96.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; /// @title TWAMM Math - Pure functions for TWAMM math calculations library TwammMath { diff --git a/contracts/libraries/TransferHelper.sol b/contracts/libraries/TransferHelper.sol index 5b1833a7..9ab40d9e 100644 --- a/contracts/libraries/TransferHelper.sol +++ b/contracts/libraries/TransferHelper.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.15; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; /// @title TransferHelper /// @notice Contains helper methods for interacting with ERC20 tokens that do not consistently return true/false diff --git a/lib/v4-core b/lib/v4-core index 0095e084..7998e6c3 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 0095e0848098c3e32e016eac6d2537b67aa47358 +Subproject commit 7998e6c391b77d2a8455f902097b0180b579db1b diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index fa9d13ed..0658d683 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -3,22 +3,22 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {FullRange} from "../contracts/hooks/examples/FullRange.sol"; import {FullRangeImplementation} from "./shared/implementation/FullRangeImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; -import {FullMath} from "@uniswap/v4-core/contracts/libraries/FullMath.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; +import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; contract TestFullRange is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -62,15 +62,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { MockERC20 token1; MockERC20 token2; - Currency currency0; - Currency currency1; - - PoolManager manager; FullRangeImplementation fullRange = FullRangeImplementation( address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG | Hooks.BEFORE_SWAP_FLAG)) ); - PoolKey key; PoolId id; PoolKey key2; @@ -80,20 +75,14 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey keyWithLiq; PoolId idWithLiq; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - function setUp() public { - token0 = new MockERC20("TestA", "A", 18, 2 ** 128); - token1 = new MockERC20("TestB", "B", 18, 2 ** 128); - token2 = new MockERC20("TestC", "C", 18, 2 ** 128); - - manager = new PoolManager(500000); + token0 = new MockERC20("TestA", "A", 18); + token1 = new MockERC20("TestB", "B", 18); + token2 = new MockERC20("TestC", "C", 18); FullRangeImplementation impl = new FullRangeImplementation(manager, fullRange); vm.etch(address(fullRange), address(impl).code); - key = createPoolKey(token0, token1); id = key.toId(); key2 = createPoolKey(token1, token2); @@ -102,9 +91,6 @@ contract TestFullRange is Test, Deployers, GasSnapshot { keyWithLiq = createPoolKey(token0, token2); idWithLiq = keyWithLiq.toId(); - modifyPositionRouter = new PoolModifyPositionTest(manager); - swapRouter = new PoolSwapTest(manager); - token0.approve(address(fullRange), type(uint256).max); token1.approve(address(fullRange), type(uint256).max); token2.approve(address(fullRange), type(uint256).max); diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index bd0e0c05..96bc7100 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -3,22 +3,21 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {GeomeanOracle} from "../contracts/hooks/examples/GeomeanOracle.sol"; import {GeomeanOracleImplementation} from "./shared/implementation/GeomeanOracleImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Oracle} from "../contracts/libraries/Oracle.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TestGeomeanOracle is Test, Deployers, TokenFixture { +contract TestGeomeanOracle is Test, Deployers { using PoolIdLibrary for PoolKey; int24 constant MAX_TICK_SPACING = 32767; @@ -26,7 +25,6 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { TestERC20 token0; TestERC20 token1; - PoolManager manager; GeomeanOracleImplementation geomeanOracle = GeomeanOracleImplementation( address( uint160( @@ -35,18 +33,12 @@ contract TestGeomeanOracle is Test, Deployers, TokenFixture { ) ) ); - PoolKey key; PoolId id; - PoolModifyPositionTest modifyPositionRouter; - function setUp() public { - initializeTokens(); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); GeomeanOracleImplementation impl = new GeomeanOracleImplementation(manager, geomeanOracle); (, bytes32[] memory writes) = vm.accesses(address(impl)); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 27613654..02eef332 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -3,41 +3,33 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GetSender} from "./shared/GetSender.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {LimitOrder, Epoch, EpochLibrary} from "../contracts/hooks/examples/LimitOrder.sol"; import {LimitOrderImplementation} from "./shared/implementation/LimitOrderImplementation.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {TestERC20} from "@uniswap/v4-core/contracts/test/TestERC20.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; - -contract TestLimitOrder is Test, Deployers, TokenFixture { +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; + +contract TestLimitOrder is Test, Deployers { using PoolIdLibrary for PoolKey; uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; TestERC20 token0; TestERC20 token1; - PoolManager manager; LimitOrder limitOrder = LimitOrder(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_SWAP_FLAG))); - PoolKey key; PoolId id; - PoolSwapTest swapRouter; - function setUp() public { - initializeTokens(); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); - vm.record(); LimitOrderImplementation impl = new LimitOrderImplementation(manager, limitOrder); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -54,8 +46,6 @@ contract TestLimitOrder is Test, Deployers, TokenFixture { id = key.toId(); manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - swapRouter = new PoolSwapTest(manager); - token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); token0.approve(address(swapRouter), type(uint256).max); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 84ed9716..f313f9d7 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -3,26 +3,25 @@ pragma solidity ^0.8.15; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; -import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {TWAMMImplementation} from "./shared/implementation/TWAMMImplementation.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/contracts/test/PoolSwapTest.sol"; -import {PoolDonateTest} from "@uniswap/v4-core/contracts/test/PoolDonateTest.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {TokenFixture} from "@uniswap/v4-core/test/foundry-tests/utils/TokenFixture.sol"; -import {CurrencyLibrary, Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {PoolDonateTest} from "@uniswap/v4-core/src/test/PoolDonateTest.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TWAMM} from "../contracts/hooks/examples/TWAMM.sol"; import {ITWAMM} from "../contracts/interfaces/ITWAMM.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { +contract TWAMMTest is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; using CurrencyLibrary for Currency; @@ -49,10 +48,6 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { address(uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_MODIFY_POSITION_FLAG)) ); // TWAMM twamm; - PoolManager manager; - PoolModifyPositionTest modifyPositionRouter; - PoolSwapTest swapRouter; - PoolDonateTest donateRouter; address hookAddress; MockERC20 token0; MockERC20 token1; @@ -60,10 +55,8 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { PoolId poolId; function setUp() public { - initializeTokens(); token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); - manager = new PoolManager(500000); TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); (, bytes32[] memory writes) = vm.accesses(address(impl)); @@ -76,9 +69,6 @@ contract TWAMMTest is Test, Deployers, TokenFixture, GasSnapshot { } } - modifyPositionRouter = new PoolModifyPositionTest(IPoolManager(address(manager))); - swapRouter = new PoolSwapTest(IPoolManager(address(manager))); - poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, twamm); poolId = poolKey.toId(); manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index fcd8ae3f..8ee0589f 100644 --- a/test/shared/implementation/FullRangeImplementation.sol +++ b/test/shared/implementation/FullRangeImplementation.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {FullRange} from "../../../contracts/hooks/examples/FullRange.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract FullRangeImplementation is FullRange { constructor(IPoolManager _poolManager, FullRange addressToEtch) FullRange(_poolManager) { diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol index 06a95fa2..68b669f1 100644 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ b/test/shared/implementation/GeomeanOracleImplementation.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {GeomeanOracle} from "../../../contracts/hooks/examples/GeomeanOracle.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract GeomeanOracleImplementation is GeomeanOracle { uint32 public time; diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol index 340cfc42..b70f2553 100644 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ b/test/shared/implementation/LimitOrderImplementation.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {LimitOrder} from "../../../contracts/hooks/examples/LimitOrder.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract LimitOrderImplementation is LimitOrder { constructor(IPoolManager _poolManager, LimitOrder addressToEtch) LimitOrder(_poolManager) { diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol index 012ca541..883eeb62 100644 --- a/test/shared/implementation/TWAMMImplementation.sol +++ b/test/shared/implementation/TWAMMImplementation.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.19; import {BaseHook} from "../../../contracts/BaseHook.sol"; import {TWAMM} from "../../../contracts/hooks/examples/TWAMM.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract TWAMMImplementation is TWAMM { constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { From 3e3ab5dfbf478b21adb64ab81a332229fb4c753b Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Thu, 16 Nov 2023 16:13:26 -0500 Subject: [PATCH 23/80] fix: tests --- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- foundry.toml | 1 + test/FullRange.t.sol | 3 +++ test/GeomeanOracle.t.sol | 3 +++ test/LimitOrder.t.sol | 3 +++ test/TWAMM.t.sol | 3 +++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 9adc49a6..d01ad33f 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123576 \ No newline at end of file +123619 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index b3132187..7c32d4b3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,6 +5,7 @@ solc_version = '0.8.20' optimizer_runs = 800 ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] +cancun = true [profile.ci] fuzz_runs = 100000 diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 0658d683..49a7b98d 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -76,6 +76,9 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolId idWithLiq; function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = new MockERC20("TestA", "A", 18); token1 = new MockERC20("TestB", "B", 18); token2 = new MockERC20("TestC", "C", 18); diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index 96bc7100..ce34adbd 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -36,6 +36,9 @@ contract TestGeomeanOracle is Test, Deployers { PoolId id; function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 02eef332..222e94c8 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -27,6 +27,9 @@ contract TestLimitOrder is Test, Deployers { PoolId id; function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index f313f9d7..575d3e6f 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -55,6 +55,9 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { PoolId poolId; function setUp() public { + deployFreshManagerAndRouters(); + (currency0, currency1) = deployMintAndApprove2Currencies(); + token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); From c5b8c228c62c55e8d65c612cfe4142ca65617e40 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 17 Nov 2023 09:24:42 -0500 Subject: [PATCH 24/80] style fixes --- contracts/lens/Quoter.sol | 22 +++++++++++----------- test/Quoter.t.sol | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 4c39edd6..d9f029b7 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -18,10 +18,10 @@ contract Quoter is IQuoter { using Hooks for IHooks; // v4 Singleton contract - IPoolManager private immutable MANAGER; + IPoolManager public immutable manager; constructor(address _poolManager) { - MANAGER = IPoolManager(_poolManager); + manager = IPoolManager(_poolManager); } function quoteExactInputSingle(ExactInputSingleParams memory params) @@ -29,7 +29,7 @@ contract Quoter is IQuoter { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try MANAGER.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} + try manager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { return _handleRevertExactInputSingle(reason, params.poolKey); } @@ -43,14 +43,14 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) { - try MANAGER.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + try manager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} catch (bytes memory reason) { return _handleRevertExactInput(reason); } } function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { - if (msg.sender != address(MANAGER)) { + if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); } @@ -103,13 +103,13 @@ contract Quoter is IQuoter { int24 tickAfter; BalanceDelta deltas; deltaAmounts = new int128[](2); - (, tickBefore,,) = MANAGER.getSlot0(poolKey.toId()); + (, tickBefore,,) = manager.getSlot0(poolKey.toId()); reason = validateRevertReason(reason); (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); deltaAmounts[0] = deltas.amount0(); deltaAmounts[1] = deltas.amount1(); - initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(MANAGER, poolKey, tickBefore, tickAfter); + initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } function _handleRevertExactInput(bytes memory reason) @@ -145,7 +145,7 @@ contract Quoter is IQuoter { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = SwapIntention.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); - (, int24 tickBefore,,) = MANAGER.getSlot0(poolKey.toId()); + (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); ExactInputSingleParams memory singleParams = ExactInputSingleParams({ poolKey: poolKey, @@ -166,7 +166,7 @@ contract Quoter is IQuoter { prevCurrencyOut = params.path[i].intermediateCurrency; sqrtPriceX96AfterList[i] = sqrtPriceX96After; initializedTicksLoadedList[i] = - PoolTicksCounter.countInitializedTicksLoaded(MANAGER, poolKey, tickBefore, tickAfter); + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } } @@ -190,7 +190,7 @@ contract Quoter is IQuoter { uint160 sqrtPriceLimitX96, bytes memory hookData ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - deltas = MANAGER.swap( + deltas = manager.swap( poolKey, IPoolManager.SwapParams({ zeroForOne: zeroForOne, @@ -199,7 +199,7 @@ contract Quoter is IQuoter { }), hookData ); - (sqrtPriceX96After, tickAfter,,) = MANAGER.getSlot0(poolKey.toId()); + (sqrtPriceX96After, tickAfter,,) = manager.getSlot0(poolKey.toId()); } function _sqrtPriceLimitOrDefault(uint160 sqrtPriceLimitX96, bool zeroForOne) private pure returns (uint160) { diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index c55a9454..6dbed3bd 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -29,6 +29,8 @@ contract QuoterTest is Test, Deployers { // Max tick for full range with tick spacing of 60 int24 internal constant MAX_TICK = -MIN_TICK; + uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; + Quoter quoter; PoolManager manager; @@ -45,7 +47,7 @@ contract QuoterTest is Test, Deployers { MockERC20[] tokenPath; function setUp() public { - manager = new PoolManager(500000); + manager = new PoolManager(CONTROLLER_GAS_LIMIT); quoter = new Quoter(address(manager)); positionManager = new PoolModifyPositionTest(manager); From 1d8f14f6c3984d4bf985ae95f08d1163422299c6 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 17 Nov 2023 09:26:23 -0500 Subject: [PATCH 25/80] inheritdoc --- contracts/lens/Quoter.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index d9f029b7..75bc4ece 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -24,6 +24,7 @@ contract Quoter is IQuoter { manager = IPoolManager(_poolManager); } + /// @inheritdoc IQuoter function quoteExactInputSingle(ExactInputSingleParams memory params) external override @@ -35,6 +36,7 @@ contract Quoter is IQuoter { } } + /// @inheritdoc IQuoter function quoteExactInput(ExactInputParams memory params) external returns ( From 55db57811f88940c40c2beb4b3ee4d9d3d5dc614 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 17 Nov 2023 14:50:28 -0500 Subject: [PATCH 26/80] ExactInSingleBatch --- contracts/interfaces/IQuoter.sol | 8 +++-- contracts/lens/Quoter.sol | 40 ++++++++++++++++++++- contracts/libraries/SwapIntention.sol | 9 +++++ test/Quoter.t.sol | 52 ++++++++++++++++++++++++++- 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 1692f963..714529d7 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -13,12 +13,14 @@ interface IQuoter { error InvalidQuoteType(); error InvalidQuoteTypeInRevert(); error InvalidLockAcquiredSender(); + error InvalidQuoteBatchParams(); error UnexpectedRevertBytes(); - struct NonZeroDeltaCurrency { - Currency currency; - int128 deltaAmount; + struct PoolDeltas { + int128 currency0Delta; + int128 currency1Delta; } + /// @notice Returns the delta amounts for a given exact input but for a swap of a single pool /// @param params The params for the quote, encoded as `ExactInputSingleParams` /// poolKey The key for identifying a V4 pool diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 75bc4ece..2c02e4e7 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -26,7 +26,7 @@ contract Quoter is IQuoter { /// @inheritdoc IQuoter function quoteExactInputSingle(ExactInputSingleParams memory params) - external + public override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { @@ -51,6 +51,44 @@ contract Quoter is IQuoter { } } + function quoteExactInputBatch(ExactInputSingleBatchParams memory params) + external + returns ( + IQuoter.PoolDeltas[] memory deltas, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + if ( + params.zeroForOnes.length != params.recipients.length || params.recipients.length != params.amountIns.length + || params.amountIns.length != params.sqrtPriceLimitX96s.length + || params.sqrtPriceLimitX96s.length != params.hookData.length + ) { + revert InvalidQuoteBatchParams(); + } + + deltas = new IQuoter.PoolDeltas[](params.amountIns.length); + sqrtPriceX96AfterList = new uint160[](params.amountIns.length); + initializedTicksLoadedList = new uint32[](params.amountIns.length); + + for (uint256 i = 0; i < params.amountIns.length; i++) { + ExactInputSingleParams memory singleParams = ExactInputSingleParams({ + poolKey: params.poolKey, + zeroForOne: params.zeroForOnes[i], + recipient: params.recipients[i], + amountIn: params.amountIns[i], + sqrtPriceLimitX96: params.sqrtPriceLimitX96s[i], + hookData: params.hookData[i] + }); + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = + quoteExactInputSingle(singleParams); + + deltas[i] = IQuoter.PoolDeltas({currency0Delta: deltaAmounts[0], currency1Delta: deltaAmounts[1]}); + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + initializedTicksLoadedList[i] = initializedTicksLoaded; + } + } + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index 7ec42ab0..0d40a594 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -28,6 +28,15 @@ struct ExactInputSingleParams { bytes hookData; } +struct ExactInputSingleBatchParams { + PoolKey poolKey; + bool[] zeroForOnes; + address[] recipients; + uint128[] amountIns; + uint160[] sqrtPriceLimitX96s; + bytes[] hookData; +} + struct ExactInputParams { Currency currencyIn; PathKey[] path; diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 6dbed3bd..086c1817 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.20; import "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import "../contracts/libraries/SwapIntention.sol"; +import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; @@ -51,7 +52,7 @@ contract QuoterTest is Test, Deployers { quoter = new Quoter(address(manager)); positionManager = new PoolModifyPositionTest(manager); - // salts are chose so that address(token0) < address(token2) && address(1) < address(token2) + // salts are chosen so that address(token0) < address(token2) && address(1) < address(token2) bytes32 salt1 = "ffff"; bytes32 salt2 = "gm"; token0 = new MockERC20{salt: salt1}("Test0", "0", 18); @@ -113,6 +114,55 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 2); } + function testQuoter_quoteExactInputBatch() public { + bool[] memory zeroForOnes = new bool[](2); + zeroForOnes[0] = true; + zeroForOnes[1] = false; + + address[] memory recipients = new address[](2); + recipients[0] = address(this); + recipients[1] = address(this); + + // repeat for the three arrays below + uint128[] memory amountIns = new uint128[](2); + amountIns[0] = 10000; + amountIns[1] = 10000; + + uint160[] memory sqrtPriceLimitX96s = new uint160[](2); + sqrtPriceLimitX96s[0] = 0; + sqrtPriceLimitX96s[1] = 0; + + bytes[] memory hookData = new bytes[](2); + hookData[0] = ZERO_BYTES; + hookData[1] = ZERO_BYTES; + + ( + IQuoter.PoolDeltas[] memory deltas, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactInputBatch( + ExactInputSingleBatchParams({ + poolKey: key02, + zeroForOnes: zeroForOnes, + recipients: recipients, + amountIns: amountIns, + sqrtPriceLimitX96s: sqrtPriceLimitX96s, + hookData: hookData + }) + ); + assertEq(deltas.length, 2); + assertEq(uint128(-deltas[0].currency1Delta), 9871); + assertEq(uint128(-deltas[1].currency0Delta), 9871); + + assertEq(sqrtPriceX96AfterList.length, 2); + assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); + assertEq(sqrtPriceX96AfterList[1], 80001962924147897865541384515); + + assertEq(initializedTicksLoadedList.length, 2); + assertEq(initializedTicksLoadedList[0], 2); + assertEq(initializedTicksLoadedList[1], 2); + } + function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); From 4fe142ca1c1ea6405b21d970b51eda7d9ada8cbd Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Fri, 17 Nov 2023 16:51:56 -0500 Subject: [PATCH 27/80] fix: update tests --- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- test/FullRange.t.sol | 65 +++++++++++-------- test/LimitOrder.t.sol | 29 +++++---- test/TWAMM.t.sol | 9 +-- test/utils/HookEnabledSwapRouter.sol | 65 +++++++++++++++++++ 12 files changed, 131 insertions(+), 53 deletions(-) create mode 100644 test/utils/HookEnabledSwapRouter.sol diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 2d5250a5..7e3a2f44 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -412696 \ No newline at end of file +390028 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index 032a6a3b..fcc9c101 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -206962 \ No newline at end of file +184294 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 9d59ac16..df930ad0 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -154763 \ No newline at end of file +133445 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index e0b3ab13..b07717ab 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -879542 \ No newline at end of file +896690 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 920384a4..594d74bc 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200095 \ No newline at end of file +177393 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 5ee38978..e2e0f2f7 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -379287 \ No newline at end of file +362063 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 436848b5..10867d68 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -112303 \ No newline at end of file +93791 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index d48620c7..791a7d96 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -153038 \ No newline at end of file +131720 \ No newline at end of file diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 49a7b98d..04d5ce27 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -19,6 +19,7 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract TestFullRange is Test, Deployers, GasSnapshot { using PoolIdLibrary for PoolKey; @@ -47,6 +48,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { uint24 fee ); + HookEnabledSwapRouter router; /// @dev Min tick for full range with tick spacing of 60 int24 internal constant MIN_TICK = -887220; /// @dev Max tick for full range with tick spacing of 60 @@ -77,15 +79,20 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function setUp() public { deployFreshManagerAndRouters(); + router = new HookEnabledSwapRouter(manager); (currency0, currency1) = deployMintAndApprove2Currencies(); token0 = new MockERC20("TestA", "A", 18); + token0.mint(address(this), 2 ** 128); token1 = new MockERC20("TestB", "B", 18); + token1.mint(address(this), 2 ** 128); token2 = new MockERC20("TestC", "C", 18); + token2.mint(address(this), 2 ** 128); FullRangeImplementation impl = new FullRangeImplementation(manager, fullRange); vm.etch(address(fullRange), address(impl).code); + key = createPoolKey(token0, token1); id = key.toId(); key2 = createPoolKey(token1, token2); @@ -97,11 +104,13 @@ contract TestFullRange is Test, Deployers, GasSnapshot { token0.approve(address(fullRange), type(uint256).max); token1.approve(address(fullRange), type(uint256).max); token2.approve(address(fullRange), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); - token2.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); + token2.approve(address(router), type(uint256).max); - manager.initialize(keyWithLiq, SQRT_RATIO_1_1, ZERO_BYTES); + initPool( + keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES + ); fullRange.addLiquidity( FullRange.AddLiquidityParams( keyWithLiq.currency0, @@ -262,16 +271,16 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.expectEmit(true, true, true, true); emit Swap( - id, address(swapRouter), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 + id, address(router), 1 ether, -906610893880149131, 72045250990510446115798809072, 10 ether, -1901, 3000 ); IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeSwap"); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); @@ -305,10 +314,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, settings, ZERO_BYTES); + router.swap(key, params, settings, ZERO_BYTES); vm.expectRevert(FullRange.TooMuchSlippage.selector); fullRange.addLiquidity( @@ -330,18 +339,18 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory settings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory settings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); snapStart("FullRangeFirstSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); snapStart("FullRangeSecondSwap"); - swapRouter.swap(testKey, params, settings, ZERO_BYTES); + router.swap(testKey, params, settings, ZERO_BYTES); snapEnd(); (hasAccruedFees,) = fullRange.poolInfo(id); @@ -366,11 +375,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10000000, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); - swapRouter.swap(key2, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key2, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -549,10 +558,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(keyWithLiq, params, testSettings, ZERO_BYTES); + router.swap(keyWithLiq, params, testSettings, ZERO_BYTES); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -676,10 +685,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { IPoolManager.SwapParams memory params = IPoolManager.SwapParams({zeroForOne: true, amountSpecified: 100 ether, sqrtPriceLimitX96: SQRT_RATIO_1_4}); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); (bool hasAccruedFees,) = fullRange.poolInfo(id); assertEq(hasAccruedFees, true); @@ -731,10 +740,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { sqrtPriceLimitX96: SQRT_RATIO_1_4 }); - PoolSwapTest.TestSettings memory testSettings = - PoolSwapTest.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); + HookEnabledSwapRouter.TestSettings memory testSettings = + HookEnabledSwapRouter.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); - swapRouter.swap(key, params, testSettings, ZERO_BYTES); + router.swap(key, params, testSettings, ZERO_BYTES); // Test contract removes liquidity, succeeds UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 222e94c8..007d84b7 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -12,15 +12,16 @@ import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {TestERC20} from "@uniswap/v4-core/src/test/TestERC20.sol"; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; -import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract TestLimitOrder is Test, Deployers { using PoolIdLibrary for PoolKey; uint160 constant SQRT_RATIO_10_1 = 250541448375047931186413801569; + HookEnabledSwapRouter router; TestERC20 token0; TestERC20 token1; LimitOrder limitOrder = LimitOrder(address(uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_SWAP_FLAG))); @@ -30,6 +31,7 @@ contract TestLimitOrder is Test, Deployers { deployFreshManagerAndRouters(); (currency0, currency1) = deployMintAndApprove2Currencies(); + router = new HookEnabledSwapRouter(manager); token0 = TestERC20(Currency.unwrap(currency0)); token1 = TestERC20(Currency.unwrap(currency1)); @@ -45,14 +47,15 @@ contract TestLimitOrder is Test, Deployers { } } - key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - id = key.toId(); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); + (key, id) = initPoolAndAddLiquidity( + currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES + ); token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); - token0.approve(address(swapRouter), type(uint256).max); - token1.approve(address(swapRouter), type(uint256).max); + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); } function testGetTickLowerLast() public { @@ -100,10 +103,10 @@ contract TestLimitOrder is Test, Deployers { function testZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( + router.swap( key, - IPoolManager.SwapParams(false, 1, SQRT_RATIO_1_1 + 1), - PoolSwapTest.TestSettings(true, true), + IPoolManager.SwapParams(false, 1 ether, SQRT_RATIO_1_1 + 1), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); @@ -126,8 +129,8 @@ contract TestLimitOrder is Test, Deployers { function testNotZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei - swapRouter.swap( - key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), PoolSwapTest.TestSettings(true, true), ZERO_BYTES + router.swap( + key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, -60, false, 1000000); @@ -185,10 +188,10 @@ contract TestLimitOrder is Test, Deployers { uint128 liquidity = 1000000; limitOrder.place(key, tickLower, zeroForOne, liquidity); - swapRouter.swap( + router.swap( key, IPoolManager.SwapParams(false, 1e18, TickMath.getSqrtRatioAtTick(60)), - PoolSwapTest.TestSettings(true, true), + HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES ); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 575d3e6f..1575d95d 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -93,6 +93,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { (PoolKey memory initKey, PoolId initId) = newPoolKeyWithTWAMM(twamm); assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); + manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } @@ -185,8 +186,8 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { assertEq(sellRate0For1, 2e18 / (expiration2 - submitTimestamp2)); assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); - assertEq(earningsFactor0For1, 1712020976636017581269515821040000); - assertEq(earningsFactor1For0, 1470157410324350030712806974476955); + assertEq(earningsFactor0For1, 1636776489931663248324424309240000); + assertEq(earningsFactor1For0, 1534530274609724617872321172427618); } function testTWAMM_submitOrder_EmitsEvent() public { @@ -409,8 +410,8 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { } function newPoolKeyWithTWAMM(IHooks hooks) public returns (PoolKey memory, PoolId) { - MockERC20[] memory tokens = deployTokens(2, 2 ** 255); - PoolKey memory key = PoolKey(Currency.wrap(address(tokens[0])), Currency.wrap(address(tokens[1])), 0, 60, hooks); + (Currency _token0, Currency _token1) = deployMintAndApprove2Currencies(); + PoolKey memory key = PoolKey(_token0, _token1, 0, 60, hooks); return (key, key.toId()); } diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol new file mode 100644 index 00000000..8c5bec5b --- /dev/null +++ b/test/utils/HookEnabledSwapRouter.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +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 {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; +import {Test} from "forge-std/Test.sol"; + +contract HookEnabledSwapRouter is Test, PoolTestBase { + using CurrencyLibrary for Currency; + + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + + error NoSwapOccurred(); + + struct CallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + IPoolManager.SwapParams params; + bytes hookData; + } + + struct TestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + function swap( + PoolKey memory key, + IPoolManager.SwapParams memory params, + TestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + manager.lock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + + function lockAcquired(bytes calldata rawData) external returns (bytes memory) { + require(msg.sender == address(manager)); + + CallbackData memory data = abi.decode(rawData, (CallbackData)); + + BalanceDelta delta = manager.swap(data.key, data.params, data.hookData); + + // Make sure youve added liquidity to the test pool! + if (BalanceDelta.unwrap(delta) == 0) revert NoSwapOccurred(); + + if (data.params.zeroForOne) { + _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); + _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + } else { + _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); + _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + } + + return abi.encode(delta); + } +} From 15a9c8a680ad728aa3d9c61d68b4660abb5d2d83 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Fri, 17 Nov 2023 17:06:03 -0500 Subject: [PATCH 28/80] fix: test router was borked --- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- foundry.toml | 1 + test/FullRange.t.sol | 4 +--- test/LimitOrder.t.sol | 9 +++++---- test/TWAMM.t.sol | 10 +++++++--- test/utils/HookEnabledSwapRouter.sol | 15 ++++++++++----- 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index df930ad0..08d86dfe 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -133445 \ No newline at end of file +133339 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index 10867d68..e21f3119 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -93791 \ No newline at end of file +93685 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 791a7d96..77bbcb9a 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -131720 \ No newline at end of file +131614 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 7c32d4b3..302fc02b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,5 +9,6 @@ cancun = true [profile.ci] fuzz_runs = 100000 +solc = "./lib/v4-core/bin/solc-static-linux" # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 04d5ce27..4c131856 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -108,9 +108,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { token1.approve(address(router), type(uint256).max); token2.approve(address(router), type(uint256).max); - initPool( - keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES - ); + initPool(keyWithLiq.currency0, keyWithLiq.currency1, fullRange, 3000, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( keyWithLiq.currency0, diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 007d84b7..415e30be 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -48,9 +48,7 @@ contract TestLimitOrder is Test, Deployers { } // key = PoolKey(currency0, currency1, 3000, 60, limitOrder); - (key, id) = initPoolAndAddLiquidity( - currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES - ); + (key, id) = initPoolAndAddLiquidity(currency0, currency1, limitOrder, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(limitOrder), type(uint256).max); token1.approve(address(limitOrder), type(uint256).max); @@ -130,7 +128,10 @@ contract TestLimitOrder is Test, Deployers { function testNotZeroForOneInRangeRevert() public { // swapping is free, there's no liquidity in the pool, so we only need to specify 1 wei router.swap( - key, IPoolManager.SwapParams(true, 1, SQRT_RATIO_1_1 - 1), HookEnabledSwapRouter.TestSettings(true, true), ZERO_BYTES + key, + IPoolManager.SwapParams(true, 1 ether, SQRT_RATIO_1_1 - 1), + HookEnabledSwapRouter.TestSettings(true, true), + ZERO_BYTES ); vm.expectRevert(LimitOrder.InRange.selector); limitOrder.place(key, -60, false, 1000000); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 1575d95d..266424db 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -61,7 +61,11 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); - TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); + TWAMMImplementation impl = new TWAMMImplementation( + manager, + 10_000, + twamm + ); (, bytes32[] memory writes) = vm.accesses(address(impl)); vm.etch(address(twamm), address(impl).code); // for each storage key that was written during the hook implementation, copy the value over @@ -236,7 +240,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, uint256(-amountDelta)); assertEq(token1Owed, orderAmount / 2); } @@ -261,7 +265,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { uint256 token1Owed = twamm.tokensOwed(poolKey.currency1, orderKey1.owner); // takes 10% off the remaining half (so 80% of original sellrate) - assertEq(updatedSellRate, originalSellRate * 80 / 100); + assertEq(updatedSellRate, (originalSellRate * 80) / 100); assertEq(token0Owed, orderAmount / 2); assertEq(token1Owed, uint256(-amountDelta)); } diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 8c5bec5b..b924ed61 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -2,19 +2,20 @@ pragma solidity ^0.8.20; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; 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 {PoolTestBase} from "@uniswap/v4-core/src/test/PoolTestBase.sol"; import {Test} from "forge-std/Test.sol"; -contract HookEnabledSwapRouter is Test, PoolTestBase { +contract HookEnabledSwapRouter is PoolTestBase { using CurrencyLibrary for Currency; - constructor(IPoolManager _manager) PoolTestBase(_manager) {} - error NoSwapOccurred(); + constructor(IPoolManager _manager) PoolTestBase(_manager) {} + struct CallbackData { address sender; TestSettings testSettings; @@ -54,10 +55,14 @@ contract HookEnabledSwapRouter is Test, PoolTestBase { if (data.params.zeroForOne) { _settle(data.key.currency0, data.sender, delta.amount0(), data.testSettings.settleUsingTransfer); - _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + if (delta.amount1() < 0) { + _take(data.key.currency1, data.sender, delta.amount1(), data.testSettings.withdrawTokens); + } } else { _settle(data.key.currency1, data.sender, delta.amount1(), data.testSettings.settleUsingTransfer); - _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + if (delta.amount0() < 0) { + _take(data.key.currency0, data.sender, delta.amount0(), data.testSettings.withdrawTokens); + } } return abi.encode(delta); From f12b2c357498cc418c25384c591036a0c1391e6b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Sat, 18 Nov 2023 09:53:54 -0500 Subject: [PATCH 29/80] exact out --- contracts/interfaces/IQuoter.sol | 18 +++++++++++++- contracts/lens/Quoter.sol | 40 ++++++++++++++++++++++++++++++-- test/Quoter.t.sol | 22 ++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 714529d7..095e965b 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -21,7 +21,7 @@ interface IQuoter { int128 currency1Delta; } - /// @notice Returns the delta amounts for a given exact input but for a swap of a single pool + /// @notice Returns the delta amounts for a given exact input swap of a single pool /// @param params The params for the quote, encoded as `ExactInputSingleParams` /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 @@ -53,4 +53,20 @@ interface IQuoter { uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksLoadedList ); + + /// @notice Returns the delta amounts for a given exact output swap of a single pool + /// @param params The params for the quote, encoded as `ExactOutputSingleParams` + /// poolKey The key for identifying a V4 pool + /// zeroForOne If the swap is from currency0 to currency1 + /// recipient The indented recipient of the output tokens + /// amountOut 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 sqrtPriceX96After The sqrt price of the pool after the swap + /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded + + function quoteExactOutputSingle(ExactOutputSingleParams calldata params) + external + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 2c02e4e7..8fcc6412 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -17,6 +17,9 @@ contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; using Hooks for IHooks; + /// @dev Transient storage variable used to check a safety condition in exact output swaps. + uint256 private amountOutCached; + // v4 Singleton contract IPoolManager public immutable manager; @@ -32,7 +35,7 @@ contract Quoter is IQuoter { { try manager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} catch (bytes memory reason) { - return _handleRevertExactInputSingle(reason, params.poolKey); + return _handleRevertSingle(reason, params.poolKey); } } @@ -89,6 +92,18 @@ contract Quoter is IQuoter { } } + /// @inheritdoc IQuoter + function quoteExactOutputSingle(ExactOutputSingleParams memory params) + public + override + returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) + { + try manager.lock(abi.encode(SwapInfo(SwapType.ExactOutputSingle, abi.encode(params)))) {} + catch (bytes memory reason) { + return _handleRevertSingle(reason, params.poolKey); + } + } + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); @@ -100,6 +115,14 @@ contract Quoter is IQuoter { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + assembly { + revert(add(0x20, result), mload(result)) + } + } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = + _quoteExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams))); + bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); assembly { revert(add(0x20, result), mload(result)) @@ -134,7 +157,7 @@ contract Quoter is IQuoter { return reason; } - function _handleRevertExactInputSingle(bytes memory reason, PoolKey memory poolKey) + function _handleRevertSingle(bytes memory reason, PoolKey memory poolKey) private view returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) @@ -223,6 +246,19 @@ contract Quoter is IQuoter { ); } + function _quoteExactOutputSingle(ExactOutputSingleParams memory params) + private + returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) + { + return _quoteExact( + params.poolKey, + params.zeroForOne, + -int256(int128(params.amountOut)), + params.sqrtPriceLimitX96, + params.hookData + ); + } + function _quoteExact( PoolKey memory poolKey, bool zeroForOne, diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 086c1817..93fe9d72 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -354,6 +354,28 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoadedList[1], 0); } + function testQuoter_quoteExactOutputSingle_0to1() public { + // encodePriceSqrt(100, 102) + uint160 sqrtPriceLimit = 78447570448055484695608110440; + + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactOutputSingle( + ExactOutputSingleParams({ + poolKey: key01, + zeroForOne: true, + recipient: address(this), + amountOut: type(uint128).max, + sqrtPriceLimitX96: sqrtPriceLimit, + hookData: ZERO_BYTES + }) + ); + + logDeltas(deltaAmounts); + assertEq(-deltaAmounts[0], 9981); + assertEq(sqrtPriceX96After, 78447570448055484695608110440); + assertEq(initializedTicksLoaded, 0); + } + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) internal pure From 0dc0c0b868d5ae8cd86c1f7def7b3d2714a29141 Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Mon, 20 Nov 2023 14:45:50 -0500 Subject: [PATCH 30/80] fix: alice comments --- .env | 2 -- test/FullRange.t.sol | 12 ++++-------- test/TWAMM.t.sol | 4 +--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.env b/.env index 721bb082..7859e840 100644 --- a/.env +++ b/.env @@ -5,5 +5,3 @@ if [[ "$OSTYPE" == "linux-gnu"* ]]; then elif [[ "$OSTYPE" == "darwin"* ]]; then export FOUNDRY_SOLC="./lib/v4-core/bin/solc-mac" fi - -# FOUNDRY_SOLC="./bin/solc-static-linux" diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 4c131856..e6c75f97 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -80,14 +80,10 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function setUp() public { deployFreshManagerAndRouters(); router = new HookEnabledSwapRouter(manager); - (currency0, currency1) = deployMintAndApprove2Currencies(); - - token0 = new MockERC20("TestA", "A", 18); - token0.mint(address(this), 2 ** 128); - token1 = new MockERC20("TestB", "B", 18); - token1.mint(address(this), 2 ** 128); - token2 = new MockERC20("TestC", "C", 18); - token2.mint(address(this), 2 ** 128); + MockERC20[] memory tokens = deployTokens(3, 2 ** 128); + token0 = tokens[0]; + token1 = tokens[1]; + token2 = tokens[2]; FullRangeImplementation impl = new FullRangeImplementation(manager, fullRange); vm.etch(address(fullRange), address(impl).code); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 266424db..14125657 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -76,9 +76,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { } } - poolKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 60, twamm); - poolId = poolKey.toId(); - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + (poolKey, poolId) = initPool(currency0, currency1, twamm, 3000, SQRT_RATIO_1_1, ZERO_BYTES); token0.approve(address(modifyPositionRouter), 100 ether); token1.approve(address(modifyPositionRouter), 100 ether); From 3665d41755a7cd677dd38154ba939b1853a9fea6 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 20 Nov 2023 18:30:07 -0500 Subject: [PATCH 31/80] fix ExactOutput --- contracts/lens/Quoter.sol | 84 ++++++++++++- contracts/libraries/SwapIntention.sol | 6 +- test/Quoter.t.sol | 166 +++++++++++++++++++++++--- 3 files changed, 235 insertions(+), 21 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 8fcc6412..82fff5f8 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "forge-std/console2.sol"; import "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; @@ -50,7 +51,7 @@ contract Quoter is IQuoter { { try manager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} catch (bytes memory reason) { - return _handleRevertExactInput(reason); + return _handleRevert(reason); } } @@ -104,6 +105,20 @@ contract Quoter is IQuoter { } } + function quoteExactOutput(ExactOutputParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + try manager.lock(abi.encode(SwapInfo(SwapType.ExactOutput, abi.encode(params)))) {} + catch (bytes memory reason) { + return _handleRevert(reason); + } + } + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); @@ -134,6 +149,17 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + assembly { + revert(add(0x20, result), mload(result)) + } + } else if (swapInfo.swapType == SwapType.ExactOutput) { + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = _quoteExactOutput(abi.decode(swapInfo.params, (ExactOutputParams))); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); assembly { revert(add(0x20, result), mload(result)) @@ -175,7 +201,7 @@ contract Quoter is IQuoter { initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } - function _handleRevertExactInput(bytes memory reason) + function _handleRevert(bytes memory reason) private pure returns ( @@ -246,6 +272,58 @@ contract Quoter is IQuoter { ); } + function _quoteExactOutput(ExactOutputParams memory params) + private + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + uint256 pathLength = params.path.length; + + deltaAmounts = new int128[](pathLength + 1); + sqrtPriceX96AfterList = new uint160[](pathLength); + initializedTicksLoadedList = new uint32[](pathLength); + Currency prevCurrencyIn; + uint128 prevAmountIn; + + console2.log( + "cIn: %s; cOut: %s", + Currency.unwrap(params.path[pathLength - 1].intermediateCurrency), + Currency.unwrap(params.currencyOut) + ); + + for (uint256 i = pathLength; i > 0; i--) { + (PoolKey memory poolKey, bool oneForZero) = SwapIntention.getPoolAndSwapDirection( + params.path[i - 1], i == pathLength ? params.currencyOut : prevCurrencyIn + ); + + (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); + + ExactOutputSingleParams memory singleParams = ExactOutputSingleParams({ + poolKey: poolKey, + zeroForOne: !oneForZero, + recipient: params.recipient, + amountOut: i == pathLength ? params.amountOut : prevAmountIn, + sqrtPriceLimitX96: 0, + hookData: params.path[i - 1].hookData + }); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactOutputSingle(singleParams); + + (int128 deltaIn, int128 deltaOut) = + !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); + deltaAmounts[i - 1] += deltaIn; + deltaAmounts[i] += deltaOut; + + prevAmountIn = !oneForZero ? uint128(curDeltas.amount0()) : uint128(curDeltas.amount1()); + prevCurrencyIn = params.path[i - 1].intermediateCurrency; + sqrtPriceX96AfterList[i - 1] = sqrtPriceX96After; + initializedTicksLoadedList[i - 1] = + PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + } + } + function _quoteExactOutputSingle(ExactOutputSingleParams memory params) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) @@ -253,7 +331,7 @@ contract Quoter is IQuoter { return _quoteExact( params.poolKey, params.zeroForOne, - -int256(int128(params.amountOut)), + -int256(uint256(params.amountOut)), params.sqrtPriceLimitX96, params.hookData ); diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index 0d40a594..6d5d1e94 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; +import "forge-std/console2.sol"; import {PathKey} from "./PathKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -64,9 +65,12 @@ struct ExactOutputParams { library SwapIntention { function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) internal - pure + view returns (PoolKey memory poolKey, bool zeroForOne) { + console2.log( + "currencyIn: %s; currency0: %s", Currency.unwrap(currencyIn), Currency.unwrap(params.intermediateCurrency) + ); (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency ? (currencyIn, params.intermediateCurrency) : (params.intermediateCurrency, currencyIn); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 93fe9d72..bd768c1f 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import "forge-std/console.sol"; +import "forge-std/console2.sol"; import {Test} from "forge-std/Test.sol"; import "../contracts/libraries/SwapIntention.sol"; import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; @@ -355,7 +355,7 @@ contract QuoterTest is Test, Deployers { } function testQuoter_quoteExactOutputSingle_0to1() public { - // encodePriceSqrt(100, 102) + //SQRT_RATIO_100_102 uint160 sqrtPriceLimit = 78447570448055484695608110440; (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter @@ -370,12 +370,142 @@ contract QuoterTest is Test, Deployers { }) ); - logDeltas(deltaAmounts); - assertEq(-deltaAmounts[0], 9981); - assertEq(sqrtPriceX96After, 78447570448055484695608110440); + assertEq(deltaAmounts[0], 9981); + assertEq(sqrtPriceX96After, sqrtPriceLimit); assertEq(initializedTicksLoaded, 0); } + function testQuoter_quoteExactOutputSingle_1to0() public { + //SQRT_RATIO_102_100 + uint160 sqrtPriceLimit = 80016521857016594389520272648; + + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter + .quoteExactOutputSingle( + ExactOutputSingleParams({ + poolKey: key01, + zeroForOne: false, + recipient: address(this), + amountOut: type(uint128).max, + sqrtPriceLimitX96: sqrtPriceLimit, + hookData: ZERO_BYTES + }) + ); + + assertEq(deltaAmounts[1], 9981); + assertEq(sqrtPriceX96After, sqrtPriceLimit); + assertEq(initializedTicksLoaded, 0); + } + + function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + ExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 15273); + assertEq(sqrtPriceX96AfterList[0], 78055527257643669242286029831); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded_initialiedAfter() public { + tokenPath.push(token0); + tokenPath.push(token2); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 6143); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 6200); + assertEq(sqrtPriceX96AfterList[0], 78757225449310403327341205211); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_1TickLoaded() public { + tokenPath.push(token0); + tokenPath.push(token2); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 4000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 4029); + assertEq(sqrtPriceX96AfterList[0], 78924219757724709840818372098); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingInitialized() public { + setupPoolWithZeroTickInitialized(key02); + tokenPath.push(token0); + tokenPath.push(token2); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 100); + + // Tick 0 initialized. Tick after = 1 + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 102); + assertEq(sqrtPriceX96AfterList[0], 79224329176051641448521403903); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_0to2_0TickLoaded_startingNotInitialized() public { + tokenPath.push(token0); + tokenPath.push(token2); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 10); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 12); + assertEq(sqrtPriceX96AfterList[0], 79227408033628034983534698435); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { + tokenPath.push(token2); + tokenPath.push(token0); + ExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput( + ExactOutputParams({ + currencyOut: Currency.wrap(address(token0)), + path: new PathKey[](0), + recipient: address(this), + amountOut: uint128(15000), + sqrtPriceLimitX96: 0 + }) + ); + + assertEq(deltaAmounts[1], 15273); + assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); + assertEq(initializedTicksLoadedList[0], 2); + } + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) internal pure @@ -492,24 +622,26 @@ contract QuoterTest is Test, Deployers { params.amountIn = uint128(amountIn); } - function logTicksLoaded(uint32[] memory num) private view { - console.logString("=== Num Ticks Crossed ==="); - for (uint256 i = 0; i < num.length; i++) { - console.logUint(num[i]); + function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) + internal + view + returns (ExactOutputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); } - } - function logSqrtPrices(uint160[] memory prices) private view { - console.logString("=== Sqrt Prices After ==="); - for (uint256 i = 0; i < prices.length; i++) { - console.logUint(prices[i]); - } + params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.path = path; + params.recipient = address(this); + params.amountOut = uint128(amountOut); } function logDeltas(int128[] memory deltas) private view { - console.logString("=== Delta Amounts ==="); + console2.logString("=== Delta Amounts ==="); for (uint256 i = 0; i < deltas.length; i++) { - console.logInt(deltas[i]); + console2.logInt(deltas[i]); } } } From f432926a69725426c2dfc11e7c754048a4cfc192 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 21 Nov 2023 10:43:30 -0800 Subject: [PATCH 32/80] add ExactOput unit tests --- contracts/lens/Quoter.sol | 7 -- contracts/libraries/SwapIntention.sol | 4 - test/Quoter.t.sol | 109 ++++++++++++++++++++------ 3 files changed, 85 insertions(+), 35 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 82fff5f8..44996312 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; @@ -288,12 +287,6 @@ contract Quoter is IQuoter { Currency prevCurrencyIn; uint128 prevAmountIn; - console2.log( - "cIn: %s; cOut: %s", - Currency.unwrap(params.path[pathLength - 1].intermediateCurrency), - Currency.unwrap(params.currencyOut) - ); - for (uint256 i = pathLength; i > 0; i--) { (PoolKey memory poolKey, bool oneForZero) = SwapIntention.getPoolAndSwapDirection( params.path[i - 1], i == pathLength ? params.currencyOut : prevCurrencyIn diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index 6d5d1e94..976a03e8 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import {PathKey} from "./PathKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -68,9 +67,6 @@ library SwapIntention { view returns (PoolKey memory poolKey, bool zeroForOne) { - console2.log( - "currencyIn: %s; currency0: %s", Currency.unwrap(currencyIn), Currency.unwrap(params.intermediateCurrency) - ); (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency ? (currencyIn, params.intermediateCurrency) : (params.intermediateCurrency, currencyIn); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index bd768c1f..022b1060 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import {Test} from "forge-std/Test.sol"; import "../contracts/libraries/SwapIntention.sol"; import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; @@ -198,7 +197,7 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoadedList[0], 1); } - function testQuoter_quoteExactInput_0to2_1TicksLoaded() public { + function testQuoter_quoteExactInput_0to2_1TickLoaded() public { tokenPath.push(token0); tokenPath.push(token2); @@ -217,7 +216,7 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoadedList[0], 1); } - function testQuoter_quoteExactInput_0to2_0TicksLoaded_startingNotInitialized() public { + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { tokenPath.push(token0); tokenPath.push(token2); ExactInputParams memory params = getExactInputParams(tokenPath, 10); @@ -233,7 +232,7 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoadedList[0], 0); } - function testQuoter_quoteExactInput_0to2_0TicksLoaded_startingInitialized() public { + function testQuoter_quoteExactInput_0to2_0TickLoaded_startingInitialized() public { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token0); tokenPath.push(token2); @@ -285,7 +284,7 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoadedList[0], 2); } - function testQuoter_quoteExactInput_2to0_0TicksLoaded_startingInitialized() public { + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingInitialized() public { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token2); tokenPath.push(token0); @@ -304,7 +303,7 @@ contract QuoterTest is Test, Deployers { } // 2->0 starting not initialized - function testQuoter_quoteExactInput_2to0_0TicksLoaded_startingNotInitialized() public { + function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { tokenPath.push(token2); tokenPath.push(token0); ExactInputParams memory params = getExactInputParams(tokenPath, 103); @@ -491,21 +490,90 @@ contract QuoterTest is Test, Deployers { int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutput( - ExactOutputParams({ - currencyOut: Currency.wrap(address(token0)), - path: new PathKey[](0), - recipient: address(this), - amountOut: uint128(15000), - sqrtPriceLimitX96: 0 - }) - ); + ) = quoter.quoteExactOutput(params); - assertEq(deltaAmounts[1], 15273); - assertEq(sqrtPriceX96AfterList[0], 80001962924147897865541384515); + assertEq(deltaAmounts[0], 15273); + assertEq(sqrtPriceX96AfterList[0], 80418414376567919517220409857); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 2); + } + + function testQuoter_quoteExactOutput_2to0_2TicksLoaded_initialiedAfter() public { + tokenPath.push(token2); + tokenPath.push(token0); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 6223); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = 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 { + tokenPath.push(token2); + tokenPath.push(token0); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 6000); + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 6055); + assertEq(sqrtPriceX96AfterList[0], 79690640184021170956740081887); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 1); + } + + function testQuoter_quoteExactOutput_2to1() public { + tokenPath.push(token2); + tokenPath.push(token1); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 9871); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutput(params); + + assertEq(deltaAmounts[0], 10000); + assertEq(sqrtPriceX96AfterList[0], 80018020393569259756601362385); + assertEq(initializedTicksLoadedList.length, 1); + assertEq(initializedTicksLoadedList[0], 0); + } + + function testQuoter_quoteExactOutput_0to2to1() public { + tokenPath.push(token0); + tokenPath.push(token2); + tokenPath.push(token1); + + ExactOutputParams memory params = getExactOutputParams(tokenPath, 9745); + + ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = 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) internal pure @@ -637,11 +705,4 @@ contract QuoterTest is Test, Deployers { params.recipient = address(this); params.amountOut = uint128(amountOut); } - - function logDeltas(int128[] memory deltas) private view { - console2.logString("=== Delta Amounts ==="); - for (uint256 i = 0; i < deltas.length; i++) { - console2.logInt(deltas[i]); - } - } } From eb3441fff439254b85f99a824452788a9906731d Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 21 Nov 2023 11:19:02 -0800 Subject: [PATCH 33/80] add quoteExactOutputBatch --- contracts/lens/Quoter.sol | 39 ++++++++++++++++ contracts/libraries/SwapIntention.sol | 11 ++++- test/Quoter.t.sol | 66 +++++++++++++++++++++++---- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 44996312..9317c37b 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -118,6 +118,45 @@ contract Quoter is IQuoter { } } + function quoteExactOutputBatch(ExactOutputSingleBatchParams memory params) + external + returns ( + IQuoter.PoolDeltas[] memory deltas, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) + { + if ( + params.zeroForOnes.length != params.recipients.length + || params.recipients.length != params.amountOuts.length + || params.amountOuts.length != params.sqrtPriceLimitX96s.length + || params.sqrtPriceLimitX96s.length != params.hookData.length + ) { + revert InvalidQuoteBatchParams(); + } + + deltas = new IQuoter.PoolDeltas[](params.amountOuts.length); + sqrtPriceX96AfterList = new uint160[](params.amountOuts.length); + initializedTicksLoadedList = new uint32[](params.amountOuts.length); + + for (uint256 i = 0; i < params.amountOuts.length; i++) { + ExactOutputSingleParams memory singleParams = ExactOutputSingleParams({ + poolKey: params.poolKey, + zeroForOne: params.zeroForOnes[i], + recipient: params.recipients[i], + amountOut: params.amountOuts[i], + sqrtPriceLimitX96: params.sqrtPriceLimitX96s[i], + hookData: params.hookData[i] + }); + (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = + quoteExactOutputSingle(singleParams); + + deltas[i] = IQuoter.PoolDeltas({currency0Delta: deltaAmounts[0], currency1Delta: deltaAmounts[1]}); + sqrtPriceX96AfterList[i] = sqrtPriceX96After; + initializedTicksLoadedList[i] = initializedTicksLoaded; + } + } + function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index 976a03e8..ab02ffce 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -53,6 +53,15 @@ struct ExactOutputSingleParams { bytes hookData; } +struct ExactOutputSingleBatchParams { + PoolKey poolKey; + bool[] zeroForOnes; + address[] recipients; + uint128[] amountOuts; + uint160[] sqrtPriceLimitX96s; + bytes[] hookData; +} + struct ExactOutputParams { Currency currencyOut; PathKey[] path; @@ -64,7 +73,7 @@ struct ExactOutputParams { library SwapIntention { function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) internal - view + pure returns (PoolKey memory poolKey, bool zeroForOne) { (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 022b1060..1b4f7f76 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -29,6 +29,9 @@ contract QuoterTest is Test, Deployers { // Max tick for full range with tick spacing of 60 int24 internal constant MAX_TICK = -MIN_TICK; + uint160 internal constant SQRT_RATIO_100_102 = 78447570448055484695608110440; + uint160 internal constant SQRT_RATIO_102_100 = 80016521857016594389520272648; + uint256 internal constant CONTROLLER_GAS_LIMIT = 500000; Quoter quoter; @@ -354,9 +357,6 @@ contract QuoterTest is Test, Deployers { } function testQuoter_quoteExactOutputSingle_0to1() public { - //SQRT_RATIO_100_102 - uint160 sqrtPriceLimit = 78447570448055484695608110440; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( ExactOutputSingleParams({ @@ -364,20 +364,17 @@ contract QuoterTest is Test, Deployers { zeroForOne: true, recipient: address(this), amountOut: type(uint128).max, - sqrtPriceLimitX96: sqrtPriceLimit, + sqrtPriceLimitX96: SQRT_RATIO_100_102, hookData: ZERO_BYTES }) ); assertEq(deltaAmounts[0], 9981); - assertEq(sqrtPriceX96After, sqrtPriceLimit); + assertEq(sqrtPriceX96After, SQRT_RATIO_100_102); assertEq(initializedTicksLoaded, 0); } function testQuoter_quoteExactOutputSingle_1to0() public { - //SQRT_RATIO_102_100 - uint160 sqrtPriceLimit = 80016521857016594389520272648; - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( ExactOutputSingleParams({ @@ -385,16 +382,65 @@ contract QuoterTest is Test, Deployers { zeroForOne: false, recipient: address(this), amountOut: type(uint128).max, - sqrtPriceLimitX96: sqrtPriceLimit, + sqrtPriceLimitX96: SQRT_RATIO_102_100, hookData: ZERO_BYTES }) ); assertEq(deltaAmounts[1], 9981); - assertEq(sqrtPriceX96After, sqrtPriceLimit); + assertEq(sqrtPriceX96After, SQRT_RATIO_102_100); assertEq(initializedTicksLoaded, 0); } + function testQuoter_quoteExactOutputBatch() public { + bool[] memory zeroForOnes = new bool[](2); + zeroForOnes[0] = true; + zeroForOnes[1] = false; + + address[] memory recipients = new address[](2); + recipients[0] = address(this); + recipients[1] = address(this); + + // repeat for the three arrays below + uint128[] memory amountOuts = new uint128[](2); + amountOuts[0] = type(uint128).max; + amountOuts[1] = type(uint128).max; + + uint160[] memory sqrtPriceLimitX96s = new uint160[](2); + sqrtPriceLimitX96s[0] = SQRT_RATIO_100_102; + sqrtPriceLimitX96s[1] = SQRT_RATIO_102_100; + + bytes[] memory hookData = new bytes[](2); + hookData[0] = ZERO_BYTES; + hookData[1] = ZERO_BYTES; + + ( + IQuoter.PoolDeltas[] memory deltas, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ) = quoter.quoteExactOutputBatch( + ExactOutputSingleBatchParams({ + poolKey: key01, + zeroForOnes: zeroForOnes, + recipients: recipients, + amountOuts: amountOuts, + sqrtPriceLimitX96s: sqrtPriceLimitX96s, + hookData: hookData + }) + ); + assertEq(deltas.length, 2); + assertEq(uint128(deltas[0].currency0Delta), 9981); + assertEq(uint128(deltas[1].currency1Delta), 9981); + + assertEq(sqrtPriceX96AfterList.length, 2); + assertEq(sqrtPriceX96AfterList[0], SQRT_RATIO_100_102); + assertEq(sqrtPriceX96AfterList[1], SQRT_RATIO_102_100); + + assertEq(initializedTicksLoadedList.length, 2); + assertEq(initializedTicksLoadedList[0], 0); + assertEq(initializedTicksLoadedList[1], 0); + } + function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); From 94f1a207d8af196750c49370e94bdf8a052bd9b3 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 11:16:58 -0800 Subject: [PATCH 34/80] remove solhint config --- .solhint.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .solhint.json diff --git a/.solhint.json b/.solhint.json deleted file mode 100644 index 669c0615..00000000 --- a/.solhint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "solhint:recommended", - "rules": { - "no-inline-assembly": "off", - "no-global-import": "off", - "no-empty-blocks": "off", - "func-visibility": ["warn", { "ignoreConstructors": true }] - } -} From 45fd1569344c4bfc323620233e8146489ffd360b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 11:17:50 -0800 Subject: [PATCH 35/80] remove newline --- contracts/interfaces/IQuoter.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 095e965b..11e4f576 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -32,7 +32,6 @@ interface IQuoter { /// @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(ExactInputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); @@ -65,7 +64,6 @@ interface IQuoter { /// @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(ExactOutputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); From 57330b2a90f732d3ef37ef42e044cbbb129587c6 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 11:22:18 -0800 Subject: [PATCH 36/80] add QuoteExactOutput in interface --- contracts/interfaces/IQuoter.sol | 17 +++++++++++++++++ contracts/lens/Quoter.sol | 4 +++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 11e4f576..555d8ce6 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -67,4 +67,21 @@ interface IQuoter { function quoteExactOutputSingle(ExactOutputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); + + /// @notice Returns the delta amounts along the swap path for a given exact output swap + /// @param params the params for the quote, encoded as 'ExactInputParams' + /// currencyOut The output currency of the swap + /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info + /// recipient The indented recipient of the output tokens + /// amountOut 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(ExactOutputParams memory params) + external + returns ( + int128[] memory deltaAmounts, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksLoadedList + ); } diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 9317c37b..9ece3f1f 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -104,8 +104,10 @@ contract Quoter is IQuoter { } } + /// @inheritdoc IQuoter function quoteExactOutput(ExactOutputParams memory params) - external + public + override returns ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, From c841c3459c2c5bf7e51433a78ac747359cfb8295 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 11:31:39 -0800 Subject: [PATCH 37/80] refactor lockAcquired --- contracts/lens/Quoter.sol | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 9ece3f1f..f76f29d9 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "../libraries/SwapIntention.sol"; -import {IQuoter} from "../interfaces/IQuoter.sol"; -import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -12,6 +9,9 @@ import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; +import "../libraries/SwapIntention.sol"; +import {IQuoter} from "../interfaces/IQuoter.sol"; +import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; @@ -165,23 +165,18 @@ contract Quoter is IQuoter { } SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); + bytes memory result; if (swapInfo.swapType == SwapType.ExactInputSingle) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); - bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - assembly { - revert(add(0x20, result), mload(result)) - } + result = abi.encode(deltas, sqrtPriceX96After, tickAfter); } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams))); - bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - assembly { - revert(add(0x20, result), mload(result)) - } + result = abi.encode(deltas, sqrtPriceX96After, tickAfter); } else if (swapInfo.swapType == SwapType.ExactInput) { ( int128[] memory deltaAmounts, @@ -189,10 +184,7 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - assembly { - revert(add(0x20, result), mload(result)) - } + result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); } else if (swapInfo.swapType == SwapType.ExactOutput) { ( int128[] memory deltaAmounts, @@ -200,13 +192,13 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) = _quoteExactOutput(abi.decode(swapInfo.params, (ExactOutputParams))); - bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - assembly { - revert(add(0x20, result), mload(result)) - } + result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); } else { revert InvalidQuoteType(); } + assembly { + revert(add(0x20, result), mload(result)) + } } function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { From a13509b273f2446a79feab6e4e2874357d8c193f Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 12:52:34 -0800 Subject: [PATCH 38/80] move magic numbers to constants + doc --- contracts/lens/Quoter.sol | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index f76f29d9..f7c30d50 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -23,6 +24,12 @@ contract Quoter is IQuoter { // v4 Singleton contract IPoolManager public immutable manager; + /// @dev function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes + uint256 internal constant MINIMUM_REASON_LENGTH = 68; + + /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes + uint256 internal constant MINIMUM_VALID_REASON_LENGTH = 96; + constructor(address _poolManager) { manager = IPoolManager(_poolManager); } @@ -202,9 +209,8 @@ contract Quoter is IQuoter { } function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { - if (reason.length < 96) { - // function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes - if (reason.length < 68) { + if (reason.length < MINIMUM_VALID_REASON_LENGTH) { + if (reason.length < MINIMUM_REASON_LENGTH) { revert UnexpectedRevertBytes(); } assembly { From dcd6471a26b5af63ce67231c3a358db62291b922 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 22 Nov 2023 14:32:02 -0800 Subject: [PATCH 39/80] add more natspec --- contracts/lens/Quoter.sol | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index f7c30d50..7c58fea8 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -208,6 +208,7 @@ contract Quoter is IQuoter { } } + /// @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_REASON_LENGTH) { if (reason.length < MINIMUM_REASON_LENGTH) { @@ -221,6 +222,7 @@ contract Quoter is IQuoter { return reason; } + /// @dev parse revert bytes from a single-pool quote function _handleRevertSingle(bytes memory reason, PoolKey memory poolKey) private view @@ -239,6 +241,7 @@ contract Quoter is IQuoter { initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } + /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, sqrtPriceX96After, and initializedTicksLoaded function _handleRevert(bytes memory reason) private pure @@ -301,7 +304,7 @@ contract Quoter is IQuoter { private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - return _quoteExact( + return _swap( params.poolKey, params.zeroForOne, int256(int128(params.amountIn)), @@ -360,7 +363,7 @@ contract Quoter is IQuoter { private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { - return _quoteExact( + return _swap( params.poolKey, params.zeroForOne, -int256(uint256(params.amountOut)), @@ -369,7 +372,8 @@ contract Quoter is IQuoter { ); } - function _quoteExact( + /// @dev Execute a swap and return the amounts delta, as well as relevant pool state + function _swap( PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, @@ -388,6 +392,7 @@ contract Quoter is IQuoter { (sqrtPriceX96After, tickAfter,,) = manager.getSlot0(poolKey.toId()); } + /// @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_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 From 9fb0ab4e380f12968ffbb42d4b1f3258defed2c5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 28 Nov 2023 10:38:34 -0500 Subject: [PATCH 40/80] natspec --- contracts/lens/Quoter.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 7c58fea8..da72d832 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -27,6 +27,7 @@ contract Quoter is IQuoter { /// @dev function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes uint256 internal constant MINIMUM_REASON_LENGTH = 68; + /// @dev min valid reason is 3-words long /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes uint256 internal constant MINIMUM_VALID_REASON_LENGTH = 96; From 3f0e6a76ddc06c5169c937755e01904038dc2625 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 28 Nov 2023 14:50:21 -0500 Subject: [PATCH 41/80] named imports --- contracts/interfaces/IQuoter.sol | 1 + contracts/lens/Quoter.sol | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 555d8ce6..04dbb8b2 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -14,6 +14,7 @@ interface IQuoter { error InvalidQuoteTypeInRevert(); error InvalidLockAcquiredSender(); error InvalidQuoteBatchParams(); + error NotSelf(); error UnexpectedRevertBytes(); struct PoolDeltas { diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index da72d832..cec9e77c 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -10,7 +10,17 @@ import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import "../libraries/SwapIntention.sol"; +import { + SwapType, + SwapInfo, + SwapIntention, + ExactInputSingleParams, + ExactInputSingleBatchParams, + ExactInputParams, + ExactOutputSingleParams, + ExactOutputSingleBatchParams, + ExactOutputParams +} from "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; @@ -31,6 +41,12 @@ contract Quoter is IQuoter { /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes uint256 internal constant MINIMUM_VALID_REASON_LENGTH = 96; + /// @dev Only this address may call this function + modifier selfOnly() { + if (msg.sender != address(this)) revert NotSelf(); + _; + } + constructor(address _poolManager) { manager = IPoolManager(_poolManager); } From 4a8e57a6e3a3786189088d00efc6f9f1bd6ff5d5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 13:32:54 -0500 Subject: [PATCH 42/80] self-call branching --- contracts/interfaces/IQuoter.sol | 1 + contracts/lens/Quoter.sol | 176 +++++++++++++++---------------- 2 files changed, 89 insertions(+), 88 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 04dbb8b2..ea7a5f90 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -14,6 +14,7 @@ interface IQuoter { error InvalidQuoteTypeInRevert(); error InvalidLockAcquiredSender(); error InvalidQuoteBatchParams(); + error LockFailure(); error NotSelf(); error UnexpectedRevertBytes(); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index cec9e77c..bb54d993 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -57,7 +57,7 @@ contract Quoter is IQuoter { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(abi.encode(SwapInfo(SwapType.ExactInputSingle, abi.encode(params)))) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason, params.poolKey); } @@ -72,7 +72,7 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encode(SwapInfo(SwapType.ExactInput, abi.encode(params)))) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -122,7 +122,7 @@ contract Quoter is IQuoter { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(abi.encode(SwapInfo(SwapType.ExactOutputSingle, abi.encode(params)))) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason, params.poolKey); } @@ -138,7 +138,7 @@ contract Quoter is IQuoter { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encode(SwapInfo(SwapType.ExactOutput, abi.encode(params)))) {} + try manager.lock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -183,46 +183,54 @@ contract Quoter is IQuoter { } } - function lockAcquired(bytes calldata encodedSwapIntention) external returns (bytes memory) { + function lockAcquired(bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); } - SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); - bytes memory result; - - if (swapInfo.swapType == SwapType.ExactInputSingle) { - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = - _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); - - result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = - _quoteExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams))); - - result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - } else if (swapInfo.swapType == SwapType.ExactInput) { - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); - - result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - } else if (swapInfo.swapType == SwapType.ExactOutput) { - ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = _quoteExactOutput(abi.decode(swapInfo.params, (ExactOutputParams))); - - result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - } else { - revert InvalidQuoteType(); - } + (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 + /// @solidity memory-safe-assembly assembly { - revert(add(0x20, result), mload(result)) + revert(add(returnData, 32), mload(returnData)) } + // SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); + // bytes memory result; + + // if (swapInfo.swapType == SwapType.ExactInputSingle) { + // (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = + // _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); + + // result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + // } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { + // (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = + // _quoteExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams))); + + // result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + // } else if (swapInfo.swapType == SwapType.ExactInput) { + // ( + // int128[] memory deltaAmounts, + // uint160[] memory sqrtPriceX96AfterList, + // uint32[] memory initializedTicksLoadedList + // ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + + // result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + // } else if (swapInfo.swapType == SwapType.ExactOutput) { + // ( + // int128[] memory deltaAmounts, + // uint160[] memory sqrtPriceX96AfterList, + // uint32[] memory initializedTicksLoadedList + // ) = _quoteExactOutput(abi.decode(swapInfo.params, (ExactOutputParams))); + + // result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + // } else { + // revert InvalidQuoteType(); + // } + // assembly { + // revert(add(0x20, result), mload(result)) + // } } /// @dev check revert bytes and pass through if considered valid; otherwise revert with different message @@ -273,19 +281,12 @@ contract Quoter is IQuoter { abi.decode(reason, (int128[], uint160[], uint32[])); } - function _quoteExactInput(ExactInputParams memory params) - private - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { + function _quoteExactInput(ExactInputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - deltaAmounts = new int128[](pathLength + 1); - sqrtPriceX96AfterList = new uint160[](pathLength); - initializedTicksLoadedList = new uint32[](pathLength); + int128[] memory deltaAmounts = new int128[](pathLength + 1); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); + uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); Currency prevCurrencyOut; uint128 prevAmountOut; @@ -294,15 +295,13 @@ contract Quoter is IQuoter { SwapIntention.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); - ExactInputSingleParams memory singleParams = ExactInputSingleParams({ - poolKey: poolKey, - zeroForOne: zeroForOne, - recipient: params.recipient, - amountIn: i == 0 ? params.amountIn : prevAmountOut, - sqrtPriceLimitX96: 0, - hookData: params.path[i].hookData - }); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactInputSingle(singleParams); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + poolKey, + zeroForOne, + int256(int128(i == 0 ? params.amountIn : prevAmountOut)), + 0, + params.path[i].hookData + ); (int128 deltaIn, int128 deltaOut) = zeroForOne ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); @@ -315,34 +314,32 @@ contract Quoter is IQuoter { initializedTicksLoadedList[i] = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + assembly { + revert(add(0x20, result), mload(result)) + } } - function _quoteExactInputSingle(ExactInputSingleParams memory params) - private - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) - { - return _swap( + function _quoteExactInputSingle(ExactInputSingleParams memory params) public selfOnly returns (bytes memory) { + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, int256(int128(params.amountIn)), params.sqrtPriceLimitX96, params.hookData ); + bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + assembly { + revert(add(0x20, result), mload(result)) + } } - function _quoteExactOutput(ExactOutputParams memory params) - private - returns ( - int128[] memory deltaAmounts, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { + function _quoteExactOutput(ExactOutputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; - deltaAmounts = new int128[](pathLength + 1); - sqrtPriceX96AfterList = new uint160[](pathLength); - initializedTicksLoadedList = new uint32[](pathLength); + int128[] memory deltaAmounts = new int128[](pathLength + 1); + uint160[] memory sqrtPriceX96AfterList = new uint160[](pathLength); + uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); Currency prevCurrencyIn; uint128 prevAmountIn; @@ -353,15 +350,13 @@ contract Quoter is IQuoter { (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); - ExactOutputSingleParams memory singleParams = ExactOutputSingleParams({ - poolKey: poolKey, - zeroForOne: !oneForZero, - recipient: params.recipient, - amountOut: i == pathLength ? params.amountOut : prevAmountIn, - sqrtPriceLimitX96: 0, - hookData: params.path[i - 1].hookData - }); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _quoteExactOutputSingle(singleParams); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( + poolKey, + !oneForZero, + -int256(int128(i == pathLength ? params.amountOut : prevAmountIn)), + 0, + params.path[i - 1].hookData + ); (int128 deltaIn, int128 deltaOut) = !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); @@ -374,19 +369,24 @@ contract Quoter is IQuoter { initializedTicksLoadedList[i - 1] = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); } + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); + assembly { + revert(add(0x20, result), mload(result)) + } } - function _quoteExactOutputSingle(ExactOutputSingleParams memory params) - private - returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) - { - return _swap( + function _quoteExactOutputSingle(ExactOutputSingleParams memory params) public selfOnly returns (bytes memory) { + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, -int256(uint256(params.amountOut)), params.sqrtPriceLimitX96, params.hookData ); + bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + assembly { + revert(add(0x20, result), mload(result)) + } } /// @dev Execute a swap and return the amounts delta, as well as relevant pool state From b1c2292369fefad26f5badbf2c42ebccd52601f1 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 13:33:24 -0500 Subject: [PATCH 43/80] remove old code --- contracts/lens/Quoter.sol | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index bb54d993..03f13cc7 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -196,41 +196,6 @@ contract Quoter is IQuoter { assembly { revert(add(returnData, 32), mload(returnData)) } - // SwapInfo memory swapInfo = abi.decode(encodedSwapIntention, (SwapInfo)); - // bytes memory result; - - // if (swapInfo.swapType == SwapType.ExactInputSingle) { - // (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = - // _quoteExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams))); - - // result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - // } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - // (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = - // _quoteExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams))); - - // result = abi.encode(deltas, sqrtPriceX96After, tickAfter); - // } else if (swapInfo.swapType == SwapType.ExactInput) { - // ( - // int128[] memory deltaAmounts, - // uint160[] memory sqrtPriceX96AfterList, - // uint32[] memory initializedTicksLoadedList - // ) = _quoteExactInput(abi.decode(swapInfo.params, (ExactInputParams))); - - // result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - // } else if (swapInfo.swapType == SwapType.ExactOutput) { - // ( - // int128[] memory deltaAmounts, - // uint160[] memory sqrtPriceX96AfterList, - // uint32[] memory initializedTicksLoadedList - // ) = _quoteExactOutput(abi.decode(swapInfo.params, (ExactOutputParams))); - - // result = abi.encode(deltaAmounts, sqrtPriceX96AfterList, initializedTicksLoadedList); - // } else { - // revert InvalidQuoteType(); - // } - // assembly { - // revert(add(0x20, result), mload(result)) - // } } /// @dev check revert bytes and pass through if considered valid; otherwise revert with different message From 238a23e31b70b77676becbf352b8255c9a4be774 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 13:34:42 -0500 Subject: [PATCH 44/80] remove console2 import --- contracts/lens/Quoter.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 03f13cc7..28afa996 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; From 5daedb0bb22ac825912fe6ad69704bca09871c49 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 13:53:16 -0500 Subject: [PATCH 45/80] refactor PathKeyLib --- contracts/lens/Quoter.sol | 6 +++--- contracts/libraries/PathKey.sol | 16 ++++++++++++++++ contracts/libraries/SwapIntention.sol | 17 ++--------------- test/Quoter.t.sol | 12 +++++++++++- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 28afa996..dc6ccf49 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -12,7 +12,6 @@ import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; import { SwapType, SwapInfo, - SwapIntention, ExactInputSingleParams, ExactInputSingleBatchParams, ExactInputParams, @@ -22,6 +21,7 @@ import { } from "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; +import {PathKeyLib} from "../libraries/PathKey.sol"; contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; @@ -256,7 +256,7 @@ contract Quoter is IQuoter { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = - SwapIntention.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); + PathKeyLib.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( @@ -308,7 +308,7 @@ contract Quoter is IQuoter { uint128 prevAmountIn; for (uint256 i = pathLength; i > 0; i--) { - (PoolKey memory poolKey, bool oneForZero) = SwapIntention.getPoolAndSwapDirection( + (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( params.path[i - 1], i == pathLength ? params.currencyOut : prevCurrencyIn ); diff --git a/contracts/libraries/PathKey.sol b/contracts/libraries/PathKey.sol index 4c0186ab..d13ccfce 100644 --- a/contracts/libraries/PathKey.sol +++ b/contracts/libraries/PathKey.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; struct PathKey { Currency intermediateCurrency; @@ -12,3 +13,18 @@ struct PathKey { IHooks hooks; bytes hookData; } + +library PathKeyLib { + function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) + internal + pure + returns (PoolKey memory poolKey, bool zeroForOne) + { + (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency + ? (currencyIn, params.intermediateCurrency) + : (params.intermediateCurrency, currencyIn); + + zeroForOne = currencyIn == currency0; + poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + } +} diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index ab02ffce..e6404812 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; -import {PathKey} from "./PathKey.sol"; +import {PathKey, PathKeyLib} from "./PathKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; @@ -70,17 +70,4 @@ struct ExactOutputParams { uint160 sqrtPriceLimitX96; } -library SwapIntention { - function getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) - internal - pure - returns (PoolKey memory poolKey, bool zeroForOne) - { - (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency - ? (currencyIn, params.intermediateCurrency) - : (params.intermediateCurrency, currencyIn); - - zeroForOne = currencyIn == currency0; - poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); - } -} +library SwapIntention {} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 1b4f7f76..20043d51 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -3,7 +3,17 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; -import "../contracts/libraries/SwapIntention.sol"; +import { + SwapType, + SwapInfo, + ExactInputSingleParams, + ExactInputSingleBatchParams, + ExactInputParams, + ExactOutputSingleParams, + ExactOutputSingleBatchParams, + ExactOutputParams +} from "../contracts/libraries/SwapIntention.sol"; +import {PathKey} from "../contracts/libraries/PathKey.sol"; import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; From b6a7373b951cef97347f53ec67ad6328752a9a48 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 14:44:36 -0500 Subject: [PATCH 46/80] amountOutCached --- contracts/lens/Quoter.sol | 5 ++++- contracts/libraries/PoolTicksCounter.sol | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index dc6ccf49..e5a9a01f 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -27,7 +27,7 @@ contract Quoter is IQuoter { using PoolIdLibrary for PoolKey; using Hooks for IHooks; - /// @dev Transient storage variable used to check a safety condition in exact output swaps. + /// @dev cache used to check a safety condition in exact output swaps. uint256 private amountOutCached; // v4 Singleton contract @@ -121,8 +121,11 @@ contract Quoter is IQuoter { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { + if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.amountOut; + try manager.lock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { + if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; return _handleRevertSingle(reason, params.poolKey); } } diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index 6c3c2780..95212893 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -16,7 +16,7 @@ library PoolTicksCounter { function countInitializedTicksLoaded(IPoolManager self, PoolKey memory key, int24 tickBefore, int24 tickAfter) internal view - returns (uint32 initializedTicksCrossed) + returns (uint32 initializedTicksLoaded) { int16 wordPosLower; int16 wordPosHigher; @@ -72,21 +72,21 @@ library PoolTicksCounter { uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); uint256 masked = bmLower & mask; - initializedTicksCrossed += countOneBits(masked); + initializedTicksLoaded += countOneBits(masked); wordPosLower++; // Reset our mask so we consider all bits on the next iteration. mask = type(uint256).max; } if (tickAfterInitialized) { - initializedTicksCrossed -= 1; + initializedTicksLoaded -= 1; } if (tickBeforeInitialized) { - initializedTicksCrossed -= 1; + initializedTicksLoaded -= 1; } - return initializedTicksCrossed; + return initializedTicksLoaded; } function countOneBits(uint256 x) private pure returns (uint16) { From ebac179b8b383784ae896195f58e7adf04f1e1e5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 14:49:13 -0500 Subject: [PATCH 47/80] inherit ILockCallback --- contracts/lens/Quoter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index e5a9a01f..d8f64eb3 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; @@ -23,7 +24,7 @@ import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKeyLib} from "../libraries/PathKey.sol"; -contract Quoter is IQuoter { +contract Quoter is IQuoter, ILockCallback { using PoolIdLibrary for PoolKey; using Hooks for IHooks; From 1912701c5ddcf8b0f028d5398bd72f61fe7d5829 Mon Sep 17 00:00:00 2001 From: Tina <59578595+tinaszheng@users.noreply.github.com> Date: Tue, 28 Nov 2023 11:41:00 -0500 Subject: [PATCH 48/80] add base contracts and interfaces (#75) --- contracts/base/Multicall.sol | 33 +++++++++++ contracts/base/PeripheryPayments.sol | 41 ++++++++++++++ contracts/base/PeripheryValidation.sol | 11 ++++ contracts/base/SelfPermit.sol | 52 +++++++++++++++++ contracts/interfaces/IMulticall.sol | 12 ++++ contracts/interfaces/IPeripheryPayments.sol | 17 ++++++ contracts/interfaces/ISelfPermit.sol | 56 +++++++++++++++++++ contracts/interfaces/external/IERC1271.sol | 16 ++++++ .../external/IERC20PermitAllowed.sol | 27 +++++++++ 9 files changed, 265 insertions(+) create mode 100644 contracts/base/Multicall.sol create mode 100644 contracts/base/PeripheryPayments.sol create mode 100644 contracts/base/PeripheryValidation.sol create mode 100644 contracts/base/SelfPermit.sol create mode 100644 contracts/interfaces/IMulticall.sol create mode 100644 contracts/interfaces/IPeripheryPayments.sol create mode 100644 contracts/interfaces/ISelfPermit.sol create mode 100644 contracts/interfaces/external/IERC1271.sol create mode 100644 contracts/interfaces/external/IERC20PermitAllowed.sol diff --git a/contracts/base/Multicall.sol b/contracts/base/Multicall.sol new file mode 100644 index 00000000..bd926766 --- /dev/null +++ b/contracts/base/Multicall.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +import {IMulticall} from "../interfaces/IMulticall.sol"; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // handle custom errors + if (result.length == 4) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol new file mode 100644 index 00000000..f272da34 --- /dev/null +++ b/contracts/base/PeripheryPayments.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; + +abstract contract PeripheryPayments is IPeripheryPayments { + using CurrencyLibrary for Currency; + using SafeTransferLib for address; + using SafeTransferLib for ERC20; + + error InsufficientToken(); + error NativeTokenTransferFrom(); + + /// @inheritdoc IPeripheryPayments + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) public payable override { + uint256 balanceCurrency = currency.balanceOfSelf(); + if (balanceCurrency < amountMinimum) revert InsufficientToken(); + + if (balanceCurrency > 0) { + currency.transfer(recipient, balanceCurrency); + } + } + + /// @param currency The currency to pay + /// @param payer The entity that must pay + /// @param recipient The entity that will receive payment + /// @param value The amount to pay + function pay(Currency currency, address payer, address recipient, uint256 value) internal { + if (payer == address(this)) { + // pay with tokens already in the contract (for the exact input multihop case) + currency.transfer(recipient, value); + } else { + if (currency.isNative()) revert NativeTokenTransferFrom(); + // pull payment + ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, recipient, value); + } + } +} diff --git a/contracts/base/PeripheryValidation.sol b/contracts/base/PeripheryValidation.sol new file mode 100644 index 00000000..b8ea81d4 --- /dev/null +++ b/contracts/base/PeripheryValidation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +abstract contract PeripheryValidation { + error TransactionTooOld(); + + modifier checkDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert TransactionTooOld(); + _; + } +} diff --git a/contracts/base/SelfPermit.sol b/contracts/base/SelfPermit.sol new file mode 100644 index 00000000..40449636 --- /dev/null +++ b/contracts/base/SelfPermit.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +import {IERC20PermitAllowed} from "../interfaces/external/IERC20PermitAllowed.sol"; +import {ISelfPermit} from "../interfaces/ISelfPermit.sol"; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function +/// that requires an approval in a single transaction. +abstract contract SelfPermit is ISelfPermit { + /// @inheritdoc ISelfPermit + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) { + selfPermitAllowed(token, nonce, expiry, v, r, s); + } + } +} diff --git a/contracts/interfaces/IMulticall.sol b/contracts/interfaces/IMulticall.sol new file mode 100644 index 00000000..dfa9db24 --- /dev/null +++ b/contracts/interfaces/IMulticall.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol new file mode 100644 index 00000000..765b980f --- /dev/null +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPayments { + // TODO: figure out if we still need unwrapWETH9 from v3? + + /// @notice Transfers the full amount of a token held by this contract to recipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + /// @param currency The contract address of the token which will be transferred to `recipient` + /// @param amountMinimum The minimum amount of token required for a transfer + /// @param recipient The destination address of the token + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) external payable; +} diff --git a/contracts/interfaces/ISelfPermit.sol b/contracts/interfaces/ISelfPermit.sol new file mode 100644 index 00000000..cb2445f5 --- /dev/null +++ b/contracts/interfaces/ISelfPermit.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; +} diff --git a/contracts/interfaces/external/IERC1271.sol b/contracts/interfaces/external/IERC1271.sol new file mode 100644 index 00000000..dcb30cb8 --- /dev/null +++ b/contracts/interfaces/external/IERC1271.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for verifying contract-based account signatures +/// @notice Interface that verifies provided signature for the data +/// @dev Interface defined by EIP-1271 +interface IERC1271 { + /// @notice Returns whether the provided signature is valid for the provided data + /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). + /// MUST allow external calls. + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + /// @return magicValue The bytes4 magic value 0x1626ba7e + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/contracts/interfaces/external/IERC20PermitAllowed.sol b/contracts/interfaces/external/IERC20PermitAllowed.sol new file mode 100644 index 00000000..7f2cf657 --- /dev/null +++ b/contracts/interfaces/external/IERC20PermitAllowed.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +} From 7d123ac7dfb0bb9bc9802dea81c5394dd433398b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 4 Dec 2023 16:27:22 -0500 Subject: [PATCH 49/80] remove unused errors --- contracts/interfaces/IQuoter.sol | 2 -- test/Quoter.t.sol | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index ea7a5f90..9e8af544 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -10,8 +10,6 @@ import {Currency} from "@uniswap/v4-core/contracts/types/Currency.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 InvalidQuoteType(); - error InvalidQuoteTypeInRevert(); error InvalidLockAcquiredSender(); error InvalidQuoteBatchParams(); error LockFailure(); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 20043d51..25272740 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -126,6 +126,12 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 2); } + // function testQuoter_callLockAcquired_reverts() public { + // vm.expectRevert() + // vm.prank(address(0)); + // quoter.callLockAcquired(); + // } + function testQuoter_quoteExactInputBatch() public { bool[] memory zeroForOnes = new bool[](2); zeroForOnes[0] = true; From 7bfcffb95d13c29ef55b992f36165ce61ca0a342 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 5 Dec 2023 13:22:39 -0500 Subject: [PATCH 50/80] test lockAcquired reverts --- contracts/lens/Quoter.sol | 10 ++++++++++ test/Quoter.t.sol | 11 ++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index d8f64eb3..d20aaae4 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; @@ -34,6 +35,9 @@ contract Quoter is IQuoter, ILockCallback { // v4 Singleton contract IPoolManager public immutable manager; + /// @dev custom error function selector length + uint256 internal constant MINIMUM_CUSTOM_ERROR_LENGTH = 4; + /// @dev function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes uint256 internal constant MINIMUM_REASON_LENGTH = 68; @@ -204,6 +208,12 @@ contract Quoter is IQuoter, ILockCallback { /// @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_REASON_LENGTH) { + //if InvalidLockAcquiredSender() + if (reason.length == MINIMUM_CUSTOM_ERROR_LENGTH) { + assembly { + revert(reason, 4) + } + } if (reason.length < MINIMUM_REASON_LENGTH) { revert UnexpectedRevertBytes(); } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 25272740..e8eab1b9 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -126,11 +126,12 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 2); } - // function testQuoter_callLockAcquired_reverts() public { - // vm.expectRevert() - // vm.prank(address(0)); - // quoter.callLockAcquired(); - // } + // nested self-call into lockAcquired reverts + function testQuoter_callLockAcquired_reverts() public { + vm.expectRevert(IQuoter.InvalidLockAcquiredSender.selector); + vm.prank(address(manager)); + quoter.lockAcquired(abi.encodeWithSelector(quoter.lockAcquired.selector, "0x")); + } function testQuoter_quoteExactInputBatch() public { bool[] memory zeroForOnes = new bool[](2); From 54373b1872aa709474a43970e7868ce05962e6aa Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 5 Dec 2023 13:28:32 -0500 Subject: [PATCH 51/80] remove ...Batch interface --- contracts/lens/Quoter.sol | 79 -------------------- contracts/libraries/SwapIntention.sol | 18 ----- test/Quoter.t.sol | 100 -------------------------- 3 files changed, 197 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index d20aaae4..bdb00b93 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -15,10 +15,8 @@ import { SwapType, SwapInfo, ExactInputSingleParams, - ExactInputSingleBatchParams, ExactInputParams, ExactOutputSingleParams, - ExactOutputSingleBatchParams, ExactOutputParams } from "../libraries/SwapIntention.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; @@ -82,44 +80,6 @@ contract Quoter is IQuoter, ILockCallback { } } - function quoteExactInputBatch(ExactInputSingleBatchParams memory params) - external - returns ( - IQuoter.PoolDeltas[] memory deltas, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { - if ( - params.zeroForOnes.length != params.recipients.length || params.recipients.length != params.amountIns.length - || params.amountIns.length != params.sqrtPriceLimitX96s.length - || params.sqrtPriceLimitX96s.length != params.hookData.length - ) { - revert InvalidQuoteBatchParams(); - } - - deltas = new IQuoter.PoolDeltas[](params.amountIns.length); - sqrtPriceX96AfterList = new uint160[](params.amountIns.length); - initializedTicksLoadedList = new uint32[](params.amountIns.length); - - for (uint256 i = 0; i < params.amountIns.length; i++) { - ExactInputSingleParams memory singleParams = ExactInputSingleParams({ - poolKey: params.poolKey, - zeroForOne: params.zeroForOnes[i], - recipient: params.recipients[i], - amountIn: params.amountIns[i], - sqrtPriceLimitX96: params.sqrtPriceLimitX96s[i], - hookData: params.hookData[i] - }); - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = - quoteExactInputSingle(singleParams); - - deltas[i] = IQuoter.PoolDeltas({currency0Delta: deltaAmounts[0], currency1Delta: deltaAmounts[1]}); - sqrtPriceX96AfterList[i] = sqrtPriceX96After; - initializedTicksLoadedList[i] = initializedTicksLoaded; - } - } - /// @inheritdoc IQuoter function quoteExactOutputSingle(ExactOutputSingleParams memory params) public @@ -151,45 +111,6 @@ contract Quoter is IQuoter, ILockCallback { } } - function quoteExactOutputBatch(ExactOutputSingleBatchParams memory params) - external - returns ( - IQuoter.PoolDeltas[] memory deltas, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) - { - if ( - params.zeroForOnes.length != params.recipients.length - || params.recipients.length != params.amountOuts.length - || params.amountOuts.length != params.sqrtPriceLimitX96s.length - || params.sqrtPriceLimitX96s.length != params.hookData.length - ) { - revert InvalidQuoteBatchParams(); - } - - deltas = new IQuoter.PoolDeltas[](params.amountOuts.length); - sqrtPriceX96AfterList = new uint160[](params.amountOuts.length); - initializedTicksLoadedList = new uint32[](params.amountOuts.length); - - for (uint256 i = 0; i < params.amountOuts.length; i++) { - ExactOutputSingleParams memory singleParams = ExactOutputSingleParams({ - poolKey: params.poolKey, - zeroForOne: params.zeroForOnes[i], - recipient: params.recipients[i], - amountOut: params.amountOuts[i], - sqrtPriceLimitX96: params.sqrtPriceLimitX96s[i], - hookData: params.hookData[i] - }); - (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = - quoteExactOutputSingle(singleParams); - - deltas[i] = IQuoter.PoolDeltas({currency0Delta: deltaAmounts[0], currency1Delta: deltaAmounts[1]}); - sqrtPriceX96AfterList[i] = sqrtPriceX96After; - initializedTicksLoadedList[i] = initializedTicksLoaded; - } - } - function lockAcquired(bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapIntention.sol index e6404812..9ad33b41 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapIntention.sol @@ -28,15 +28,6 @@ struct ExactInputSingleParams { bytes hookData; } -struct ExactInputSingleBatchParams { - PoolKey poolKey; - bool[] zeroForOnes; - address[] recipients; - uint128[] amountIns; - uint160[] sqrtPriceLimitX96s; - bytes[] hookData; -} - struct ExactInputParams { Currency currencyIn; PathKey[] path; @@ -53,15 +44,6 @@ struct ExactOutputSingleParams { bytes hookData; } -struct ExactOutputSingleBatchParams { - PoolKey poolKey; - bool[] zeroForOnes; - address[] recipients; - uint128[] amountOuts; - uint160[] sqrtPriceLimitX96s; - bytes[] hookData; -} - struct ExactOutputParams { Currency currencyOut; PathKey[] path; diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index e8eab1b9..ca813803 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -7,10 +7,8 @@ import { SwapType, SwapInfo, ExactInputSingleParams, - ExactInputSingleBatchParams, ExactInputParams, ExactOutputSingleParams, - ExactOutputSingleBatchParams, ExactOutputParams } from "../contracts/libraries/SwapIntention.sol"; import {PathKey} from "../contracts/libraries/PathKey.sol"; @@ -133,55 +131,6 @@ contract QuoterTest is Test, Deployers { quoter.lockAcquired(abi.encodeWithSelector(quoter.lockAcquired.selector, "0x")); } - function testQuoter_quoteExactInputBatch() public { - bool[] memory zeroForOnes = new bool[](2); - zeroForOnes[0] = true; - zeroForOnes[1] = false; - - address[] memory recipients = new address[](2); - recipients[0] = address(this); - recipients[1] = address(this); - - // repeat for the three arrays below - uint128[] memory amountIns = new uint128[](2); - amountIns[0] = 10000; - amountIns[1] = 10000; - - uint160[] memory sqrtPriceLimitX96s = new uint160[](2); - sqrtPriceLimitX96s[0] = 0; - sqrtPriceLimitX96s[1] = 0; - - bytes[] memory hookData = new bytes[](2); - hookData[0] = ZERO_BYTES; - hookData[1] = ZERO_BYTES; - - ( - IQuoter.PoolDeltas[] memory deltas, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactInputBatch( - ExactInputSingleBatchParams({ - poolKey: key02, - zeroForOnes: zeroForOnes, - recipients: recipients, - amountIns: amountIns, - sqrtPriceLimitX96s: sqrtPriceLimitX96s, - hookData: hookData - }) - ); - assertEq(deltas.length, 2); - assertEq(uint128(-deltas[0].currency1Delta), 9871); - assertEq(uint128(-deltas[1].currency0Delta), 9871); - - assertEq(sqrtPriceX96AfterList.length, 2); - assertEq(sqrtPriceX96AfterList[0], 78461846509168490764501028180); - assertEq(sqrtPriceX96AfterList[1], 80001962924147897865541384515); - - assertEq(initializedTicksLoadedList.length, 2); - assertEq(initializedTicksLoadedList[0], 2); - assertEq(initializedTicksLoadedList[1], 2); - } - function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); @@ -409,55 +358,6 @@ contract QuoterTest is Test, Deployers { assertEq(initializedTicksLoaded, 0); } - function testQuoter_quoteExactOutputBatch() public { - bool[] memory zeroForOnes = new bool[](2); - zeroForOnes[0] = true; - zeroForOnes[1] = false; - - address[] memory recipients = new address[](2); - recipients[0] = address(this); - recipients[1] = address(this); - - // repeat for the three arrays below - uint128[] memory amountOuts = new uint128[](2); - amountOuts[0] = type(uint128).max; - amountOuts[1] = type(uint128).max; - - uint160[] memory sqrtPriceLimitX96s = new uint160[](2); - sqrtPriceLimitX96s[0] = SQRT_RATIO_100_102; - sqrtPriceLimitX96s[1] = SQRT_RATIO_102_100; - - bytes[] memory hookData = new bytes[](2); - hookData[0] = ZERO_BYTES; - hookData[1] = ZERO_BYTES; - - ( - IQuoter.PoolDeltas[] memory deltas, - uint160[] memory sqrtPriceX96AfterList, - uint32[] memory initializedTicksLoadedList - ) = quoter.quoteExactOutputBatch( - ExactOutputSingleBatchParams({ - poolKey: key01, - zeroForOnes: zeroForOnes, - recipients: recipients, - amountOuts: amountOuts, - sqrtPriceLimitX96s: sqrtPriceLimitX96s, - hookData: hookData - }) - ); - assertEq(deltas.length, 2); - assertEq(uint128(deltas[0].currency0Delta), 9981); - assertEq(uint128(deltas[1].currency1Delta), 9981); - - assertEq(sqrtPriceX96AfterList.length, 2); - assertEq(sqrtPriceX96AfterList[0], SQRT_RATIO_100_102); - assertEq(sqrtPriceX96AfterList[1], SQRT_RATIO_102_100); - - assertEq(initializedTicksLoadedList.length, 2); - assertEq(initializedTicksLoadedList[0], 0); - assertEq(initializedTicksLoadedList[1], 0); - } - function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); From 6ecf72bb57391db8ace3739519ae4989b19a78b5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 5 Dec 2023 13:36:32 -0500 Subject: [PATCH 52/80] REASON -> RESPONSE when valid --- contracts/lens/Quoter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index bdb00b93..86bde7c9 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -41,7 +41,7 @@ contract Quoter is IQuoter, ILockCallback { /// @dev min valid reason is 3-words long /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes - uint256 internal constant MINIMUM_VALID_REASON_LENGTH = 96; + uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96; /// @dev Only this address may call this function modifier selfOnly() { @@ -128,7 +128,7 @@ contract Quoter is IQuoter, ILockCallback { /// @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_REASON_LENGTH) { + if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { //if InvalidLockAcquiredSender() if (reason.length == MINIMUM_CUSTOM_ERROR_LENGTH) { assembly { From a40476eca98bd1891564770d69299ac267bb496c Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 5 Dec 2023 13:43:46 -0500 Subject: [PATCH 53/80] complete natspec --- contracts/lens/Quoter.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 86bde7c9..eecd2821 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -111,6 +111,7 @@ contract Quoter is IQuoter, ILockCallback { } } + /// @inheritdoc ILockCallback function lockAcquired(bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); @@ -180,6 +181,7 @@ contract Quoter is IQuoter, ILockCallback { abi.decode(reason, (int128[], uint160[], uint32[])); } + /// @dev quote an ExactInput swap along a path of tokens, then revert with the result function _quoteExactInput(ExactInputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; @@ -219,6 +221,7 @@ contract Quoter is IQuoter, ILockCallback { } } + /// @dev quote an ExactInput swap on a pool, then revert with the result function _quoteExactInputSingle(ExactInputSingleParams memory params) public selfOnly returns (bytes memory) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, @@ -233,6 +236,7 @@ contract Quoter is IQuoter, ILockCallback { } } + /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result function _quoteExactOutput(ExactOutputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; @@ -274,6 +278,7 @@ contract Quoter is IQuoter, ILockCallback { } } + /// @dev quote an ExactOutput swap on a pool, then revert with the result function _quoteExactOutputSingle(ExactOutputSingleParams memory params) public selfOnly returns (bytes memory) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, From 7e7b880f8a36a546d9e65c1214792758993942cc Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 5 Dec 2023 15:23:08 -0500 Subject: [PATCH 54/80] remove SwapInfo imports --- contracts/lens/Quoter.sol | 1 - test/Quoter.t.sol | 1 - 2 files changed, 2 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index eecd2821..b69b2bda 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -13,7 +13,6 @@ import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; import { SwapType, - SwapInfo, ExactInputSingleParams, ExactInputParams, ExactOutputSingleParams, diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index ca813803..7eeba854 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import { SwapType, - SwapInfo, ExactInputSingleParams, ExactInputParams, ExactOutputSingleParams, From cb15b3e3e5bb75b7c64da999736c11319ce7d202 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 6 Dec 2023 11:21:46 -0500 Subject: [PATCH 55/80] rename to SwapParameters --- contracts/interfaces/IQuoter.sol | 7 ++++++- contracts/lens/Quoter.sol | 2 +- .../libraries/{SwapIntention.sol => SwapParameters.sol} | 2 -- test/Quoter.t.sol | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) rename contracts/libraries/{SwapIntention.sol => SwapParameters.sol} (97%) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 9e8af544..9147f088 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "../libraries/SwapIntention.sol"; +import { + ExactInputSingleParams, + ExactInputParams, + ExactOutputSingleParams, + ExactOutputParams +} from "../libraries/SwapParameters.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; /// @title Quoter Interface diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index b69b2bda..247f2406 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -17,7 +17,7 @@ import { ExactInputParams, ExactOutputSingleParams, ExactOutputParams -} from "../libraries/SwapIntention.sol"; +} from "../libraries/SwapParameters.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKeyLib} from "../libraries/PathKey.sol"; diff --git a/contracts/libraries/SwapIntention.sol b/contracts/libraries/SwapParameters.sol similarity index 97% rename from contracts/libraries/SwapIntention.sol rename to contracts/libraries/SwapParameters.sol index 9ad33b41..088b4eb4 100644 --- a/contracts/libraries/SwapIntention.sol +++ b/contracts/libraries/SwapParameters.sol @@ -51,5 +51,3 @@ struct ExactOutputParams { uint128 amountOut; uint160 sqrtPriceLimitX96; } - -library SwapIntention {} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 7eeba854..4971c9c6 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -9,7 +9,7 @@ import { ExactInputParams, ExactOutputSingleParams, ExactOutputParams -} from "../contracts/libraries/SwapIntention.sol"; +} from "../contracts/libraries/SwapParameters.sol"; import {PathKey} from "../contracts/libraries/PathKey.sol"; import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; From 710705b298e42388db0c2fb384b2e7f3ebba457b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 6 Dec 2023 12:04:13 -0500 Subject: [PATCH 56/80] move quoter structs into IQuoter interface --- contracts/interfaces/IQuoter.sol | 57 ++++++++++++++++++------ contracts/lens/Quoter.sol | 27 +++++------- contracts/libraries/SwapParameters.sol | 53 ---------------------- test/Quoter.t.sol | 61 ++++++++++++-------------- 4 files changed, 82 insertions(+), 116 deletions(-) delete mode 100644 contracts/libraries/SwapParameters.sol diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 9147f088..89d3e27d 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -1,13 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import { - ExactInputSingleParams, - ExactInputParams, - ExactOutputSingleParams, - ExactOutputParams -} from "../libraries/SwapParameters.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {PathKey} from "../libraries/PathKey.sol"; /// @title Quoter Interface /// @notice Supports quoting the delta amounts from exact input or exact output swaps. @@ -26,8 +22,41 @@ interface IQuoter { int128 currency1Delta; } + struct QuoteExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + + struct QuoteExactInputParams { + Currency currencyIn; + PathKey[] path; + address recipient; + uint128 amountIn; + } + + struct QuoteExactOutputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountOut; + uint160 sqrtPriceLimitX96; + bytes hookData; + } + + struct QuoteExactOutputParams { + Currency currencyOut; + PathKey[] path; + address recipient; + uint128 amountOut; + uint160 sqrtPriceLimitX96; + } + /// @notice Returns the delta amounts for a given exact input swap of a single pool - /// @param params The params for the quote, encoded as `ExactInputSingleParams` + /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 /// recipient The indented recipient of the output tokens @@ -37,12 +66,12 @@ interface IQuoter { /// @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(ExactInputSingleParams calldata params) + function quoteExactInputSingle(QuoteExactInputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); /// @notice Returns the delta amounts along the swap path for a given exact input swap - /// @param params the params for the quote, encoded as 'ExactInputParams' + /// @param params the params for the quote, encoded as 'QuoteExactInputParams' /// currencyIn The input currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// recipient The indented recipient of the output tokens @@ -50,7 +79,7 @@ interface IQuoter { /// @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(ExactInputParams memory params) + function quoteExactInput(QuoteExactInputParams memory params) external returns ( int128[] memory deltaAmounts, @@ -59,7 +88,7 @@ interface IQuoter { ); /// @notice Returns the delta amounts for a given exact output swap of a single pool - /// @param params The params for the quote, encoded as `ExactOutputSingleParams` + /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 /// recipient The indented recipient of the output tokens @@ -69,12 +98,12 @@ interface IQuoter { /// @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(ExactOutputSingleParams calldata params) + function quoteExactOutputSingle(QuoteExactOutputSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); /// @notice Returns the delta amounts along the swap path for a given exact output swap - /// @param params the params for the quote, encoded as 'ExactInputParams' + /// @param params the params for the quote, encoded as 'QuoteExactInputParams' /// currencyOut The output currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// recipient The indented recipient of the output tokens @@ -82,7 +111,7 @@ interface IQuoter { /// @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(ExactOutputParams memory params) + function quoteExactOutput(QuoteExactOutputParams memory params) external returns ( int128[] memory deltaAmounts, diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 247f2406..4d4f3226 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -11,13 +11,6 @@ import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import { - SwapType, - ExactInputSingleParams, - ExactInputParams, - ExactOutputSingleParams, - ExactOutputParams -} from "../libraries/SwapParameters.sol"; import {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKeyLib} from "../libraries/PathKey.sol"; @@ -53,7 +46,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactInputSingle(ExactInputSingleParams memory params) + function quoteExactInputSingle(QuoteExactInputSingleParams memory params) public override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) @@ -65,7 +58,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactInput(ExactInputParams memory params) + function quoteExactInput(QuoteExactInputParams memory params) external returns ( int128[] memory deltaAmounts, @@ -80,7 +73,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactOutputSingle(ExactOutputSingleParams memory params) + function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) public override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) @@ -95,7 +88,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactOutput(ExactOutputParams memory params) + function quoteExactOutput(QuoteExactOutputParams memory params) public override returns ( @@ -181,7 +174,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result - function _quoteExactInput(ExactInputParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInput(QuoteExactInputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; int128[] memory deltaAmounts = new int128[](pathLength + 1); @@ -221,7 +214,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactInput swap on a pool, then revert with the result - function _quoteExactInputSingle(ExactInputSingleParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInputSingle(QuoteExactInputSingleParams memory params) public selfOnly returns (bytes memory) { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, @@ -236,7 +229,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result - function _quoteExactOutput(ExactOutputParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactOutput(QuoteExactOutputParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; int128[] memory deltaAmounts = new int128[](pathLength + 1); @@ -278,7 +271,11 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactOutput swap on a pool, then revert with the result - function _quoteExactOutputSingle(ExactOutputSingleParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + public + selfOnly + returns (bytes memory) + { (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, diff --git a/contracts/libraries/SwapParameters.sol b/contracts/libraries/SwapParameters.sol deleted file mode 100644 index 088b4eb4..00000000 --- a/contracts/libraries/SwapParameters.sol +++ /dev/null @@ -1,53 +0,0 @@ -//SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.20; - -import {PathKey, PathKeyLib} from "./PathKey.sol"; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; - -enum SwapType { - ExactInput, - ExactInputSingle, - ExactOutput, - ExactOutputSingle -} - -struct SwapInfo { - SwapType swapType; - bytes params; -} - -struct ExactInputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountIn; - uint160 sqrtPriceLimitX96; - bytes hookData; -} - -struct ExactInputParams { - Currency currencyIn; - PathKey[] path; - address recipient; - uint128 amountIn; -} - -struct ExactOutputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountOut; - uint160 sqrtPriceLimitX96; - bytes hookData; -} - -struct ExactOutputParams { - Currency currencyOut; - PathKey[] path; - address recipient; - uint128 amountOut; - uint160 sqrtPriceLimitX96; -} diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 4971c9c6..cd1cacb7 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -3,13 +3,6 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; -import { - SwapType, - ExactInputSingleParams, - ExactInputParams, - ExactOutputSingleParams, - ExactOutputParams -} from "../contracts/libraries/SwapParameters.sol"; import {PathKey} from "../contracts/libraries/PathKey.sol"; import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; @@ -86,7 +79,7 @@ contract QuoterTest is Test, Deployers { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( - ExactInputSingleParams({ + IQuoter.QuoteExactInputSingleParams({ poolKey: key02, zeroForOne: true, recipient: address(this), @@ -108,7 +101,7 @@ contract QuoterTest is Test, Deployers { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( - ExactInputSingleParams({ + IQuoter.QuoteExactInputSingleParams({ poolKey: key02, zeroForOne: false, recipient: address(this), @@ -133,7 +126,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); - ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -152,7 +145,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is -120. // -120 is an initialized tick for this pool. We check that we don't count it. - ExactInputParams memory params = getExactInputParams(tokenPath, 6200); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 6200); ( int128[] memory deltaAmounts, @@ -171,7 +164,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is -60. // -60 is an initialized tick for this pool. We check that we don't count it. - ExactInputParams memory params = getExactInputParams(tokenPath, 4000); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 4000); ( int128[] memory deltaAmounts, @@ -187,7 +180,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { tokenPath.push(token0); tokenPath.push(token2); - ExactInputParams memory params = getExactInputParams(tokenPath, 10); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -204,7 +197,7 @@ contract QuoterTest is Test, Deployers { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token0); tokenPath.push(token2); - ExactInputParams memory params = getExactInputParams(tokenPath, 10); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -220,7 +213,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { tokenPath.push(token2); tokenPath.push(token0); - ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -239,7 +232,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is 120. // 120 is an initialized tick for this pool. We check that we don't count it. - ExactInputParams memory params = getExactInputParams(tokenPath, 6250); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 6250); ( int128[] memory deltaAmounts, @@ -256,7 +249,7 @@ contract QuoterTest is Test, Deployers { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token2); tokenPath.push(token0); - ExactInputParams memory params = getExactInputParams(tokenPath, 200); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 ( @@ -274,7 +267,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { tokenPath.push(token2); tokenPath.push(token0); - ExactInputParams memory params = getExactInputParams(tokenPath, 103); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 103); ( int128[] memory deltaAmounts, @@ -290,7 +283,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to1() public { tokenPath.push(token2); tokenPath.push(token1); - ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -306,7 +299,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); tokenPath.push(token1); - ExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -324,7 +317,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutputSingle_0to1() public { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( - ExactOutputSingleParams({ + IQuoter.QuoteExactOutputSingleParams({ poolKey: key01, zeroForOne: true, recipient: address(this), @@ -342,7 +335,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutputSingle_1to0() public { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( - ExactOutputSingleParams({ + IQuoter.QuoteExactOutputSingleParams({ poolKey: key01, zeroForOne: false, recipient: address(this), @@ -360,7 +353,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); ( int128[] memory deltaAmounts, @@ -377,7 +370,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 6143); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6143); ( int128[] memory deltaAmounts, @@ -394,7 +387,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 4000); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 4000); ( int128[] memory deltaAmounts, @@ -412,7 +405,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 100); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 ( @@ -430,7 +423,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 10); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -446,7 +439,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { tokenPath.push(token2); tokenPath.push(token0); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); ( int128[] memory deltaAmounts, @@ -464,7 +457,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token0); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 6223); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6223); ( int128[] memory deltaAmounts, @@ -482,7 +475,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token0); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 6000); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6000); ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, @@ -499,7 +492,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token1); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 9871); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 9871); ( int128[] memory deltaAmounts, @@ -518,7 +511,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token1); - ExactOutputParams memory params = getExactOutputParams(tokenPath, 9745); + IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 9745); ( int128[] memory deltaAmounts, @@ -639,7 +632,7 @@ contract QuoterTest is Test, Deployers { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal view - returns (ExactInputParams memory params) + returns (IQuoter.QuoteExactInputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = 0; i < _tokenPath.length - 1; i++) { @@ -655,7 +648,7 @@ contract QuoterTest is Test, Deployers { function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal view - returns (ExactOutputParams memory params) + returns (IQuoter.QuoteExactOutputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = _tokenPath.length - 1; i > 0; i--) { From 311ff6f870f0e6306cdcf9de58612ebba29ae71f Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 11 Dec 2023 13:58:50 -0500 Subject: [PATCH 57/80] update to latest core --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- .gitignore | 3 +- contracts/BaseHook.sol | 11 ++++-- contracts/hooks/examples/FullRange.sol | 20 ++++++----- contracts/hooks/examples/GeomeanOracle.sol | 12 ++++--- contracts/hooks/examples/LimitOrder.sol | 14 +++++--- contracts/hooks/examples/TWAMM.sol | 21 ++++++++---- contracts/hooks/examples/VolatilityOracle.sol | 8 +++-- lib/v4-core | 2 +- test/FullRange.t.sol | 34 +++++++++---------- test/GeomeanOracle.t.sol | 18 +++++----- test/LimitOrder.t.sol | 4 +-- test/TWAMM.t.sol | 2 +- .../FullRangeImplementation.sol | 2 +- .../GeomeanOracleImplementation.sol | 2 +- .../LimitOrderImplementation.sol | 2 +- .../implementation/TWAMMImplementation.sol | 2 +- test/utils/HookEnabledSwapRouter.sol | 5 +-- 47 files changed, 126 insertions(+), 96 deletions(-) diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 638f8744..7706f4dd 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -21068 \ No newline at end of file +23377 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 1bc3059d..8afa5484 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -21318 \ No newline at end of file +23624 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index a5bb2393..f66ebbd5 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2076 \ No newline at end of file +2798 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index db768f3a..9db3df4e 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -20164 \ No newline at end of file +22396 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index c04b75bb..b2f26cf1 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -20458 \ No newline at end of file +22695 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 7e3a2f44..7d8545fe 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -390028 \ No newline at end of file +410731 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index fcc9c101..e7c101bd 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -184294 \ No newline at end of file +204653 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index 08d86dfe..2b5ad7d2 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -133339 \ No newline at end of file +156432 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index b07717ab..c2b5d0ef 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -896690 \ No newline at end of file +897565 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 594d74bc..f8ded9d9 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -177393 \ No newline at end of file +200027 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index e2e0f2f7..504a326a 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -362063 \ No newline at end of file +386065 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index e21f3119..9e12e78d 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -93685 \ No newline at end of file +114700 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index 77bbcb9a..d9365d02 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -131614 \ No newline at end of file +154641 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 61763356..3aa3cfac 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -233028 \ No newline at end of file +254711 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 4f1264df..50fc054a 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -223717 \ No newline at end of file +245393 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 3d85d6d7..15a052b9 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -32886 \ No newline at end of file +54893 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index bc6dc069..d6664238 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -23586 \ No newline at end of file +45575 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index da81ec04..3039612c 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -51411 \ No newline at end of file +72361 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index f61a3565..ae13ac3f 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -5571 \ No newline at end of file +6618 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 41599c5d..b63da1de 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -75965 \ No newline at end of file +88543 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 7f966954..f91847e9 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -1525 \ No newline at end of file +2130 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index 9463411b..bc61a749 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2000 \ No newline at end of file +2771 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index 0b1caa8d..ba7fb703 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -5746 \ No newline at end of file +6807 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index bee097af..3ee11622 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -5277 \ No newline at end of file +6319 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a51f76e9..204ec243 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -2615 \ No newline at end of file +3466 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index d01ad33f..21c2b598 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -123619 \ No newline at end of file +146128 \ No newline at end of file diff --git a/.gitignore b/.gitignore index de5c2c73..785fb393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ cache/ -foundry-out/ \ No newline at end of file +foundry-out/ +.vscode/ \ No newline at end of file diff --git a/contracts/BaseHook.sol b/contracts/BaseHook.sol index a16ab91d..941de34c 100644 --- a/contracts/BaseHook.sol +++ b/contracts/BaseHook.sol @@ -40,16 +40,21 @@ abstract contract BaseHook is IHooks { _; } - function getHooksCalls() public pure virtual returns (Hooks.Calls memory); + function getHooksCalls() public pure virtual returns (Hooks.Permissions memory); // this function is virtual so that we can override it during testing, // which allows us to deploy an implementation to any address // and then etch the bytecode into the correct address function validateHookAddress(BaseHook _this) internal pure virtual { - Hooks.validateHookAddress(_this, getHooksCalls()); + Hooks.validateHookPermissions(_this, getHooksCalls()); } - function lockAcquired(bytes calldata data) external virtual poolManagerOnly returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata data) + external + virtual + poolManagerOnly + returns (bytes memory) + { (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; if (returnData.length == 0) revert LockFailure(); diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index eaa7539b..ecd4a26b 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -87,8 +87,8 @@ contract FullRange is BaseHook, ILockCallback { _; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -96,7 +96,9 @@ contract FullRange is BaseHook, ILockCallback { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -115,7 +117,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -172,7 +174,7 @@ contract FullRange is BaseHook, ILockCallback { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); if (sqrtPriceX96 == 0) revert PoolNotInitialized(); @@ -247,7 +249,9 @@ contract FullRange is BaseHook, ILockCallback { internal returns (BalanceDelta delta) { - delta = abi.decode(poolManager.lock(abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta)); + delta = abi.decode( + poolManager.lock(address(this), abi.encode(CallbackData(msg.sender, key, params))), (BalanceDelta) + ); } function _settleDeltas(address sender, PoolKey memory key, BalanceDelta delta) internal { @@ -295,7 +299,7 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = false; } - function lockAcquired(bytes calldata rawData) + function lockAcquired(address, /*sender*/ bytes calldata rawData) external override(ILockCallback, BaseHook) poolManagerOnly @@ -332,7 +336,7 @@ contract FullRange is BaseHook, ILockCallback { ) * FixedPointMathLib.sqrt(FixedPoint96.Q96) ).toUint160(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); poolManager.swap( key, diff --git a/contracts/hooks/examples/GeomeanOracle.sol b/contracts/hooks/examples/GeomeanOracle.sol index 8ae1c640..35389d0f 100644 --- a/contracts/hooks/examples/GeomeanOracle.sol +++ b/contracts/hooks/examples/GeomeanOracle.sol @@ -60,8 +60,8 @@ contract GeomeanOracle is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: true, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract GeomeanOracle is BaseHook { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -101,7 +103,7 @@ contract GeomeanOracle is BaseHook { /// @dev Called before any action that potentially modifies pool price or liquidity, such as swap or modify position function _updatePool(PoolKey calldata key) private { PoolId id = key.toId(); - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); @@ -146,7 +148,7 @@ contract GeomeanOracle is BaseHook { ObservationState memory state = states[id]; - (, int24 tick,,) = poolManager.getSlot0(id); + (, int24 tick,) = poolManager.getSlot0(id); uint128 liquidity = poolManager.getLiquidity(id); diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index d2654f49..2a5287bf 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -73,8 +73,8 @@ contract LimitOrder is BaseHook { constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: false, afterInitialize: true, beforeModifyPosition: false, @@ -82,7 +82,9 @@ contract LimitOrder is BaseHook { beforeSwap: false, afterSwap: true, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -107,7 +109,7 @@ contract LimitOrder is BaseHook { } function getTick(PoolId poolId) private view returns (int24 tick) { - (, tick,,) = poolManager.getSlot0(poolId); + (, tick,) = poolManager.getSlot0(poolId); } function getTickLower(int24 tick, int24 tickSpacing) private pure returns (int24) { @@ -156,6 +158,7 @@ contract LimitOrder is BaseHook { (uint256 amount0, uint256 amount1) = abi.decode( poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredFill, (key, lower, -int256(uint256(epochInfo.liquidityTotal)))) ), (uint256, uint256) @@ -219,6 +222,7 @@ contract LimitOrder is BaseHook { if (liquidity == 0) revert ZeroLiquidity(); poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredPlace, (key, tickLower, zeroForOne, int256(uint256(liquidity)), msg.sender)) ); @@ -302,6 +306,7 @@ contract LimitOrder is BaseHook { uint256 amount1Fee; (amount0, amount1, amount0Fee, amount1Fee) = abi.decode( poolManager.lock( + address(this), abi.encodeCall( this.lockAcquiredKill, (key, tickLower, -int256(uint256(liquidity)), to, liquidity == liquidityTotal) @@ -385,6 +390,7 @@ contract LimitOrder is BaseHook { epochInfo.liquidityTotal = liquidityTotal - liquidity; poolManager.lock( + address(this), abi.encodeCall(this.lockAcquiredWithdraw, (epochInfo.currency0, epochInfo.currency1, amount0, amount1, to)) ); diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index a6635e94..bb0592ad 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -60,8 +60,8 @@ contract TWAMM is BaseHook, ITWAMM { expirationInterval = _expirationInterval; } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: true, @@ -69,7 +69,9 @@ contract TWAMM is BaseHook, ITWAMM { beforeSwap: true, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } @@ -138,7 +140,7 @@ contract TWAMM is BaseHook, ITWAMM { /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); - (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = poolManager.getSlot0(poolId); State storage twamm = twammStates[poolId]; (bool zeroForOne, uint160 sqrtPriceLimitX96) = _executeTWAMMOrders( @@ -146,7 +148,9 @@ contract TWAMM is BaseHook, ITWAMM { ); if (sqrtPriceLimitX96 != 0 && sqrtPriceLimitX96 != sqrtPriceX96) { - poolManager.lock(abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96))); + poolManager.lock( + address(this), abi.encode(key, IPoolManager.SwapParams(zeroForOne, type(int256).max, sqrtPriceLimitX96)) + ); } } @@ -302,7 +306,12 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(bytes calldata rawData) external override poolManagerOnly returns (bytes memory) { + function lockAcquired(address sender, bytes calldata rawData) + external + override + poolManagerOnly + returns (bytes memory) + { (PoolKey memory key, IPoolManager.SwapParams memory swapParams) = abi.decode(rawData, (PoolKey, IPoolManager.SwapParams)); diff --git a/contracts/hooks/examples/VolatilityOracle.sol b/contracts/hooks/examples/VolatilityOracle.sol index 65485e11..657c9fae 100644 --- a/contracts/hooks/examples/VolatilityOracle.sol +++ b/contracts/hooks/examples/VolatilityOracle.sol @@ -30,8 +30,8 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { deployTimestamp = _blockTimestamp(); } - function getHooksCalls() public pure override returns (Hooks.Calls memory) { - return Hooks.Calls({ + function getHooksCalls() public pure override returns (Hooks.Permissions memory) { + return Hooks.Permissions({ beforeInitialize: true, afterInitialize: false, beforeModifyPosition: false, @@ -39,7 +39,9 @@ contract VolatilityOracle is BaseHook, IDynamicFeeManager { beforeSwap: false, afterSwap: false, beforeDonate: false, - afterDonate: false + afterDonate: false, + noOp: false, + accessLock: false }); } diff --git a/lib/v4-core b/lib/v4-core index 7998e6c3..cda1f483 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 7998e6c391b77d2a8455f902097b0180b579db1b +Subproject commit cda1f483d41b0db7ee28a7de3f3bbe47c7f33cfc diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index e6c75f97..3d3f6800 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -127,7 +127,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { emit Initialize(id, testKey.currency0, testKey.currency1, testKey.fee, testKey.tickSpacing, testKey.hooks); snapStart("FullRangeInitialize"); - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); snapEnd(); (, address liquidityToken) = fullRange.poolInfo(id); @@ -139,11 +139,11 @@ contract TestFullRange is Test, Deployers, GasSnapshot { PoolKey memory wrongKey = PoolKey(key.currency0, key.currency1, 0, TICK_SPACING + 1, fullRange); vm.expectRevert(FullRange.TickSpacingNotDefault.selector); - manager.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(wrongKey, SQRT_RATIO_1_1, ZERO_BYTES); } function testFullRange_addLiquidity_InitialAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -169,7 +169,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_InitialAddFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); if (amount < LOCKED_LIQUIDITY) { vm.expectRevert(FullRange.LiquidityDoesntMeetMinimum.selector); fullRange.addLiquidity( @@ -244,7 +244,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_SwapThenAddSucceeds() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -298,7 +298,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_addLiquidity_FailsIfTooMuchSlippage() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -323,7 +323,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { function testFullRange_swap_TwoSwaps() public { PoolKey memory testKey = key; - manager.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(testKey, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -352,8 +352,8 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_swap_TwoPools() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - manager.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key2, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -408,7 +408,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_InitialRemoveFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); fullRange.addLiquidity( FullRange.AddLiquidityParams( @@ -456,7 +456,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_FailsIfNoLiquidity() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); UniswapV4ERC20(liquidityToken).approve(address(fullRange), type(uint256).max); @@ -468,7 +468,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SucceedsWithPartial() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOfSelf(); uint256 prevBalance1 = key.currency1.balanceOfSelf(); @@ -503,7 +503,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_DiffRatios() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); uint256 prevBalance0 = key.currency0.balanceOf(address(this)); uint256 prevBalance1 = key.currency1.balanceOf(address(this)); @@ -571,7 +571,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_RemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -626,7 +626,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { vm.prank(address(2)); token1.approve(address(fullRange), type(uint256).max); - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); // Test contract adds liquidity @@ -704,7 +704,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_removeLiquidity_SwapRemoveAllFuzz(uint256 amount) public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (, address liquidityToken) = fullRange.poolInfo(id); if (amount <= LOCKED_LIQUIDITY) { @@ -753,7 +753,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { } function testFullRange_BeforeModifyPositionFailsWithWrongMsgSender() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); vm.expectRevert(FullRange.SenderMustBeHook.selector); modifyPositionRouter.modifyPosition( diff --git a/test/GeomeanOracle.t.sol b/test/GeomeanOracle.t.sol index ce34adbd..aa5e5c6d 100644 --- a/test/GeomeanOracle.t.sol +++ b/test/GeomeanOracle.t.sol @@ -66,12 +66,12 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeInitializeAllowsPoolCreation() public { - manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } function testBeforeInitializeRevertsIfFee() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 1, MAX_TICK_SPACING, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -80,7 +80,7 @@ contract TestGeomeanOracle is Test, Deployers { function testBeforeInitializeRevertsIfNotMaxTickSpacing() public { vm.expectRevert(GeomeanOracle.OnlyOneOraclePoolAllowed.selector); - manager.initialize( + initializeRouter.initialize( PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 0, 60, geomeanOracle), SQRT_RATIO_1_1, ZERO_BYTES @@ -88,7 +88,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeState() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); assertEq(observationState.index, 0); assertEq(observationState.cardinality, 1); @@ -96,7 +96,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); Oracle.Observation memory observation = geomeanOracle.getObservation(key, 0); assertTrue(observation.initialized); assertEq(observation.blockTimestamp, 1); @@ -105,7 +105,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testAfterInitializeObserve0() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); uint32[] memory secondsAgo = new uint32[](1); secondsAgo[0] = 0; (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = @@ -117,7 +117,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionNoObservations() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); modifyPositionRouter.modifyPosition( key, IPoolManager.ModifyPositionParams( @@ -139,7 +139,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservation() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds modifyPositionRouter.modifyPosition( key, @@ -162,7 +162,7 @@ contract TestGeomeanOracle is Test, Deployers { } function testBeforeModifyPositionObservationAndCardinality() public { - manager.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); + initializeRouter.initialize(key, SQRT_RATIO_2_1, ZERO_BYTES); geomeanOracle.setTime(3); // advance 2 seconds geomeanOracle.increaseCardinalityNext(key, 2); GeomeanOracle.ObservationState memory observationState = geomeanOracle.getState(key); diff --git a/test/LimitOrder.t.sol b/test/LimitOrder.t.sol index 415e30be..94cca602 100644 --- a/test/LimitOrder.t.sol +++ b/test/LimitOrder.t.sol @@ -63,7 +63,7 @@ contract TestLimitOrder is Test, Deployers { function testGetTickLowerLastWithDifferentPrice() public { PoolKey memory differentKey = PoolKey(Currency.wrap(address(token0)), Currency.wrap(address(token1)), 3000, 61, limitOrder); - manager.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); + initializeRouter.initialize(differentKey, SQRT_RATIO_10_1, ZERO_BYTES); assertEq(limitOrder.getTickLowerLast(differentKey.toId()), 22997); } @@ -197,7 +197,7 @@ contract TestLimitOrder is Test, Deployers { ); assertEq(limitOrder.getTickLowerLast(id), 60); - (, int24 tick,,) = manager.getSlot0(id); + (, int24 tick,) = manager.getSlot0(id); assertEq(tick, 60); (bool filled,,, uint256 token0Total, uint256 token1Total,) = limitOrder.epochInfos(Epoch.wrap(1)); diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 14125657..1e3b6a5f 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -96,7 +96,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { assertEq(twamm.lastVirtualOrderTimestamp(initId), 0); vm.warp(10000); - manager.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(initKey, SQRT_RATIO_1_1, ZERO_BYTES); assertEq(twamm.lastVirtualOrderTimestamp(initId), 10000); } diff --git a/test/shared/implementation/FullRangeImplementation.sol b/test/shared/implementation/FullRangeImplementation.sol index 8ee0589f..63592f5c 100644 --- a/test/shared/implementation/FullRangeImplementation.sol +++ b/test/shared/implementation/FullRangeImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract FullRangeImplementation is FullRange { constructor(IPoolManager _poolManager, FullRange addressToEtch) FullRange(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/GeomeanOracleImplementation.sol b/test/shared/implementation/GeomeanOracleImplementation.sol index 68b669f1..0c964671 100644 --- a/test/shared/implementation/GeomeanOracleImplementation.sol +++ b/test/shared/implementation/GeomeanOracleImplementation.sol @@ -10,7 +10,7 @@ contract GeomeanOracleImplementation is GeomeanOracle { uint32 public time; constructor(IPoolManager _poolManager, GeomeanOracle addressToEtch) GeomeanOracle(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/LimitOrderImplementation.sol b/test/shared/implementation/LimitOrderImplementation.sol index b70f2553..c0f5a5f8 100644 --- a/test/shared/implementation/LimitOrderImplementation.sol +++ b/test/shared/implementation/LimitOrderImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract LimitOrderImplementation is LimitOrder { constructor(IPoolManager _poolManager, LimitOrder addressToEtch) LimitOrder(_poolManager) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/shared/implementation/TWAMMImplementation.sol b/test/shared/implementation/TWAMMImplementation.sol index 883eeb62..27a9e10c 100644 --- a/test/shared/implementation/TWAMMImplementation.sol +++ b/test/shared/implementation/TWAMMImplementation.sol @@ -8,7 +8,7 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; contract TWAMMImplementation is TWAMM { constructor(IPoolManager poolManager, uint256 interval, TWAMM addressToEtch) TWAMM(poolManager, interval) { - Hooks.validateHookAddress(addressToEtch, getHooksCalls()); + Hooks.validateHookPermissions(addressToEtch, getHooksCalls()); } // make this a no-op in testing diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index b924ed61..54832b4a 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -36,14 +36,15 @@ contract HookEnabledSwapRouter is PoolTestBase { bytes memory hookData ) external payable returns (BalanceDelta delta) { delta = abi.decode( - manager.lock(abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), (BalanceDelta) + manager.lock(address(this), abi.encode(CallbackData(msg.sender, testSettings, key, params, hookData))), + (BalanceDelta) ); uint256 ethBalance = address(this).balance; if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); } - function lockAcquired(bytes calldata rawData) external returns (bytes memory) { + function lockAcquired(address, /*sender*/ bytes calldata rawData) external returns (bytes memory) { require(msg.sender == address(manager)); CallbackData memory data = abi.decode(rawData, (CallbackData)); From 5f4b7171edcf24be0cc977898f43da2bc345455c Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 11 Dec 2023 14:02:41 -0500 Subject: [PATCH 58/80] use prev values --- test/TWAMM.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index 1e3b6a5f..bc4f73ff 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -188,8 +188,8 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { assertEq(sellRate0For1, 2e18 / (expiration2 - submitTimestamp2)); assertEq(sellRate1For0, 3e18 / (expiration2 - submitTimestamp1)); - assertEq(earningsFactor0For1, 1636776489931663248324424309240000); - assertEq(earningsFactor1For0, 1534530274609724617872321172427618); + assertEq(earningsFactor0For1, 1712020976636017581269515821040000); + assertEq(earningsFactor1For0, 1470157410324350030712806974476955); } function testTWAMM_submitOrder_EmitsEvent() public { From ffe3b05dfe0e3e390f49cf39b8d731aa158cb5b3 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 11 Dec 2023 14:27:23 -0500 Subject: [PATCH 59/80] change twamm to use pool getters --- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/hooks/examples/TWAMM.sol | 4 ++-- contracts/libraries/PoolGetters.sol | 5 +++-- lib/v4-core | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 21c2b598..0aef60be 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -146128 \ No newline at end of file +146158 \ No newline at end of file diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index bb0592ad..395469a7 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -525,7 +525,7 @@ contract TWAMM is BaseHook, ITWAMM { _isCrossingInitializedTick(params.pool, poolManager, poolKey, finalSqrtPriceX96); if (crossingInitializedTick) { - int128 liquidityNetAtTick = poolManager.getNetLiquidityAtTick(poolKey.toId(), tick); + int128 liquidityNetAtTick = poolManager.getPoolTickInfo(poolKey.toId(), tick).liquidityNet; uint160 initializedSqrtPrice = TickMath.getSqrtRatioAtTick(tick); uint256 swapDelta0 = SqrtPriceMath.getAmount0Delta( @@ -609,7 +609,7 @@ contract TWAMM is BaseHook, ITWAMM { unchecked { // update pool - int128 liquidityNet = poolManager.getNetLiquidityAtTick(poolKey.toId(), params.initializedTick); + int128 liquidityNet = poolManager.getPoolTickInfo(poolKey.toId(), params.initializedTick).liquidityNet; if (initializedSqrtPrice < params.pool.sqrtPriceX96) liquidityNet = -liquidityNet; params.pool.liquidity = liquidityNet < 0 ? params.pool.liquidity - uint128(-liquidityNet) diff --git a/contracts/libraries/PoolGetters.sol b/contracts/libraries/PoolGetters.sol index 78f34c87..e3cb318b 100644 --- a/contracts/libraries/PoolGetters.sol +++ b/contracts/libraries/PoolGetters.sol @@ -7,6 +7,7 @@ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; /// @title Helper functions to access pool information +/// TODO: Expose other getters on core with extsload. Only use when extsload is available and storage layout is frozen. library PoolGetters { uint256 constant POOL_SLOT = 10; uint256 constant TICKS_OFFSET = 4; @@ -62,7 +63,7 @@ library PoolGetters { // all the 1s at or to the right of the current bitPos uint256 mask = (1 << bitPos) - 1 + (1 << bitPos); // uint256 masked = self[wordPos] & mask; - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the right of or at the current tick, return rightmost in the word initialized = masked != 0; @@ -75,7 +76,7 @@ library PoolGetters { (int16 wordPos, uint8 bitPos) = position(compressed + 1); // all the 1s at or to the left of the bitPos uint256 mask = ~((1 << bitPos) - 1); - uint256 masked = getTickBitmapAtWord(poolManager, poolId, wordPos) & mask; + uint256 masked = poolManager.getPoolBitmapInfo(poolId, wordPos) & mask; // if there are no initialized ticks to the left of the current tick, return leftmost in the word initialized = masked != 0; diff --git a/lib/v4-core b/lib/v4-core index cda1f483..83557113 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit cda1f483d41b0db7ee28a7de3f3bbe47c7f33cfc +Subproject commit 83557113a0425eb3d81570c30e7a5ce550037149 From 6a72276c339aec55e7b8d75d7c5fbb806fb14a15 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 11 Dec 2023 15:11:36 -0500 Subject: [PATCH 60/80] changes after merging main --- contracts/base/PeripheryPayments.sol | 2 +- contracts/interfaces/IPeripheryPayments.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol index f272da34..24466924 100644 --- a/contracts/base/PeripheryPayments.sol +++ b/contracts/base/PeripheryPayments.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.19; import {ERC20} from "solmate/tokens/ERC20.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol index 765b980f..f3c24660 100644 --- a/contracts/interfaces/IPeripheryPayments.sol +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; /// @title Periphery Payments /// @notice Functions to ease deposits and withdrawals of ETH From 57183bdd7e540d65b002ccf7b9787ea1ada18898 Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Mon, 11 Dec 2023 15:23:20 -0500 Subject: [PATCH 61/80] use --via-ir in cli --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 280df88b..f6d99f2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,6 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test -vvv --via-ir env: FOUNDRY_PROFILE: ci From 18d68e812f056675e7356222a745315c2221ea4c Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Thu, 14 Dec 2023 12:30:46 -0500 Subject: [PATCH 62/80] fix formatting --- test/TWAMM.t.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/TWAMM.t.sol b/test/TWAMM.t.sol index bc4f73ff..17ed64a1 100644 --- a/test/TWAMM.t.sol +++ b/test/TWAMM.t.sol @@ -61,11 +61,7 @@ contract TWAMMTest is Test, Deployers, GasSnapshot { token0 = MockERC20(Currency.unwrap(currency0)); token1 = MockERC20(Currency.unwrap(currency1)); - TWAMMImplementation impl = new TWAMMImplementation( - manager, - 10_000, - twamm - ); + TWAMMImplementation impl = new TWAMMImplementation(manager, 10_000, twamm); (, bytes32[] memory writes) = vm.accesses(address(impl)); vm.etch(address(twamm), address(impl).code); // for each storage key that was written during the hook implementation, copy the value over From fead64a423609083982e195016c814e307319ddc Mon Sep 17 00:00:00 2001 From: Sara Reynolds Date: Thu, 14 Dec 2023 12:53:17 -0500 Subject: [PATCH 63/80] fix FullRange/TWAMM hook --- .forge-snapshots/FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- contracts/hooks/examples/FullRange.sol | 4 +++- contracts/hooks/examples/TWAMM.sol | 8 +------- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 7d8545fe..ef62f828 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -410731 \ No newline at end of file +410761 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index e7c101bd..b3688dfa 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -204653 \ No newline at end of file +204683 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index f8ded9d9..bc1c95e2 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200027 \ No newline at end of file +200057 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 504a326a..b5d7708e 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -386065 \ No newline at end of file +386095 \ No newline at end of file diff --git a/contracts/hooks/examples/FullRange.sol b/contracts/hooks/examples/FullRange.sol index ecd4a26b..1bd0cfe2 100644 --- a/contracts/hooks/examples/FullRange.sol +++ b/contracts/hooks/examples/FullRange.sol @@ -299,12 +299,14 @@ contract FullRange is BaseHook, ILockCallback { pool.hasAccruedFees = false; } - function lockAcquired(address, /*sender*/ bytes calldata rawData) + function lockAcquired(address sender, bytes calldata rawData) external override(ILockCallback, BaseHook) poolManagerOnly returns (bytes memory) { + // Now that manager can be called by EOAs with a lock target, it's necessary for lockAcquired to check the original sender if it wants to trust the data passed through. + if (sender != address(this)) revert SenderMustBeHook(); CallbackData memory data = abi.decode(rawData, (CallbackData)); BalanceDelta delta; diff --git a/contracts/hooks/examples/TWAMM.sol b/contracts/hooks/examples/TWAMM.sol index 395469a7..4fd5dd74 100644 --- a/contracts/hooks/examples/TWAMM.sol +++ b/contracts/hooks/examples/TWAMM.sol @@ -131,12 +131,6 @@ contract TWAMM is BaseHook, ITWAMM { self.lastVirtualOrderTimestamp = block.timestamp; } - struct CallbackData { - address sender; - PoolKey key; - IPoolManager.SwapParams params; - } - /// @inheritdoc ITWAMM function executeTWAMMOrders(PoolKey memory key) public { PoolId poolId = key.toId(); @@ -306,7 +300,7 @@ contract TWAMM is BaseHook, ITWAMM { IERC20Minimal(Currency.unwrap(token)).safeTransfer(to, amountTransferred); } - function lockAcquired(address sender, bytes calldata rawData) + function lockAcquired(address, /*sender*/ bytes calldata rawData) external override poolManagerOnly From 588ffde934cd29053f479f0dc3a8495c93e09aa0 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 15 Dec 2023 12:20:40 -0500 Subject: [PATCH 64/80] update ticks counter --- .../FullOracleObserve0After5Seconds.snap | 2 +- .../FullOracleObserve200By13.snap | 2 +- .../FullOracleObserve200By13Plus5.snap | 2 +- .../FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .../FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .../FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .../FullRangeRemoveLiquidity.snap | 2 +- .../FullRangeRemoveLiquidityAndRebalance.snap | 2 +- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .../OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .../OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- ...eObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .../OracleObserveCurrentTime.snap | 2 +- ...racleObserveCurrentTimeCounterfactual.snap | 2 +- .../OracleObserveLast20Seconds.snap | 2 +- .../OracleObserveLatestEqual.snap | 2 +- .../OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .../OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- contracts/interfaces/IQuoter.sol | 5 ++- contracts/lens/Quoter.sol | 39 ++++++++++--------- contracts/libraries/PathKey.sol | 6 +-- contracts/libraries/PoolTicksCounter.sol | 15 ++++--- test/Quoter.t.sol | 35 ++++++++--------- 35 files changed, 83 insertions(+), 77 deletions(-) diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index b1ff0c36..a08fb8e1 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2771 +2687 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 5e58bbb6..bb219663 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -23377 +22933 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 25649c80..6eb59a1d 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23624 +23180 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index 3327d279..94c197e9 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2798 +2738 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index 61958e5e..75080690 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -22396 +21892 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index 6d1b226a..9b54c31b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22695 +22191 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 73617bff..94ac0e08 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -410761 +407968 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index c1a8daa3..d1198e0f 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -204683 +201962 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index db35c1f6..aef75115 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -156432 +153306 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 4e3b576b..3b5a43d1 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -897565 +1112212 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 7412ad37..58273980 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200057 +197519 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index 734f0550..8e473407 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1 +1 @@ -386095 +379147 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index b10cb111..3f185fb2 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -114700 +111940 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index f1d06abf..68f6f4d2 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -154641 +151523 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 84b13171..f484e31f 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254711 +254660 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 3921c0e0..83917a8d 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245393 +245360 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 5d4871b3..8f98b8b1 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54893 +54869 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index c2340fa1..ee2ae68d 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45575 +45569 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index f84eea30..1e8b26e0 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72361 +72316 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index 60345e8b..a695bf26 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6618 +6492 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 695a03c6..5265bba3 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -88543 +86878 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index b1ff0c36..a08fb8e1 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2771 +2687 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index cdf4da11..d0974c4f 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6807 +6684 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index 1862acef..05796bbf 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6319 +6193 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a2e13edc..ed8dd329 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3466 +3382 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 2cd08dd7..1ba4a8d1 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -146158 +145648 \ No newline at end of file diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 89d3e27d..002e2c93 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PathKey} from "../libraries/PathKey.sol"; /// @title Quoter Interface @@ -12,6 +12,7 @@ import {PathKey} from "../libraries/PathKey.sol"; /// to compute the result. They are also not gas efficient and should not be called on-chain. interface IQuoter { error InvalidLockAcquiredSender(); + error InvalidLockCaller(); error InvalidQuoteBatchParams(); error LockFailure(); error NotSelf(); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 4d4f3226..13076b81 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -2,15 +2,15 @@ pragma solidity ^0.8.20; import "forge-std/console2.sol"; -import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {ILockCallback} from "@uniswap/v4-core/contracts/interfaces/callback/ILockCallback.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {ILockCallback} from "@uniswap/v4-core/src/interfaces/callback/ILockCallback.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 {IQuoter} from "../interfaces/IQuoter.sol"; import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKeyLib} from "../libraries/PathKey.sol"; @@ -51,7 +51,7 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - try manager.lock(abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { return _handleRevertSingle(reason, params.poolKey); } @@ -66,7 +66,7 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } @@ -80,7 +80,7 @@ contract Quoter is IQuoter, ILockCallback { { if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.amountOut; - try manager.lock(abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; return _handleRevertSingle(reason, params.poolKey); @@ -97,17 +97,20 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList ) { - try manager.lock(abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} + try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutput.selector, params)) {} catch (bytes memory reason) { return _handleRevert(reason); } } /// @inheritdoc ILockCallback - function lockAcquired(bytes calldata data) external returns (bytes memory) { + function lockAcquired(address lockCaller, bytes calldata data) external returns (bytes memory) { if (msg.sender != address(manager)) { revert InvalidLockAcquiredSender(); } + if (lockCaller != address(this)) { + revert InvalidLockCaller(); + } (bool success, bytes memory returnData) = address(this).call(data); if (success) return returnData; @@ -149,7 +152,7 @@ contract Quoter is IQuoter, ILockCallback { int24 tickAfter; BalanceDelta deltas; deltaAmounts = new int128[](2); - (, tickBefore,,) = manager.getSlot0(poolKey.toId()); + (, tickBefore,) = manager.getSlot0(poolKey.toId()); reason = validateRevertReason(reason); (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); deltaAmounts[0] = deltas.amount0(); @@ -186,7 +189,7 @@ contract Quoter is IQuoter, ILockCallback { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = PathKeyLib.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); - (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); + (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( poolKey, @@ -243,7 +246,7 @@ contract Quoter is IQuoter, ILockCallback { params.path[i - 1], i == pathLength ? params.currencyOut : prevCurrencyIn ); - (, int24 tickBefore,,) = manager.getSlot0(poolKey.toId()); + (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( poolKey, @@ -306,7 +309,7 @@ contract Quoter is IQuoter, ILockCallback { }), hookData ); - (sqrtPriceX96After, tickAfter,,) = manager.getSlot0(poolKey.toId()); + (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); } /// @dev return either the sqrtPriceLimit from user input, or the max/min value possible depending on trade direction diff --git a/contracts/libraries/PathKey.sol b/contracts/libraries/PathKey.sol index d13ccfce..f9d5da33 100644 --- a/contracts/libraries/PathKey.sol +++ b/contracts/libraries/PathKey.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; -import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; struct PathKey { Currency intermediateCurrency; diff --git a/contracts/libraries/PoolTicksCounter.sol b/contracts/libraries/PoolTicksCounter.sol index 95212893..b0e9ab5b 100644 --- a/contracts/libraries/PoolTicksCounter.sol +++ b/contracts/libraries/PoolTicksCounter.sol @@ -2,9 +2,9 @@ pragma solidity >=0.8.20; import {PoolGetters} from "./PoolGetters.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; library PoolTicksCounter { using PoolIdLibrary for PoolKey; @@ -37,13 +37,15 @@ library PoolTicksCounter { // 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 = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); + uint256 bmAfter = self.getPoolBitmapInfo(key.toId(), wordPosAfter); + //uint256 bmAfter = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosAfter); 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 = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); + uint256 bmBefore = self.getPoolBitmapInfo(key.toId(), wordPos); + //uint256 bmBefore = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPos); tickBeforeInitialized = ((bmBefore & (1 << bitPos)) > 0) && ((tickBefore % key.tickSpacing) == 0) && (tickBefore < tickAfter); @@ -70,7 +72,8 @@ library PoolTicksCounter { mask = mask & (type(uint256).max >> (255 - bitPosHigher)); } - uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); + //uint256 bmLower = PoolGetters.getTickBitmapAtWord(self, key.toId(), wordPosLower); + uint256 bmLower = self.getPoolBitmapInfo(key.toId(), wordPosLower); uint256 masked = bmLower & mask; initializedTicksLoaded += countOneBits(masked); wordPosLower++; diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index cd1cacb7..813503a6 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -8,17 +8,17 @@ import {IQuoter} from "../contracts/interfaces/IQuoter.sol"; import {Quoter} from "../contracts/lens/Quoter.sol"; import {LiquidityAmounts} from "../contracts/libraries/LiquidityAmounts.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; -import {SafeCast} from "@uniswap/v4-core/contracts/libraries/SafeCast.sol"; -import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; -import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol"; -import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; -import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; -import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/src/test/PoolModifyPositionTest.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; contract QuoterTest is Test, Deployers { using SafeCast for *; @@ -36,7 +36,6 @@ contract QuoterTest is Test, Deployers { Quoter quoter; - PoolManager manager; PoolModifyPositionTest positionManager; MockERC20 token0; @@ -50,7 +49,7 @@ contract QuoterTest is Test, Deployers { MockERC20[] tokenPath; function setUp() public { - manager = new PoolManager(CONTROLLER_GAS_LIMIT); + deployFreshManagerAndRouters(); quoter = new Quoter(address(manager)); positionManager = new PoolModifyPositionTest(manager); @@ -120,7 +119,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_callLockAcquired_reverts() public { vm.expectRevert(IQuoter.InvalidLockAcquiredSender.selector); vm.prank(address(manager)); - quoter.lockAcquired(abi.encodeWithSelector(quoter.lockAcquired.selector, "0x")); + quoter.lockAcquired(address(this), abi.encodeWithSelector(quoter.lockAcquired.selector, "0x")); } function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { @@ -539,7 +538,7 @@ contract QuoterTest is Test, Deployers { } function setupPool(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyPosition( @@ -554,7 +553,7 @@ contract QuoterTest is Test, Deployers { } function setupPoolMultiplePositions(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); MockERC20(Currency.unwrap(poolKey.currency1)).approve(address(positionManager), type(uint256).max); positionManager.modifyPosition( @@ -584,9 +583,9 @@ contract QuoterTest is Test, Deployers { function setupPoolWithZeroTickInitialized(PoolKey memory poolKey) internal { PoolId poolId = poolKey.toId(); - (uint160 sqrtPriceX96,,,) = manager.getSlot0(poolId); + (uint160 sqrtPriceX96,,) = manager.getSlot0(poolId); if (sqrtPriceX96 == 0) { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + initializeRouter.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); } MockERC20(Currency.unwrap(poolKey.currency0)).approve(address(positionManager), type(uint256).max); From f6a551669b8e17e076bf0750de157561fddecec2 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 15 Dec 2023 12:46:51 -0500 Subject: [PATCH 65/80] update Quoter test --- contracts/lens/Quoter.sol | 1 - test/Quoter.t.sol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 13076b81..bec9709a 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 813503a6..44c20cb1 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -119,7 +119,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_callLockAcquired_reverts() public { vm.expectRevert(IQuoter.InvalidLockAcquiredSender.selector); vm.prank(address(manager)); - quoter.lockAcquired(address(this), abi.encodeWithSelector(quoter.lockAcquired.selector, "0x")); + quoter.lockAcquired(address(quoter), abi.encodeWithSelector(quoter.lockAcquired.selector, address(this), "0x")); } function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { From 9204cea8f433e1b7e55959d15b5ad2c46c458ca5 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 10:44:41 -0500 Subject: [PATCH 66/80] typo --- contracts/interfaces/IQuoter.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index 002e2c93..a25fb03a 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -60,7 +60,7 @@ interface IQuoter { /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams` /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 - /// recipient The indented recipient of the output tokens + /// recipient The intended recipient of the output tokens /// amountIn 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 @@ -75,7 +75,7 @@ interface IQuoter { /// @param params the params for the quote, encoded as 'QuoteExactInputParams' /// currencyIn The input currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info - /// recipient The indented recipient of the output tokens + /// recipient The intended recipient of the output tokens /// amountIn 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 @@ -92,7 +92,7 @@ interface IQuoter { /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams` /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 - /// recipient The indented recipient of the output tokens + /// recipient The intended recipient of the output tokens /// amountOut 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 @@ -107,7 +107,7 @@ interface IQuoter { /// @param params the params for the quote, encoded as 'QuoteExactInputParams' /// currencyOut The output currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info - /// recipient The indented recipient of the output tokens + /// recipient The intended recipient of the output tokens /// amountOut 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 From 678919f9872476014fbf9aa0fbfd28ff6b090044 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 10:48:48 -0500 Subject: [PATCH 67/80] typo --- contracts/interfaces/IQuoter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index a25fb03a..e38bdd95 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -104,7 +104,7 @@ interface IQuoter { returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); /// @notice Returns the delta amounts along the swap path for a given exact output swap - /// @param params the params for the quote, encoded as 'QuoteExactInputParams' + /// @param params the params for the quote, encoded as 'QuoteExactOutputParams' /// currencyOut The output currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// recipient The intended recipient of the output tokens From b7f737fa0dc4e072de64d59b7741785d8fdbbe76 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 11:54:22 -0500 Subject: [PATCH 68/80] simplify handleRevertSingle --- contracts/lens/Quoter.sol | 41 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index bec9709a..852395a3 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -52,7 +52,7 @@ contract Quoter is IQuoter, ILockCallback { { try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactInputSingle.selector, params)) {} catch (bytes memory reason) { - return _handleRevertSingle(reason, params.poolKey); + return _handleRevertSingle(reason); } } @@ -82,7 +82,7 @@ contract Quoter is IQuoter, ILockCallback { try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; - return _handleRevertSingle(reason, params.poolKey); + return _handleRevertSingle(reason); } } @@ -142,22 +142,13 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev parse revert bytes from a single-pool quote - function _handleRevertSingle(bytes memory reason, PoolKey memory poolKey) + function _handleRevertSingle(bytes memory reason) private - view + pure returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - int24 tickBefore; - int24 tickAfter; - BalanceDelta deltas; - deltaAmounts = new int128[](2); - (, tickBefore,) = manager.getSlot0(poolKey.toId()); reason = validateRevertReason(reason); - (deltas, sqrtPriceX96After, tickAfter) = abi.decode(reason, (BalanceDelta, uint160, int24)); - deltaAmounts[0] = deltas.amount0(); - deltaAmounts[1] = deltas.amount1(); - - initializedTicksLoaded = PoolTicksCounter.countInitializedTicksLoaded(manager, poolKey, tickBefore, tickAfter); + (deltaAmounts, sqrtPriceX96After, initializedTicksLoaded) = abi.decode(reason, (int128[], uint160, uint32)); } /// @dev parse revert bytes from a potentially multi-hop quote and return the delta amounts, sqrtPriceX96After, and initializedTicksLoaded @@ -217,6 +208,8 @@ contract Quoter is IQuoter, ILockCallback { /// @dev quote an ExactInput swap on a pool, then revert with the result function _quoteExactInputSingle(QuoteExactInputSingleParams memory params) public selfOnly returns (bytes memory) { + (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); + (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, @@ -224,7 +217,15 @@ contract Quoter is IQuoter, ILockCallback { params.sqrtPriceLimitX96, params.hookData ); - bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + + int128[] memory deltaAmounts = new int128[](2); + + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); + + uint32 initializedTicksLoaded = + PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); assembly { revert(add(0x20, result), mload(result)) } @@ -278,6 +279,7 @@ contract Quoter is IQuoter, ILockCallback { selfOnly returns (bytes memory) { + (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, @@ -285,7 +287,14 @@ contract Quoter is IQuoter, ILockCallback { params.sqrtPriceLimitX96, params.hookData ); - bytes memory result = abi.encode(deltas, sqrtPriceX96After, tickAfter); + int128[] memory deltaAmounts = new int128[](2); + + deltaAmounts[0] = deltas.amount0(); + deltaAmounts[1] = deltas.amount1(); + + uint32 initializedTicksLoaded = + PoolTicksCounter.countInitializedTicksLoaded(manager, params.poolKey, tickBefore, tickAfter); + bytes memory result = abi.encode(deltaAmounts, sqrtPriceX96After, initializedTicksLoaded); assembly { revert(add(0x20, result), mload(result)) } From 67772de9a5d8e222cee8af16d5cbadcc1f8cf223 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 14:06:24 -0500 Subject: [PATCH 69/80] merge QuoteInput/OutputSingle structs --- contracts/interfaces/IQuoter.sol | 21 ++++++--------------- contracts/lens/Quoter.sol | 18 +++++++----------- test/Quoter.t.sol | 16 ++++++++-------- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index e38bdd95..dc128c6c 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -23,11 +23,11 @@ interface IQuoter { int128 currency1Delta; } - struct QuoteExactInputSingleParams { + struct QuoteExactSingleParams { PoolKey poolKey; bool zeroForOne; address recipient; - uint128 amountIn; + uint128 exactAmount; uint160 sqrtPriceLimitX96; bytes hookData; } @@ -39,15 +39,6 @@ interface IQuoter { uint128 amountIn; } - struct QuoteExactOutputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountOut; - uint160 sqrtPriceLimitX96; - bytes hookData; - } - struct QuoteExactOutputParams { Currency currencyOut; PathKey[] path; @@ -61,13 +52,13 @@ interface IQuoter { /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 /// recipient The intended recipient of the output tokens - /// amountIn The desired input amount + /// 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 sqrtPriceX96After The sqrt price of the pool after the swap /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded - function quoteExactInputSingle(QuoteExactInputSingleParams calldata params) + function quoteExactInputSingle(QuoteExactSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); @@ -93,13 +84,13 @@ interface IQuoter { /// poolKey The key for identifying a V4 pool /// zeroForOne If the swap is from currency0 to currency1 /// recipient The intended recipient of the output tokens - /// amountOut The desired input amount + /// 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 sqrtPriceX96After The sqrt price of the pool after the swap /// @return initializedTicksLoaded The number of initialized ticks that the swap loaded - function quoteExactOutputSingle(QuoteExactOutputSingleParams calldata params) + function quoteExactOutputSingle(QuoteExactSingleParams calldata params) external returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 852395a3..ea7fa3eb 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -45,7 +45,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + function quoteExactInputSingle(QuoteExactSingleParams memory params) public override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) @@ -72,12 +72,12 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + function quoteExactOutputSingle(QuoteExactSingleParams memory params) public override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.amountOut; + if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { @@ -207,13 +207,13 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactInput swap on a pool, then revert with the result - function _quoteExactInputSingle(QuoteExactInputSingleParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, - int256(int128(params.amountIn)), + int256(int128(params.exactAmount)), params.sqrtPriceLimitX96, params.hookData ); @@ -274,16 +274,12 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactOutput swap on a pool, then revert with the result - function _quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) - public - selfOnly - returns (bytes memory) - { + function _quoteExactOutputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, params.zeroForOne, - -int256(uint256(params.amountOut)), + -int256(uint256(params.exactAmount)), params.sqrtPriceLimitX96, params.hookData ); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 44c20cb1..627102a8 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -78,11 +78,11 @@ contract QuoterTest is Test, Deployers { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( - IQuoter.QuoteExactInputSingleParams({ + IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: true, recipient: address(this), - amountIn: uint128(amountIn), + exactAmount: uint128(amountIn), sqrtPriceLimitX96: 0, hookData: ZERO_BYTES }) @@ -100,11 +100,11 @@ contract QuoterTest is Test, Deployers { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactInputSingle( - IQuoter.QuoteExactInputSingleParams({ + IQuoter.QuoteExactSingleParams({ poolKey: key02, zeroForOne: false, recipient: address(this), - amountIn: uint128(amountIn), + exactAmount: uint128(amountIn), sqrtPriceLimitX96: 0, hookData: ZERO_BYTES }) @@ -316,11 +316,11 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutputSingle_0to1() public { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( - IQuoter.QuoteExactOutputSingleParams({ + IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: true, recipient: address(this), - amountOut: type(uint128).max, + exactAmount: type(uint128).max, sqrtPriceLimitX96: SQRT_RATIO_100_102, hookData: ZERO_BYTES }) @@ -334,11 +334,11 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutputSingle_1to0() public { (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) = quoter .quoteExactOutputSingle( - IQuoter.QuoteExactOutputSingleParams({ + IQuoter.QuoteExactSingleParams({ poolKey: key01, zeroForOne: false, recipient: address(this), - amountOut: type(uint128).max, + exactAmount: type(uint128).max, sqrtPriceLimitX96: SQRT_RATIO_102_100, hookData: ZERO_BYTES }) From fbb6d1b6ef73972b715885c29bac40eeef029103 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 14:38:20 -0500 Subject: [PATCH 70/80] combine IQuoter structs --- contracts/interfaces/IQuoter.sol | 22 +++++-------- contracts/lens/Quoter.sol | 16 +++++----- test/Quoter.t.sol | 54 ++++++++++++++++---------------- 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index dc128c6c..feda3d8e 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -32,19 +32,11 @@ interface IQuoter { bytes hookData; } - struct QuoteExactInputParams { - Currency currencyIn; + struct QuoteExactParams { + Currency exactCurrency; PathKey[] path; address recipient; - uint128 amountIn; - } - - struct QuoteExactOutputParams { - Currency currencyOut; - PathKey[] path; - address recipient; - uint128 amountOut; - uint160 sqrtPriceLimitX96; + uint128 exactAmount; } /// @notice Returns the delta amounts for a given exact input swap of a single pool @@ -67,11 +59,11 @@ interface IQuoter { /// currencyIn The input currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// recipient The intended recipient of the output tokens - /// amountIn The desired input amount + /// 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(QuoteExactInputParams memory params) + function quoteExactInput(QuoteExactParams memory params) external returns ( int128[] memory deltaAmounts, @@ -99,11 +91,11 @@ interface IQuoter { /// currencyOut The output currency of the swap /// path The path of the swap encoded as PathKeys that contains currency, fee, tickSpacing, and hook info /// recipient The intended recipient of the output tokens - /// amountOut The desired output amount + /// 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(QuoteExactOutputParams memory params) + function quoteExactOutput(QuoteExactParams memory params) external returns ( int128[] memory deltaAmounts, diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index ea7fa3eb..ea65dfa2 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -57,7 +57,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactInput(QuoteExactInputParams memory params) + function quoteExactInput(QuoteExactParams memory params) external returns ( int128[] memory deltaAmounts, @@ -87,7 +87,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @inheritdoc IQuoter - function quoteExactOutput(QuoteExactOutputParams memory params) + function quoteExactOutput(QuoteExactParams memory params) public override returns ( @@ -167,7 +167,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactInput swap along a path of tokens, then revert with the result - function _quoteExactInput(QuoteExactInputParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactInput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; int128[] memory deltaAmounts = new int128[](pathLength + 1); @@ -178,13 +178,13 @@ contract Quoter is IQuoter, ILockCallback { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = - PathKeyLib.getPoolAndSwapDirection(params.path[i], i == 0 ? params.currencyIn : prevCurrencyOut); + PathKeyLib.getPoolAndSwapDirection(params.path[i], i == 0 ? params.exactCurrency : prevCurrencyOut); (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( poolKey, zeroForOne, - int256(int128(i == 0 ? params.amountIn : prevAmountOut)), + int256(int128(i == 0 ? params.exactAmount : prevAmountOut)), 0, params.path[i].hookData ); @@ -232,7 +232,7 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev quote an ExactOutput swap along a path of tokens, then revert with the result - function _quoteExactOutput(QuoteExactOutputParams memory params) public selfOnly returns (bytes memory) { + function _quoteExactOutput(QuoteExactParams memory params) public selfOnly returns (bytes memory) { uint256 pathLength = params.path.length; int128[] memory deltaAmounts = new int128[](pathLength + 1); @@ -243,7 +243,7 @@ contract Quoter is IQuoter, ILockCallback { for (uint256 i = pathLength; i > 0; i--) { (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( - params.path[i - 1], i == pathLength ? params.currencyOut : prevCurrencyIn + params.path[i - 1], i == pathLength ? params.exactCurrency : prevCurrencyIn ); (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); @@ -251,7 +251,7 @@ contract Quoter is IQuoter, ILockCallback { (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( poolKey, !oneForZero, - -int256(int128(i == pathLength ? params.amountOut : prevAmountIn)), + -int256(int128(i == pathLength ? params.exactAmount : prevAmountIn)), 0, params.path[i - 1].hookData ); diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 627102a8..056b0818 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -125,7 +125,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -144,7 +144,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is -120. // -120 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 6200); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6200); ( int128[] memory deltaAmounts, @@ -163,7 +163,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is -60. // -60 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 4000); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 4000); ( int128[] memory deltaAmounts, @@ -179,7 +179,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_0to2_0TickLoaded_startingNotInitialized() public { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -196,7 +196,7 @@ contract QuoterTest is Test, Deployers { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -212,7 +212,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to0_2TicksLoaded() public { tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -231,7 +231,7 @@ contract QuoterTest is Test, Deployers { // The swap amount is set such that the active tick after the swap is 120. // 120 is an initialized tick for this pool. We check that we don't count it. - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 6250); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 6250); ( int128[] memory deltaAmounts, @@ -248,7 +248,7 @@ contract QuoterTest is Test, Deployers { setupPoolWithZeroTickInitialized(key02); tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 200); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 200); // Tick 0 initialized. Tick after = 1 ( @@ -266,7 +266,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to0_0TickLoaded_startingNotInitialized() public { tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 103); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 103); ( int128[] memory deltaAmounts, @@ -282,7 +282,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactInput_2to1() public { tokenPath.push(token2); tokenPath.push(token1); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -298,7 +298,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); tokenPath.push(token1); - IQuoter.QuoteExactInputParams memory params = getExactInputParams(tokenPath, 10000); + IQuoter.QuoteExactParams memory params = getExactInputParams(tokenPath, 10000); ( int128[] memory deltaAmounts, @@ -352,7 +352,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutput_0to2_2TicksLoaded() public { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); ( int128[] memory deltaAmounts, @@ -369,7 +369,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6143); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6143); ( int128[] memory deltaAmounts, @@ -386,7 +386,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 4000); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 4000); ( int128[] memory deltaAmounts, @@ -404,7 +404,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 100); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 100); // Tick 0 initialized. Tick after = 1 ( @@ -422,7 +422,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token0); tokenPath.push(token2); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 10); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 10); ( int128[] memory deltaAmounts, @@ -438,7 +438,7 @@ contract QuoterTest is Test, Deployers { function testQuoter_quoteExactOutput_2to0_2TicksLoaded() public { tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 15000); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 15000); ( int128[] memory deltaAmounts, @@ -456,7 +456,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6223); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6223); ( int128[] memory deltaAmounts, @@ -474,7 +474,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token0); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 6000); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 6000); ( int128[] memory deltaAmounts, uint160[] memory sqrtPriceX96AfterList, @@ -491,7 +491,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token1); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 9871); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9871); ( int128[] memory deltaAmounts, @@ -510,7 +510,7 @@ contract QuoterTest is Test, Deployers { tokenPath.push(token2); tokenPath.push(token1); - IQuoter.QuoteExactOutputParams memory params = getExactOutputParams(tokenPath, 9745); + IQuoter.QuoteExactParams memory params = getExactOutputParams(tokenPath, 9745); ( int128[] memory deltaAmounts, @@ -631,32 +631,32 @@ contract QuoterTest is Test, Deployers { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal view - returns (IQuoter.QuoteExactInputParams memory params) + returns (IQuoter.QuoteExactParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = 0; i < _tokenPath.length - 1; i++) { path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); } - params.currencyIn = Currency.wrap(address(_tokenPath[0])); + params.exactCurrency = Currency.wrap(address(_tokenPath[0])); params.path = path; params.recipient = address(this); - params.amountIn = uint128(amountIn); + params.exactAmount = uint128(amountIn); } function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal view - returns (IQuoter.QuoteExactOutputParams memory params) + returns (IQuoter.QuoteExactParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = _tokenPath.length - 1; i > 0; i--) { path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); } - params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.exactCurrency = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); params.path = path; params.recipient = address(this); - params.amountOut = uint128(amountOut); + params.exactAmount = uint128(amountOut); } } From ad3a966a5f9d05dba0fb3721509fbf7f4ca52690 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Mon, 18 Dec 2023 15:35:12 -0500 Subject: [PATCH 71/80] using ... ordering --- contracts/lens/Quoter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index ea65dfa2..aceee007 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -15,8 +15,8 @@ import {PoolTicksCounter} from "../libraries/PoolTicksCounter.sol"; import {PathKeyLib} from "../libraries/PathKey.sol"; contract Quoter is IQuoter, ILockCallback { - using PoolIdLibrary for PoolKey; using Hooks for IHooks; + using PoolIdLibrary for PoolKey; /// @dev cache used to check a safety condition in exact output swaps. uint256 private amountOutCached; From 6a7882acb8032006502f530adc17b5609b34ffad Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 11:36:11 -0500 Subject: [PATCH 72/80] update snapshots --- .forge-snapshots/FullOracleObserve0After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserve200By13.snap | 2 +- .forge-snapshots/FullOracleObserve200By13Plus5.snap | 2 +- .forge-snapshots/FullOracleObserve5After5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveOldest.snap | 2 +- .forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap | 2 +- .forge-snapshots/FullOracleObserveZero.snap | 2 +- .forge-snapshots/FullRangeAddInitialLiquidity.snap | 2 +- .forge-snapshots/FullRangeAddLiquidity.snap | 2 +- .forge-snapshots/FullRangeFirstSwap.snap | 2 +- .forge-snapshots/FullRangeInitialize.snap | 2 +- .forge-snapshots/FullRangeRemoveLiquidity.snap | 2 +- .forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap | 6 +----- .forge-snapshots/FullRangeSecondSwap.snap | 2 +- .forge-snapshots/FullRangeSwap.snap | 2 +- .forge-snapshots/OracleGrow10Slots.snap | 2 +- .forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap | 2 +- .forge-snapshots/OracleGrow1Slot.snap | 2 +- .forge-snapshots/OracleGrow1SlotCardinalityGreater.snap | 2 +- .forge-snapshots/OracleInitialize.snap | 2 +- .../OracleObserveBetweenOldestAndOldestPlusOne.snap | 2 +- .forge-snapshots/OracleObserveCurrentTime.snap | 2 +- .../OracleObserveCurrentTimeCounterfactual.snap | 2 +- .forge-snapshots/OracleObserveLast20Seconds.snap | 2 +- .forge-snapshots/OracleObserveLatestEqual.snap | 2 +- .forge-snapshots/OracleObserveLatestTransform.snap | 2 +- .forge-snapshots/OracleObserveMiddle.snap | 2 +- .forge-snapshots/OracleObserveOldest.snap | 2 +- .forge-snapshots/OracleObserveSinceMostRecent.snap | 2 +- .forge-snapshots/TWAMMSubmitOrder.snap | 2 +- 30 files changed, 30 insertions(+), 34 deletions(-) diff --git a/.forge-snapshots/FullOracleObserve0After5Seconds.snap b/.forge-snapshots/FullOracleObserve0After5Seconds.snap index b1ff0c36..a08fb8e1 100644 --- a/.forge-snapshots/FullOracleObserve0After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve0After5Seconds.snap @@ -1 +1 @@ -2771 +2687 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13.snap b/.forge-snapshots/FullOracleObserve200By13.snap index 5e58bbb6..bb219663 100644 --- a/.forge-snapshots/FullOracleObserve200By13.snap +++ b/.forge-snapshots/FullOracleObserve200By13.snap @@ -1 +1 @@ -23377 +22933 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve200By13Plus5.snap b/.forge-snapshots/FullOracleObserve200By13Plus5.snap index 25649c80..6eb59a1d 100644 --- a/.forge-snapshots/FullOracleObserve200By13Plus5.snap +++ b/.forge-snapshots/FullOracleObserve200By13Plus5.snap @@ -1 +1 @@ -23624 +23180 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserve5After5Seconds.snap b/.forge-snapshots/FullOracleObserve5After5Seconds.snap index 3327d279..94c197e9 100644 --- a/.forge-snapshots/FullOracleObserve5After5Seconds.snap +++ b/.forge-snapshots/FullOracleObserve5After5Seconds.snap @@ -1 +1 @@ -2798 +2738 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldest.snap b/.forge-snapshots/FullOracleObserveOldest.snap index 61958e5e..75080690 100644 --- a/.forge-snapshots/FullOracleObserveOldest.snap +++ b/.forge-snapshots/FullOracleObserveOldest.snap @@ -1 +1 @@ -22396 +21892 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap index 6d1b226a..9b54c31b 100644 --- a/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap +++ b/.forge-snapshots/FullOracleObserveOldestAfter5Seconds.snap @@ -1 +1 @@ -22695 +22191 \ No newline at end of file diff --git a/.forge-snapshots/FullOracleObserveZero.snap b/.forge-snapshots/FullOracleObserveZero.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/FullOracleObserveZero.snap +++ b/.forge-snapshots/FullOracleObserveZero.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddInitialLiquidity.snap b/.forge-snapshots/FullRangeAddInitialLiquidity.snap index 73617bff..94ac0e08 100644 --- a/.forge-snapshots/FullRangeAddInitialLiquidity.snap +++ b/.forge-snapshots/FullRangeAddInitialLiquidity.snap @@ -1 +1 @@ -410761 +407968 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeAddLiquidity.snap b/.forge-snapshots/FullRangeAddLiquidity.snap index c1a8daa3..d1198e0f 100644 --- a/.forge-snapshots/FullRangeAddLiquidity.snap +++ b/.forge-snapshots/FullRangeAddLiquidity.snap @@ -1 +1 @@ -204683 +201962 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeFirstSwap.snap b/.forge-snapshots/FullRangeFirstSwap.snap index db35c1f6..aef75115 100644 --- a/.forge-snapshots/FullRangeFirstSwap.snap +++ b/.forge-snapshots/FullRangeFirstSwap.snap @@ -1 +1 @@ -156432 +153306 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeInitialize.snap b/.forge-snapshots/FullRangeInitialize.snap index 4e3b576b..3b5a43d1 100644 --- a/.forge-snapshots/FullRangeInitialize.snap +++ b/.forge-snapshots/FullRangeInitialize.snap @@ -1 +1 @@ -897565 +1112212 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidity.snap b/.forge-snapshots/FullRangeRemoveLiquidity.snap index 7412ad37..58273980 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidity.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidity.snap @@ -1 +1 @@ -200057 +197519 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap index eca2a631..8e473407 100644 --- a/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap +++ b/.forge-snapshots/FullRangeRemoveLiquidityAndRebalance.snap @@ -1,5 +1 @@ -<<<<<<< HEAD -379147 -======= -386095 ->>>>>>> main +379147 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSecondSwap.snap b/.forge-snapshots/FullRangeSecondSwap.snap index b10cb111..3f185fb2 100644 --- a/.forge-snapshots/FullRangeSecondSwap.snap +++ b/.forge-snapshots/FullRangeSecondSwap.snap @@ -1 +1 @@ -114700 +111940 \ No newline at end of file diff --git a/.forge-snapshots/FullRangeSwap.snap b/.forge-snapshots/FullRangeSwap.snap index f1d06abf..68f6f4d2 100644 --- a/.forge-snapshots/FullRangeSwap.snap +++ b/.forge-snapshots/FullRangeSwap.snap @@ -1 +1 @@ -154641 +151523 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10Slots.snap b/.forge-snapshots/OracleGrow10Slots.snap index 84b13171..f484e31f 100644 --- a/.forge-snapshots/OracleGrow10Slots.snap +++ b/.forge-snapshots/OracleGrow10Slots.snap @@ -1 +1 @@ -254711 +254660 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap index 3921c0e0..83917a8d 100644 --- a/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow10SlotsCardinalityGreater.snap @@ -1 +1 @@ -245393 +245360 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1Slot.snap b/.forge-snapshots/OracleGrow1Slot.snap index 5d4871b3..8f98b8b1 100644 --- a/.forge-snapshots/OracleGrow1Slot.snap +++ b/.forge-snapshots/OracleGrow1Slot.snap @@ -1 +1 @@ -54893 +54869 \ No newline at end of file diff --git a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap index c2340fa1..ee2ae68d 100644 --- a/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap +++ b/.forge-snapshots/OracleGrow1SlotCardinalityGreater.snap @@ -1 +1 @@ -45575 +45569 \ No newline at end of file diff --git a/.forge-snapshots/OracleInitialize.snap b/.forge-snapshots/OracleInitialize.snap index f84eea30..1e8b26e0 100644 --- a/.forge-snapshots/OracleInitialize.snap +++ b/.forge-snapshots/OracleInitialize.snap @@ -1 +1 @@ -72361 +72316 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap index 60345e8b..a695bf26 100644 --- a/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap +++ b/.forge-snapshots/OracleObserveBetweenOldestAndOldestPlusOne.snap @@ -1 +1 @@ -6618 +6492 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTime.snap b/.forge-snapshots/OracleObserveCurrentTime.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTime.snap +++ b/.forge-snapshots/OracleObserveCurrentTime.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap +++ b/.forge-snapshots/OracleObserveCurrentTimeCounterfactual.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLast20Seconds.snap b/.forge-snapshots/OracleObserveLast20Seconds.snap index 695a03c6..5265bba3 100644 --- a/.forge-snapshots/OracleObserveLast20Seconds.snap +++ b/.forge-snapshots/OracleObserveLast20Seconds.snap @@ -1 +1 @@ -88543 +86878 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestEqual.snap b/.forge-snapshots/OracleObserveLatestEqual.snap index 92ab26f6..2a55d550 100644 --- a/.forge-snapshots/OracleObserveLatestEqual.snap +++ b/.forge-snapshots/OracleObserveLatestEqual.snap @@ -1 +1 @@ -2130 +2070 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveLatestTransform.snap b/.forge-snapshots/OracleObserveLatestTransform.snap index b1ff0c36..a08fb8e1 100644 --- a/.forge-snapshots/OracleObserveLatestTransform.snap +++ b/.forge-snapshots/OracleObserveLatestTransform.snap @@ -1 +1 @@ -2771 +2687 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveMiddle.snap b/.forge-snapshots/OracleObserveMiddle.snap index cdf4da11..d0974c4f 100644 --- a/.forge-snapshots/OracleObserveMiddle.snap +++ b/.forge-snapshots/OracleObserveMiddle.snap @@ -1 +1 @@ -6807 +6684 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveOldest.snap b/.forge-snapshots/OracleObserveOldest.snap index 1862acef..05796bbf 100644 --- a/.forge-snapshots/OracleObserveOldest.snap +++ b/.forge-snapshots/OracleObserveOldest.snap @@ -1 +1 @@ -6319 +6193 \ No newline at end of file diff --git a/.forge-snapshots/OracleObserveSinceMostRecent.snap b/.forge-snapshots/OracleObserveSinceMostRecent.snap index a2e13edc..ed8dd329 100644 --- a/.forge-snapshots/OracleObserveSinceMostRecent.snap +++ b/.forge-snapshots/OracleObserveSinceMostRecent.snap @@ -1 +1 @@ -3466 +3382 \ No newline at end of file diff --git a/.forge-snapshots/TWAMMSubmitOrder.snap b/.forge-snapshots/TWAMMSubmitOrder.snap index 2cd08dd7..1ba4a8d1 100644 --- a/.forge-snapshots/TWAMMSubmitOrder.snap +++ b/.forge-snapshots/TWAMMSubmitOrder.snap @@ -1 +1 @@ -146158 +145648 \ No newline at end of file From 6a2b71f11890866252903ed4ff999c8c6df8c379 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 16:43:15 -0500 Subject: [PATCH 73/80] move amountOutCached into inner call --- contracts/interfaces/IQuoter.sol | 1 + contracts/lens/Quoter.sol | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index feda3d8e..faeaeea8 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -14,6 +14,7 @@ interface IQuoter { error InvalidLockAcquiredSender(); error InvalidLockCaller(); error InvalidQuoteBatchParams(); + error InsufficientAmountOut(); error LockFailure(); error NotSelf(); error UnexpectedRevertBytes(); diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index aceee007..48c1c79f 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -77,8 +77,6 @@ contract Quoter is IQuoter, ILockCallback { override returns (int128[] memory deltaAmounts, uint160 sqrtPriceX96After, uint32 initializedTicksLoaded) { - if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; - try manager.lock(address(this), abi.encodeWithSelector(this._quoteExactOutputSingle.selector, params)) {} catch (bytes memory reason) { if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; @@ -275,6 +273,9 @@ contract Quoter is IQuoter, ILockCallback { /// @dev quote an ExactOutput swap on a pool, then revert with the result function _quoteExactOutputSingle(QuoteExactSingleParams memory params) public selfOnly returns (bytes memory) { + // if no price limit has been specified, cache the output amount for comparison in the swap callback + if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.exactAmount; + (, int24 tickBefore,) = manager.getSlot0(params.poolKey.toId()); (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( params.poolKey, @@ -283,6 +284,8 @@ contract Quoter is IQuoter, ILockCallback { params.sqrtPriceLimitX96, params.hookData ); + + if (amountOutCached != 0) delete amountOutCached; int128[] memory deltaAmounts = new int128[](2); deltaAmounts[0] = deltas.amount0(); @@ -313,6 +316,10 @@ contract Quoter is IQuoter, ILockCallback { }), hookData ); + // only exactOut case + if (amountOutCached != 0 && amountOutCached != uint256(int256(-deltas.amount1()))) { + revert InsufficientAmountOut(); + } (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); } From 88cbff3815383c2d2f582ffff6cbbe1d231081b2 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 16:49:34 -0500 Subject: [PATCH 74/80] using PathKeyLib for PathKey --- contracts/lens/Quoter.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 48c1c79f..f6ff06e9 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -12,11 +12,12 @@ 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 {PathKeyLib} from "../libraries/PathKey.sol"; +import {PathKey, PathKeyLib} from "../libraries/PathKey.sol"; contract Quoter is IQuoter, ILockCallback { using Hooks for IHooks; using PoolIdLibrary for PoolKey; + using PathKeyLib for PathKey; /// @dev cache used to check a safety condition in exact output swaps. uint256 private amountOutCached; @@ -176,7 +177,7 @@ contract Quoter is IQuoter, ILockCallback { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = - PathKeyLib.getPoolAndSwapDirection(params.path[i], i == 0 ? params.exactCurrency : prevCurrencyOut); + params.path[i].getPoolAndSwapDirection(i == 0 ? params.exactCurrency : prevCurrencyOut); (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( From c43f0f1b0e06b986e6cedd02dc3e1bb5209d24de Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 18:58:51 -0500 Subject: [PATCH 75/80] fix amountOutCached --- contracts/lens/Quoter.sol | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index f6ff06e9..2f7773ea 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; +import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; @@ -20,7 +21,7 @@ contract Quoter is IQuoter, ILockCallback { using PathKeyLib for PathKey; /// @dev cache used to check a safety condition in exact output swaps. - uint256 private amountOutCached; + uint128 private amountOutCached; // v4 Singleton contract IPoolManager public immutable manager; @@ -239,22 +240,23 @@ contract Quoter is IQuoter, ILockCallback { uint32[] memory initializedTicksLoadedList = new uint32[](pathLength); Currency prevCurrencyIn; uint128 prevAmountIn; + uint128 curAmountOut; for (uint256 i = pathLength; i > 0; i--) { + curAmountOut = i == pathLength ? params.exactAmount : prevAmountIn; + amountOutCached = curAmountOut; + (PoolKey memory poolKey, bool oneForZero) = PathKeyLib.getPoolAndSwapDirection( params.path[i - 1], i == pathLength ? params.exactCurrency : prevCurrencyIn ); (, int24 tickBefore,) = manager.getSlot0(poolKey.toId()); - (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = _swap( - poolKey, - !oneForZero, - -int256(int128(i == pathLength ? params.exactAmount : prevAmountIn)), - 0, - params.path[i - 1].hookData - ); + (BalanceDelta curDeltas, uint160 sqrtPriceX96After, int24 tickAfter) = + _swap(poolKey, !oneForZero, -int256(uint256(curAmountOut)), 0, params.path[i - 1].hookData); + // always clear because sqrtPriceLimitX96 is set to 0 always + delete amountOutCached; (int128 deltaIn, int128 deltaOut) = !oneForZero ? (curDeltas.amount0(), curDeltas.amount1()) : (curDeltas.amount1(), curDeltas.amount0()); deltaAmounts[i - 1] += deltaIn; @@ -304,7 +306,7 @@ contract Quoter is IQuoter, ILockCallback { function _swap( PoolKey memory poolKey, bool zeroForOne, - int256 amountSpecified, + int256 amountSpecified, // exactInput = amountSpecified > 0 uint160 sqrtPriceLimitX96, bytes memory hookData ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { @@ -318,7 +320,7 @@ contract Quoter is IQuoter, ILockCallback { hookData ); // only exactOut case - if (amountOutCached != 0 && amountOutCached != uint256(int256(-deltas.amount1()))) { + if (amountOutCached != 0 && amountOutCached != uint128(zeroForOne ? -deltas.amount1() : -deltas.amount0())) { revert InsufficientAmountOut(); } (sqrtPriceX96After, tickAfter,) = manager.getSlot0(poolKey.toId()); From f00e616555127d966e1d64521d6fae48b8943842 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 19:00:53 -0500 Subject: [PATCH 76/80] remove console2 import --- contracts/lens/Quoter.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 2f7773ea..84753cd5 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.20; -import "forge-std/console2.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; From 753d11a91ea14316f8194b9d53c6b7d3de5292f6 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Tue, 19 Dec 2023 19:16:09 -0500 Subject: [PATCH 77/80] resurface revert reason --- contracts/interfaces/IQuoter.sol | 2 +- contracts/lens/Quoter.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/interfaces/IQuoter.sol b/contracts/interfaces/IQuoter.sol index faeaeea8..90a390fc 100644 --- a/contracts/interfaces/IQuoter.sol +++ b/contracts/interfaces/IQuoter.sol @@ -17,7 +17,7 @@ interface IQuoter { error InsufficientAmountOut(); error LockFailure(); error NotSelf(); - error UnexpectedRevertBytes(); + error UnexpectedRevertBytes(bytes revertData); struct PoolDeltas { int128 currency0Delta; diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 84753cd5..09d4d216 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -124,13 +124,13 @@ contract Quoter is IQuoter, ILockCallback { function validateRevertReason(bytes memory reason) private pure returns (bytes memory) { if (reason.length < MINIMUM_VALID_RESPONSE_LENGTH) { //if InvalidLockAcquiredSender() - if (reason.length == MINIMUM_CUSTOM_ERROR_LENGTH) { + if (reason.length <= MINIMUM_CUSTOM_ERROR_LENGTH) { assembly { revert(reason, 4) } } if (reason.length < MINIMUM_REASON_LENGTH) { - revert UnexpectedRevertBytes(); + revert UnexpectedRevertBytes(reason); } assembly { reason := add(reason, 0x04) From 386a95780bf28b0ee8db7c0d4b29f864c22da07b Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 20 Dec 2023 17:43:54 -0500 Subject: [PATCH 78/80] clean up validateRevert --- contracts/lens/Quoter.sol | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 09d4d216..08614c4c 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -28,9 +28,6 @@ contract Quoter is IQuoter, ILockCallback { /// @dev custom error function selector length uint256 internal constant MINIMUM_CUSTOM_ERROR_LENGTH = 4; - /// @dev function selector + length of bytes as uint256 + min length of revert reason padded to multiple of 32 bytes - uint256 internal constant MINIMUM_REASON_LENGTH = 68; - /// @dev min valid reason is 3-words long /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96; @@ -123,19 +120,7 @@ contract Quoter is IQuoter, ILockCallback { /// @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) { - //if InvalidLockAcquiredSender() - if (reason.length <= MINIMUM_CUSTOM_ERROR_LENGTH) { - assembly { - revert(reason, 4) - } - } - if (reason.length < MINIMUM_REASON_LENGTH) { - revert UnexpectedRevertBytes(reason); - } - assembly { - reason := add(reason, 0x04) - } - revert(abi.decode(reason, (string))); + revert UnexpectedRevertBytes(reason); } return reason; } From 91c018e58b16108bf23514fcfa08eef48816db76 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 20 Dec 2023 17:45:56 -0500 Subject: [PATCH 79/80] update natsppec --- contracts/lens/Quoter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 08614c4c..26a3dd0d 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -287,10 +287,11 @@ contract Quoter is IQuoter, ILockCallback { } /// @dev Execute a swap and return the amounts delta, as well as relevant pool state + /// @notice if amountSpecified > 0, the swap is exactInput, otherwise exactOutput function _swap( PoolKey memory poolKey, bool zeroForOne, - int256 amountSpecified, // exactInput = amountSpecified > 0 + int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes memory hookData ) private returns (BalanceDelta deltas, uint160 sqrtPriceX96After, int24 tickAfter) { From a657d5c84c6baae959e929de016d461a47e19590 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 20 Dec 2023 17:48:39 -0500 Subject: [PATCH 80/80] remove unused --- contracts/lens/Quoter.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/lens/Quoter.sol b/contracts/lens/Quoter.sol index 26a3dd0d..8b2b16e0 100644 --- a/contracts/lens/Quoter.sol +++ b/contracts/lens/Quoter.sol @@ -25,9 +25,6 @@ contract Quoter is IQuoter, ILockCallback { // v4 Singleton contract IPoolManager public immutable manager; - /// @dev custom error function selector length - uint256 internal constant MINIMUM_CUSTOM_ERROR_LENGTH = 4; - /// @dev min valid reason is 3-words long /// @dev int128[2] + sqrtPriceX96After padded to 32bytes + intializeTicksLoaded padded to 32bytes uint256 internal constant MINIMUM_VALID_RESPONSE_LENGTH = 96;