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);