From 55db57811f88940c40c2beb4b3ee4d9d3d5dc614 Mon Sep 17 00:00:00 2001 From: ConjunctiveNormalForm Date: Fri, 17 Nov 2023 14:50:28 -0500 Subject: [PATCH] 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);