From a69c7823d6489b986f6a98f422008979f90963ae Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Wed, 15 Nov 2023 10:11:31 -0500 Subject: [PATCH] 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]); + } + } }