From b6f8d3b42a249127a97fa196ce1537fcc73655b2 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Thu, 17 Aug 2023 10:16:59 -0400 Subject: [PATCH 01/19] start routing --- contracts/Routing.sol | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 contracts/Routing.sol diff --git a/contracts/Routing.sol b/contracts/Routing.sol new file mode 100644 index 00000000..d951723c --- /dev/null +++ b/contracts/Routing.sol @@ -0,0 +1,12 @@ +// 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"; + +abstract contract V4Routing { + +} From d5dc31a68de740ba2a3a7fe28cffcd46a4d355ae Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Wed, 23 Aug 2023 13:31:18 -0400 Subject: [PATCH 02/19] start routing contract --- contracts/Routing.sol | 68 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/contracts/Routing.sol b/contracts/Routing.sol index d951723c..56c56a20 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -3,10 +3,72 @@ 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"; -abstract contract V4Routing { - +/// @title UniswapV4Routing +/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools +abstract contract UniswapV4Routing { + IPoolManager immutable poolManager; + + error NotPoolManager(); + error InvalidSwapType(); + + struct SwapInfo { + SwapType swapType; + bytes params; + } + + struct ExactInputParams { + PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey) + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + } + + enum SwapType { + ExactInput, + ExactInputSingle, + ExactOutput, + ExactOutputSingle + } + + /// @dev Only the pool manager may call this function + modifier poolManagerOnly() { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + _; + } + + modifier checkDeadline(uint256 deadline) { + require(block.timestamp <= deadline, 'Transaction too old'); + _; + } + + constructor(IPoolManager _poolManager) { + poolManager = _poolManager; + } + + function v4Swap(SwapInfo memory swapInfo, uint256 deadline) internal checkDeadline(deadline) { + poolManager.lock(abi.encode(swapInfo)); + } + + function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly() returns (bytes memory) { + SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); + + if (swapInfo.swapType == SwapType.ExactInput) { + _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + } else { + revert InvalidSwapType(); + } + + return bytes(""); + } + + function _swapExactInput(ExactInputParams memory params) private { + for (uint256 i = 0; i < params.path.length; i++) { + + } + } + + } From 2ca710a168703be467f095c83068421ac299b668 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Thu, 24 Aug 2023 13:22:29 -0400 Subject: [PATCH 03/19] naive swapExactIn impl --- .forge-snapshots/RouterExactIn0Hops.snap | 1 + .forge-snapshots/RouterExactIn1Hop.snap | 1 + .forge-snapshots/RouterExactIn2Hops.snap | 1 + contracts/Routing.sol | 80 ++++++-- test/Routing.t.sol | 185 ++++++++++++++++++ .../implementation/RoutingImplementation.sol | 18 ++ 6 files changed, 272 insertions(+), 14 deletions(-) create mode 100644 .forge-snapshots/RouterExactIn0Hops.snap create mode 100644 .forge-snapshots/RouterExactIn1Hop.snap create mode 100644 .forge-snapshots/RouterExactIn2Hops.snap create mode 100644 test/Routing.t.sol create mode 100644 test/shared/implementation/RoutingImplementation.sol diff --git a/.forge-snapshots/RouterExactIn0Hops.snap b/.forge-snapshots/RouterExactIn0Hops.snap new file mode 100644 index 00000000..0a5a2376 --- /dev/null +++ b/.forge-snapshots/RouterExactIn0Hops.snap @@ -0,0 +1 @@ +194338 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap new file mode 100644 index 00000000..b483211f --- /dev/null +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -0,0 +1 @@ +271794 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap new file mode 100644 index 00000000..16c03a4d --- /dev/null +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -0,0 +1 @@ +349270 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 56c56a20..73468e4c 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -5,25 +5,32 @@ import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.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/contracts/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; /// @title UniswapV4Routing /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools abstract contract UniswapV4Routing { + using CurrencyLibrary for Currency; + IPoolManager immutable poolManager; error NotPoolManager(); error InvalidSwapType(); + error TooLittleReceived(); struct SwapInfo { SwapType swapType; + address msgSender; bytes params; } struct ExactInputParams { - PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey) + PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey but bytes) address recipient; - uint256 amountIn; - uint256 amountOutMinimum; + uint128 amountIn; + uint128 amountOutMinimum; + uint160 sqrtPriceLimitX96; } enum SwapType { @@ -39,24 +46,19 @@ abstract contract UniswapV4Routing { _; } - modifier checkDeadline(uint256 deadline) { - require(block.timestamp <= deadline, 'Transaction too old'); - _; - } - constructor(IPoolManager _poolManager) { poolManager = _poolManager; } - function v4Swap(SwapInfo memory swapInfo, uint256 deadline) internal checkDeadline(deadline) { - poolManager.lock(abi.encode(swapInfo)); + function v4Swap(SwapType swapType, bytes memory params) internal { + poolManager.lock(abi.encode(SwapInfo(swapType, msg.sender, params))); } - function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly() returns (bytes memory) { + function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly returns (bytes memory) { SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { - _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams))); + _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -64,11 +66,61 @@ abstract contract UniswapV4Routing { return bytes(""); } - function _swapExactInput(ExactInputParams memory params) private { + function _swapExactInput(ExactInputParams memory params, address msgSender) private { for (uint256 i = 0; i < params.path.length; i++) { - + (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i]); + BalanceDelta delta = poolManager.swap( + poolKey, + IPoolManager.SwapParams( + zeroForOne, + int256(int128(params.amountIn)), + params.sqrtPriceLimitX96 == 0 + ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : params.sqrtPriceLimitX96 + ), + bytes("") + ); + + if (i == 0) { + if (zeroForOne) { + _pay(Currency.unwrap(poolKey.currency0), msgSender, address(poolManager), uint256(uint128(delta.amount0()))); + poolManager.settle(poolKey.currency0); + } else { + _pay(Currency.unwrap(poolKey.currency1), msgSender, address(poolManager), uint256(uint128(delta.amount1()))); + poolManager.settle(poolKey.currency1); + } + } + + if (i == params.path.length - 1) { + if (zeroForOne) { + poolManager.take(poolKey.currency1, msgSender, uint256(uint128(-delta.amount1()))); + } else { + poolManager.take(poolKey.currency0, msgSender, uint256(uint128(-delta.amount0()))); + } + } + + if (zeroForOne) { + params.amountIn = uint128(-delta.amount1()); + } else { + params.amountIn = uint128(-delta.amount0()); + } } + + if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } + function _getPoolAndSwapDirection(PoolKey memory params) + pure + private + returns (PoolKey memory poolKey, bool zeroForOne) + { + (Currency currency0, Currency currency1) = params.currency0 < params.currency1 + ? (params.currency0, params.currency1) + : (params.currency1, params.currency0); + + zeroForOne = params.currency0 == currency0; + poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + } + function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; } diff --git a/test/Routing.t.sol b/test/Routing.t.sol new file mode 100644 index 00000000..2cf452e3 --- /dev/null +++ b/test/Routing.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import 'forge-std/console.sol'; + +import {Test} from "forge-std/Test.sol"; +import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol"; +import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; +import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {UniswapV4Routing} from "../contracts/Routing.sol"; +import {RoutingImplementation} from "./shared/implementation/RoutingImplementation.sol"; +import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; + +contract RoutingTest is Test, Deployers, GasSnapshot { + using CurrencyLibrary for Currency; + + PoolManager manager; + PoolModifyPositionTest positionManager; + RoutingImplementation router; + + MockERC20 token0; + MockERC20 token1; + MockERC20 token2; + MockERC20 token3; + + PoolKey key0; + PoolKey key1; + PoolKey key2; + + PoolKey[] path; + + function setUp() public { + manager = new PoolManager(500000); + router = new RoutingImplementation(manager); + positionManager = new PoolModifyPositionTest(manager); + + token0 = new MockERC20("Test0", "0", 18, 2 ** 128); + token1 = new MockERC20("Test1", "1", 18, 2 ** 128); + token2 = new MockERC20("Test2", "2", 18, 2 ** 128); + token3 = new MockERC20("Test3", "3", 18, 2 ** 128); + + key0 = createPoolKey(token0, token1); + key1 = createPoolKey(token1, token2); + key2 = createPoolKey(token2, token3); + + setupPool(key0); + setupPool(key1); + setupPool(key2); + + token0.approve(address(router), type(uint256).max); + token1.approve(address(router), type(uint256).max); + token2.approve(address(router), type(uint256).max); + token3.approve(address(router), type(uint256).max); + } + + function testRouter_swapExactIn_0Hops_zeroForOne() public { + path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); + PoolKey[] memory _pathCached = path; + + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + snapStart("RouterExactIn0Hops"); + router.swap( + UniswapV4Routing.SwapType.ExactInput, + abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + ); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, amountIn); + assertEq(newBalance1 - prevBalance1, expectedAmountOut); + } + + function testRouter_swapExactIn_0Hops_oneForZero() public { + path.push(PoolKey(toCurrency(token1), toCurrency(token0), 3000, 60, IHooks(address(0)))); + + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + router.swap( + UniswapV4Routing.SwapType.ExactInput, + abi.encode(UniswapV4Routing.ExactInputParams(path, address(this), uint128(amountIn), 0, 0)) + ); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance1 - newBalance1, amountIn); + assertEq(newBalance0 - prevBalance0, expectedAmountOut); + } + + function testRouter_swapExactIn_1Hop() public { + path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); + path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); + PoolKey[] memory _pathCached = path; + + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 984211133872795298; + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance2 = token2.balanceOf(address(this)); + + + snapStart("RouterExactIn1Hop"); + router.swap( + UniswapV4Routing.SwapType.ExactInput, + abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + ); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance2 = token2.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, amountIn); + assertEq(prevBalance1 - newBalance1, 0); + assertEq(newBalance2 - prevBalance2, expectedAmountOut); + assertEq(token0.balanceOf(address(router)), 0); + assertEq(token1.balanceOf(address(router)), 0); + assertEq(token2.balanceOf(address(router)), 0); + } + + function testRouter_swapExactIn_2Hops() public { + path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); + path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); + path.push(PoolKey(toCurrency(token2), toCurrency(token3), 3000, 60, IHooks(address(0)))); + PoolKey[] memory _pathCached = path; + + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 976467664490096191; + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance3 = token3.balanceOf(address(this)); + + + snapStart("RouterExactIn2Hops"); + router.swap( + UniswapV4Routing.SwapType.ExactInput, + abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + ); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance3 = token3.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, amountIn); + assertEq(newBalance3 - prevBalance3, expectedAmountOut); + assertEq(token0.balanceOf(address(router)), 0); + assertEq(token1.balanceOf(address(router)), 0); + assertEq(token2.balanceOf(address(router)), 0); + assertEq(token3.balanceOf(address(router)), 0); + } + + + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) 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(address(0))); + } + + 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(-887220, 887220, 200 ether)); + } + + function toCurrency(MockERC20 token) internal pure returns (Currency) { + return Currency.wrap(address(token)); + } +} diff --git a/test/shared/implementation/RoutingImplementation.sol b/test/shared/implementation/RoutingImplementation.sol new file mode 100644 index 00000000..44c99638 --- /dev/null +++ b/test/shared/implementation/RoutingImplementation.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {UniswapV4Routing} from "../../../contracts/Routing.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; + +contract RoutingImplementation is UniswapV4Routing { + constructor(IPoolManager _poolManager) UniswapV4Routing(_poolManager) {} + + function swap(SwapType swapType, bytes memory params) external { + v4Swap(swapType, params); + } + + function _pay(address token, address payer, address recipient, uint256 amount) internal override { + IERC20Minimal(token).transferFrom(payer, recipient, amount); + } +} From 664d2c25b796ad4768f463d94ac85f3e835f6c7c Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Wed, 30 Aug 2023 17:33:02 -0400 Subject: [PATCH 04/19] lint + bytecode snapshot --- .forge-snapshots/RouterBytecode.snap | 1 + contracts/Routing.sol | 16 +++++++++++++--- test/Routing.t.sol | 15 ++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 .forge-snapshots/RouterBytecode.snap diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap new file mode 100644 index 00000000..b3aa7c5e --- /dev/null +++ b/.forge-snapshots/RouterBytecode.snap @@ -0,0 +1 @@ +4129 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 73468e4c..fdbabbd2 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -83,10 +83,20 @@ abstract contract UniswapV4Routing { if (i == 0) { if (zeroForOne) { - _pay(Currency.unwrap(poolKey.currency0), msgSender, address(poolManager), uint256(uint128(delta.amount0()))); + _pay( + Currency.unwrap(poolKey.currency0), + msgSender, + address(poolManager), + uint256(uint128(delta.amount0())) + ); poolManager.settle(poolKey.currency0); } else { - _pay(Currency.unwrap(poolKey.currency1), msgSender, address(poolManager), uint256(uint128(delta.amount1()))); + _pay( + Currency.unwrap(poolKey.currency1), + msgSender, + address(poolManager), + uint256(uint128(delta.amount1())) + ); poolManager.settle(poolKey.currency1); } } @@ -110,8 +120,8 @@ abstract contract UniswapV4Routing { } function _getPoolAndSwapDirection(PoolKey memory params) - pure private + pure returns (PoolKey memory poolKey, bool zeroForOne) { (Currency currency0, Currency currency1) = params.currency0 < params.currency1 diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 2cf452e3..7a8ed487 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import 'forge-std/console.sol'; +import "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; @@ -17,7 +17,7 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Curren import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; contract RoutingTest is Test, Deployers, GasSnapshot { - using CurrencyLibrary for Currency; + using CurrencyLibrary for Currency; PoolManager manager; PoolModifyPositionTest positionManager; @@ -37,7 +37,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { function setUp() public { manager = new PoolManager(500000); router = new RoutingImplementation(manager); - positionManager = new PoolModifyPositionTest(manager); + positionManager = new PoolModifyPositionTest(manager); token0 = new MockERC20("Test0", "0", 18, 2 ** 128); token1 = new MockERC20("Test1", "1", 18, 2 ** 128); @@ -58,6 +58,10 @@ contract RoutingTest is Test, Deployers, GasSnapshot { token3.approve(address(router), type(uint256).max); } + function testRouter_bytecodeSize() public { + snapSize("RouterBytecode", address(router)); + } + function testRouter_swapExactIn_0Hops_zeroForOne() public { path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); PoolKey[] memory _pathCached = path; @@ -115,7 +119,6 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance1 = token1.balanceOf(address(this)); uint256 prevBalance2 = token2.balanceOf(address(this)); - snapStart("RouterExactIn1Hop"); router.swap( UniswapV4Routing.SwapType.ExactInput, @@ -147,7 +150,6 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance3 = token3.balanceOf(address(this)); - snapStart("RouterExactIn2Hops"); router.swap( UniswapV4Routing.SwapType.ExactInput, @@ -166,7 +168,6 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(token3.balanceOf(address(router)), 0); } - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) 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(address(0))); @@ -180,6 +181,6 @@ contract RoutingTest is Test, Deployers, GasSnapshot { } function toCurrency(MockERC20 token) internal pure returns (Currency) { - return Currency.wrap(address(token)); + return Currency.wrap(address(token)); } } From 8b687b8c5e4789049526b1fc92f0f7cdc633d217 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Thu, 31 Aug 2023 13:23:40 -0400 Subject: [PATCH 05/19] change concept of hops to token hops --- .forge-snapshots/RouterExactIn0Hops.snap | 1 - .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 1 + test/Routing.t.sol | 14 +++++++------- 5 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 .forge-snapshots/RouterExactIn0Hops.snap create mode 100644 .forge-snapshots/RouterExactIn3Hops.snap diff --git a/.forge-snapshots/RouterExactIn0Hops.snap b/.forge-snapshots/RouterExactIn0Hops.snap deleted file mode 100644 index 0a5a2376..00000000 --- a/.forge-snapshots/RouterExactIn0Hops.snap +++ /dev/null @@ -1 +0,0 @@ -194338 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index b483211f..0a5a2376 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -271794 \ No newline at end of file +194338 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 16c03a4d..b483211f 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -349270 \ No newline at end of file +271794 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap new file mode 100644 index 00000000..16c03a4d --- /dev/null +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -0,0 +1 @@ +349270 \ No newline at end of file diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 7a8ed487..5d2f623e 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -62,7 +62,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapSize("RouterBytecode", address(router)); } - function testRouter_swapExactIn_0Hops_zeroForOne() public { + function testRouter_swapExactIn_1Hop_zeroForOne() public { path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); PoolKey[] memory _pathCached = path; @@ -72,7 +72,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - snapStart("RouterExactIn0Hops"); + snapStart("RouterExactIn1Hop"); router.swap( UniswapV4Routing.SwapType.ExactInput, abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) @@ -86,7 +86,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, expectedAmountOut); } - function testRouter_swapExactIn_0Hops_oneForZero() public { + function testRouter_swapExactIn_1Hop_oneForZero() public { path.push(PoolKey(toCurrency(token1), toCurrency(token0), 3000, 60, IHooks(address(0)))); uint256 amountIn = 1 ether; @@ -107,7 +107,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(newBalance0 - prevBalance0, expectedAmountOut); } - function testRouter_swapExactIn_1Hop() public { + function testRouter_swapExactIn_2Hops() public { path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); PoolKey[] memory _pathCached = path; @@ -119,7 +119,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance1 = token1.balanceOf(address(this)); uint256 prevBalance2 = token2.balanceOf(address(this)); - snapStart("RouterExactIn1Hop"); + snapStart("RouterExactIn2Hops"); router.swap( UniswapV4Routing.SwapType.ExactInput, abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) @@ -138,7 +138,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(token2.balanceOf(address(router)), 0); } - function testRouter_swapExactIn_2Hops() public { + function testRouter_swapExactIn_3Hops() public { path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); path.push(PoolKey(toCurrency(token2), toCurrency(token3), 3000, 60, IHooks(address(0)))); @@ -150,7 +150,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance3 = token3.balanceOf(address(this)); - snapStart("RouterExactIn2Hops"); + snapStart("RouterExactIn3Hops"); router.swap( UniswapV4Routing.SwapType.ExactInput, abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) From 1717039f53399833801aa4f5fdddb5211bb0b129 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Fri, 1 Sep 2023 12:59:40 -0400 Subject: [PATCH 06/19] UniswapV4Routing --> Routing --- contracts/Routing.sol | 4 ++-- test/Routing.t.sol | 18 +++++++++--------- .../implementation/RoutingImplementation.sol | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contracts/Routing.sol b/contracts/Routing.sol index fdbabbd2..c634a82b 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -10,7 +10,7 @@ import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; /// @title UniswapV4Routing /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -abstract contract UniswapV4Routing { +abstract contract Routing { using CurrencyLibrary for Currency; IPoolManager immutable poolManager; @@ -26,7 +26,7 @@ abstract contract UniswapV4Routing { } struct ExactInputParams { - PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey but bytes) + PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey but bytes) address recipient; uint128 amountIn; uint128 amountOutMinimum; diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 5d2f623e..9c5420ba 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -10,7 +10,7 @@ import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {UniswapV4Routing} from "../contracts/Routing.sol"; +import {Routing} from "../contracts/Routing.sol"; import {RoutingImplementation} from "./shared/implementation/RoutingImplementation.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; @@ -74,8 +74,8 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapStart("RouterExactIn1Hop"); router.swap( - UniswapV4Routing.SwapType.ExactInput, - abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + Routing.SwapType.ExactInput, + abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) ); snapEnd(); @@ -96,8 +96,8 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance1 = token1.balanceOf(address(this)); router.swap( - UniswapV4Routing.SwapType.ExactInput, - abi.encode(UniswapV4Routing.ExactInputParams(path, address(this), uint128(amountIn), 0, 0)) + Routing.SwapType.ExactInput, + abi.encode(Routing.ExactInputParams(path, address(this), uint128(amountIn), 0, 0)) ); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -121,8 +121,8 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapStart("RouterExactIn2Hops"); router.swap( - UniswapV4Routing.SwapType.ExactInput, - abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + Routing.SwapType.ExactInput, + abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) ); snapEnd(); @@ -152,8 +152,8 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapStart("RouterExactIn3Hops"); router.swap( - UniswapV4Routing.SwapType.ExactInput, - abi.encode(UniswapV4Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) + Routing.SwapType.ExactInput, + abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) ); snapEnd(); diff --git a/test/shared/implementation/RoutingImplementation.sol b/test/shared/implementation/RoutingImplementation.sol index 44c99638..f705db31 100644 --- a/test/shared/implementation/RoutingImplementation.sol +++ b/test/shared/implementation/RoutingImplementation.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {UniswapV4Routing} from "../../../contracts/Routing.sol"; +import {Routing} from "../../../contracts/Routing.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -contract RoutingImplementation is UniswapV4Routing { - constructor(IPoolManager _poolManager) UniswapV4Routing(_poolManager) {} +contract RoutingImplementation is Routing { + constructor(IPoolManager _poolManager) Routing(_poolManager) {} function swap(SwapType swapType, bytes memory params) external { v4Swap(swapType, params); From 2549c3afc6e6a1c0575af9b127d3ee9423b3089c Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Fri, 1 Sep 2023 13:13:02 -0400 Subject: [PATCH 07/19] use PathKey --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- contracts/Routing.sol | 26 ++++++--- test/Routing.t.sol | 74 +++++++++++++----------- 6 files changed, 63 insertions(+), 45 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index b3aa7c5e..3cb2c2f8 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4129 \ No newline at end of file +4187 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 0a5a2376..03c0d55e 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -194338 \ No newline at end of file +194222 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index b483211f..8c23a320 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271794 \ No newline at end of file +271324 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 16c03a4d..235eebeb 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -349270 \ No newline at end of file +348443 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index c634a82b..0e454b52 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import "forge-std/console.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.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/contracts/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; /// @title UniswapV4Routing /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools @@ -25,8 +27,16 @@ abstract contract Routing { bytes params; } + struct PathKey { + Currency currencyOut; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + } + struct ExactInputParams { - PoolKey[] path; // TODO: pack this and get rid of redundant token (ultimately will NOT be PoolKey but bytes) + Currency currencyIn; + PathKey[] path; address recipient; uint128 amountIn; uint128 amountOutMinimum; @@ -68,7 +78,7 @@ abstract contract Routing { function _swapExactInput(ExactInputParams memory params, address msgSender) private { for (uint256 i = 0; i < params.path.length; i++) { - (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i]); + (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); BalanceDelta delta = poolManager.swap( poolKey, IPoolManager.SwapParams( @@ -114,21 +124,21 @@ abstract contract Routing { } else { params.amountIn = uint128(-delta.amount0()); } + params.currencyIn = params.path[i].currencyOut; } if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } - function _getPoolAndSwapDirection(PoolKey memory params) + function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) private - pure + view returns (PoolKey memory poolKey, bool zeroForOne) { - (Currency currency0, Currency currency1) = params.currency0 < params.currency1 - ? (params.currency0, params.currency1) - : (params.currency1, params.currency0); + (Currency currency0, Currency currency1) = + currencyIn < params.currencyOut ? (currencyIn, params.currencyOut) : (params.currencyOut, currencyIn); - zeroForOne = params.currency0 == currency0; + zeroForOne = currencyIn == currency0; poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 9c5420ba..03db00c0 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "forge-std/console.sol"; - import {Test} from "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; @@ -32,7 +30,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { PoolKey key1; PoolKey key2; - PoolKey[] path; + MockERC20[] tokenPath; function setUp() public { manager = new PoolManager(500000); @@ -63,20 +61,18 @@ contract RoutingTest is Test, Deployers, GasSnapshot { } function testRouter_swapExactIn_1Hop_zeroForOne() public { - path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); - PoolKey[] memory _pathCached = path; - uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; + tokenPath.push(token0); + tokenPath.push(token1); + Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactIn1Hop"); - router.swap( - Routing.SwapType.ExactInput, - abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) - ); + router.swap(Routing.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -87,18 +83,16 @@ contract RoutingTest is Test, Deployers, GasSnapshot { } function testRouter_swapExactIn_1Hop_oneForZero() public { - path.push(PoolKey(toCurrency(token1), toCurrency(token0), 3000, 60, IHooks(address(0)))); - uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; + tokenPath.push(token1); + tokenPath.push(token0); + Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - router.swap( - Routing.SwapType.ExactInput, - abi.encode(Routing.ExactInputParams(path, address(this), uint128(amountIn), 0, 0)) - ); + router.swap(Routing.SwapType.ExactInput, abi.encode(params)); uint256 newBalance0 = token0.balanceOf(address(this)); uint256 newBalance1 = token1.balanceOf(address(this)); @@ -108,22 +102,20 @@ contract RoutingTest is Test, Deployers, GasSnapshot { } function testRouter_swapExactIn_2Hops() public { - path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); - path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); - PoolKey[] memory _pathCached = path; - uint256 amountIn = 1 ether; uint256 expectedAmountOut = 984211133872795298; + tokenPath.push(token0); + tokenPath.push(token1); + tokenPath.push(token2); + Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); uint256 prevBalance2 = token2.balanceOf(address(this)); snapStart("RouterExactIn2Hops"); - router.swap( - Routing.SwapType.ExactInput, - abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) - ); + router.swap(Routing.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -139,22 +131,20 @@ contract RoutingTest is Test, Deployers, GasSnapshot { } function testRouter_swapExactIn_3Hops() public { - path.push(PoolKey(toCurrency(token0), toCurrency(token1), 3000, 60, IHooks(address(0)))); - path.push(PoolKey(toCurrency(token1), toCurrency(token2), 3000, 60, IHooks(address(0)))); - path.push(PoolKey(toCurrency(token2), toCurrency(token3), 3000, 60, IHooks(address(0)))); - PoolKey[] memory _pathCached = path; - uint256 amountIn = 1 ether; uint256 expectedAmountOut = 976467664490096191; + tokenPath.push(token0); + tokenPath.push(token1); + tokenPath.push(token2); + tokenPath.push(token3); + Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance3 = token3.balanceOf(address(this)); snapStart("RouterExactIn3Hops"); - router.swap( - Routing.SwapType.ExactInput, - abi.encode(Routing.ExactInputParams(_pathCached, address(this), uint128(amountIn), 0, 0)) - ); + router.swap(Routing.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -183,4 +173,22 @@ contract RoutingTest is Test, Deployers, GasSnapshot { function toCurrency(MockERC20 token) internal pure returns (Currency) { return Currency.wrap(address(token)); } + + function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) + internal + view + returns (Routing.ExactInputParams memory params) + { + Routing.PathKey[] memory path = new Routing.PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = Routing.PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0))); + } + + params.currencyIn = Currency.wrap(address(_tokenPath[0])); + params.path = path; + params.recipient = address(this); + params.amountIn = uint128(amountIn); + params.amountOutMinimum = 0; + params.sqrtPriceLimitX96 = 0; + } } From e1c46fd3a925503465064fa58ab72cd7e08c5ad5 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Fri, 1 Sep 2023 15:20:51 -0400 Subject: [PATCH 08/19] exactInputSingle --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInSingle.snap | 1 + contracts/Routing.sol | 46 ++++++++++++++++++++++- test/Routing.t.sol | 40 ++++++++++++++++++++ 7 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 .forge-snapshots/RouterExactInSingle.snap diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 3cb2c2f8..f3d151ed 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4187 \ No newline at end of file +5508 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 03c0d55e..60451bcd 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -194222 \ No newline at end of file +194327 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 8c23a320..580f0c2c 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271324 \ No newline at end of file +271499 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 235eebeb..ddaa0321 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348443 \ No newline at end of file +348688 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInSingle.snap b/.forge-snapshots/RouterExactInSingle.snap new file mode 100644 index 00000000..58658ea3 --- /dev/null +++ b/.forge-snapshots/RouterExactInSingle.snap @@ -0,0 +1 @@ +192022 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 0e454b52..77a328d2 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "forge-std/console.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; @@ -34,6 +33,15 @@ abstract contract Routing { IHooks hooks; } + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint128 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + struct ExactInputParams { Currency currencyIn; PathKey[] path; @@ -69,6 +77,8 @@ abstract contract Routing { if (swapInfo.swapType == SwapType.ExactInput) { _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams)), swapInfo.msgSender); + } else if (swapInfo.swapType == SwapType.ExactInputSingle) { + _swapExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -76,6 +86,40 @@ abstract contract Routing { return bytes(""); } + function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private { + BalanceDelta delta = poolManager.swap( + params.poolKey, + IPoolManager.SwapParams( + params.zeroForOne, + int256(int128(params.amountIn)), + params.sqrtPriceLimitX96 == 0 + ? (params.zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : params.sqrtPriceLimitX96 + ), + bytes("") + ); + + if (params.zeroForOne) { + _pay( + Currency.unwrap(params.poolKey.currency0), + msgSender, + address(poolManager), + uint256(uint128(delta.amount0())) + ); + poolManager.settle(params.poolKey.currency0); + poolManager.take(params.poolKey.currency1, msgSender, uint256(uint128(-delta.amount1()))); + } else { + _pay( + Currency.unwrap(params.poolKey.currency1), + msgSender, + address(poolManager), + uint256(uint128(delta.amount1())) + ); + poolManager.settle(params.poolKey.currency1); + poolManager.take(params.poolKey.currency0, msgSender, uint256(uint128(-delta.amount0()))); + } + } + function _swapExactInput(ExactInputParams memory params, address msgSender) private { for (uint256 i = 0; i < params.path.length; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 03db00c0..c79e5a41 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -60,6 +60,46 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapSize("RouterBytecode", address(router)); } + function testRouter_swapExactInSingle_zeroForOne() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + Routing.ExactInputSingleParams memory params = + Routing.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + snapStart("RouterExactInSingle"); + router.swap(Routing.SwapType.ExactInputSingle, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, amountIn); + assertEq(newBalance1 - prevBalance1, expectedAmountOut); + } + + function testRouter_swapExactInSingle_oneForZero() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + Routing.ExactInputSingleParams memory params = + Routing.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + router.swap(Routing.SwapType.ExactInputSingle, abi.encode(params)); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance1 - newBalance1, amountIn); + assertEq(newBalance0 - prevBalance0, expectedAmountOut); + } + function testRouter_swapExactIn_1Hop_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; From 8aae01b092076483169765ec28938fbaa9041558 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Fri, 1 Sep 2023 16:57:11 -0400 Subject: [PATCH 09/19] save DRY progress --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInSingle.snap | 2 +- contracts/Routing.sol | 121 +++++++++------------- 6 files changed, 53 insertions(+), 78 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index f3d151ed..1492a39b 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5508 \ No newline at end of file +4383 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 60451bcd..5c3aef17 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -194327 \ No newline at end of file +194248 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 580f0c2c..f1252b40 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271499 \ No newline at end of file +271503 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index ddaa0321..4245379c 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348688 \ No newline at end of file +348777 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInSingle.snap b/.forge-snapshots/RouterExactInSingle.snap index 58658ea3..fc27e7d2 100644 --- a/.forge-snapshots/RouterExactInSingle.snap +++ b/.forge-snapshots/RouterExactInSingle.snap @@ -1 +1 @@ -192022 \ No newline at end of file +192093 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 77a328d2..bd9b705e 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -87,96 +87,66 @@ abstract contract Routing { } function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private { - BalanceDelta delta = poolManager.swap( - params.poolKey, - IPoolManager.SwapParams( - params.zeroForOne, - int256(int128(params.amountIn)), - params.sqrtPriceLimitX96 == 0 - ? (params.zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) - : params.sqrtPriceLimitX96 - ), - bytes("") + _swapExactInputPrivate( + params.poolKey, params.zeroForOne, params.amountIn, params.sqrtPriceLimitX96, msgSender, true, true ); - - if (params.zeroForOne) { - _pay( - Currency.unwrap(params.poolKey.currency0), - msgSender, - address(poolManager), - uint256(uint128(delta.amount0())) - ); - poolManager.settle(params.poolKey.currency0); - poolManager.take(params.poolKey.currency1, msgSender, uint256(uint128(-delta.amount1()))); - } else { - _pay( - Currency.unwrap(params.poolKey.currency1), - msgSender, - address(poolManager), - uint256(uint128(delta.amount1())) - ); - poolManager.settle(params.poolKey.currency1); - poolManager.take(params.poolKey.currency0, msgSender, uint256(uint128(-delta.amount0()))); - } } function _swapExactInput(ExactInputParams memory params, address msgSender) private { for (uint256 i = 0; i < params.path.length; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); - BalanceDelta delta = poolManager.swap( + uint128 amountOut = _swapExactInputPrivate( poolKey, - IPoolManager.SwapParams( - zeroForOne, - int256(int128(params.amountIn)), - params.sqrtPriceLimitX96 == 0 - ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) - : params.sqrtPriceLimitX96 - ), - bytes("") + zeroForOne, + params.amountIn, + params.sqrtPriceLimitX96, + msgSender, + i == 0, + i == params.path.length - 1 ); - if (i == 0) { - if (zeroForOne) { - _pay( - Currency.unwrap(poolKey.currency0), - msgSender, - address(poolManager), - uint256(uint128(delta.amount0())) - ); - poolManager.settle(poolKey.currency0); - } else { - _pay( - Currency.unwrap(poolKey.currency1), - msgSender, - address(poolManager), - uint256(uint128(delta.amount1())) - ); - poolManager.settle(poolKey.currency1); - } - } - - if (i == params.path.length - 1) { - if (zeroForOne) { - poolManager.take(poolKey.currency1, msgSender, uint256(uint128(-delta.amount1()))); - } else { - poolManager.take(poolKey.currency0, msgSender, uint256(uint128(-delta.amount0()))); - } - } - - if (zeroForOne) { - params.amountIn = uint128(-delta.amount1()); - } else { - params.amountIn = uint128(-delta.amount0()); - } + params.amountIn = amountOut; params.currencyIn = params.path[i].currencyOut; } if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } + function _swapExactInputPrivate( + PoolKey memory poolKey, + bool zeroForOne, + uint128 amountIn, + uint160 sqrtPriceLimitX96, + address msgSender, + bool settle, + bool take + ) private returns (uint128 amountOut) { + BalanceDelta delta = poolManager.swap( + poolKey, + IPoolManager.SwapParams( + zeroForOne, + int256(int128(amountIn)), + sqrtPriceLimitX96 == 0 + ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + : sqrtPriceLimitX96 + ), + bytes("") + ); + + if (zeroForOne) { + amountOut = uint128(-delta.amount1()); + if (settle) _payAndSettle(poolKey.currency0, msgSender, delta.amount0()); + if (take) poolManager.take(poolKey.currency1, msgSender, uint256(amountOut)); + } else { + amountOut = uint128(-delta.amount0()); + if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); + if (take) poolManager.take(poolKey.currency0, msgSender, uint256(amountOut)); + } + } + function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) private - view + pure returns (PoolKey memory poolKey, bool zeroForOne) { (Currency currency0, Currency currency1) = @@ -186,5 +156,10 @@ abstract contract Routing { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } + function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { + _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(settleAmount))); + poolManager.settle(currency); + } + function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; } From 5823d2fc41c173c5987c36a078298445a9018bbb Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 10:35:51 -0400 Subject: [PATCH 10/19] no sqrtPriceLimit for multipool hops --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- contracts/Routing.sol | 3 +-- test/Routing.t.sol | 1 - 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 1492a39b..39de0497 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4383 \ No newline at end of file +4363 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 5c3aef17..c197c87f 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -194248 \ No newline at end of file +193825 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index f1252b40..eeb8f643 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271503 \ No newline at end of file +271071 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 4245379c..657729cc 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348777 \ No newline at end of file +348334 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index bd9b705e..9c7faa76 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -48,7 +48,6 @@ abstract contract Routing { address recipient; uint128 amountIn; uint128 amountOutMinimum; - uint160 sqrtPriceLimitX96; } enum SwapType { @@ -99,7 +98,7 @@ abstract contract Routing { poolKey, zeroForOne, params.amountIn, - params.sqrtPriceLimitX96, + 0, msgSender, i == 0, i == params.path.length - 1 diff --git a/test/Routing.t.sol b/test/Routing.t.sol index c79e5a41..589ce4ad 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -229,6 +229,5 @@ contract RoutingTest is Test, Deployers, GasSnapshot { params.recipient = address(this); params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; - params.sqrtPriceLimitX96 = 0; } } From 7e253900b93eb54d99a2bc0a6cadf07feea4d32c Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 12:13:59 -0400 Subject: [PATCH 11/19] exactOut implemented w awkward loops/int conversions single hops passing on exactOut --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInSingle.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 1 + .forge-snapshots/RouterExactOut2Hops.snap | 1 + .forge-snapshots/RouterExactOut3Hops.snap | 1 + contracts/Routing.sol | 84 +++++++++++---- test/Routing.t.sol | 119 ++++++++++++++++++++++ 10 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 .forge-snapshots/RouterExactOut1Hop.snap create mode 100644 .forge-snapshots/RouterExactOut2Hops.snap create mode 100644 .forge-snapshots/RouterExactOut3Hops.snap diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 39de0497..640a57a5 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4363 \ No newline at end of file +4855 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index c197c87f..02ca69ef 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -193825 \ No newline at end of file +194162 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index eeb8f643..0bb7aa9d 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271071 \ No newline at end of file +271461 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 657729cc..ae20bc3c 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348334 \ No newline at end of file +348765 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInSingle.snap b/.forge-snapshots/RouterExactInSingle.snap index fc27e7d2..3a60ad66 100644 --- a/.forge-snapshots/RouterExactInSingle.snap +++ b/.forge-snapshots/RouterExactInSingle.snap @@ -1 +1 @@ -192093 \ No newline at end of file +192195 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap new file mode 100644 index 00000000..36daa7ec --- /dev/null +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -0,0 +1 @@ +193514 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap new file mode 100644 index 00000000..b69e7ae1 --- /dev/null +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -0,0 +1 @@ +271891 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap new file mode 100644 index 00000000..28db89fc --- /dev/null +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -0,0 +1 @@ +350296 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 9c7faa76..c5161060 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -19,6 +19,7 @@ abstract contract Routing { error NotPoolManager(); error InvalidSwapType(); error TooLittleReceived(); + error TooMuchRequested(); struct SwapInfo { SwapType swapType; @@ -27,7 +28,7 @@ abstract contract Routing { } struct PathKey { - Currency currencyOut; + Currency tradeCurrency; uint24 fee; int24 tickSpacing; IHooks hooks; @@ -50,6 +51,14 @@ abstract contract Routing { uint128 amountOutMinimum; } + struct ExactOutputParams { + Currency currencyOut; + PathKey[] path; + address recipient; + uint128 amountOut; + uint128 amountInMaximum; + } + enum SwapType { ExactInput, ExactInputSingle, @@ -78,6 +87,8 @@ abstract contract Routing { _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactInputSingle) { _swapExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams)), swapInfo.msgSender); + } else if (swapInfo.swapType == SwapType.ExactOutput) { + _swapExactOutput(abi.decode(swapInfo.params, (ExactOutputParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -86,45 +97,76 @@ abstract contract Routing { } function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private { - _swapExactInputPrivate( - params.poolKey, params.zeroForOne, params.amountIn, params.sqrtPriceLimitX96, msgSender, true, true + _swapExactPrivate( + params.poolKey, + params.zeroForOne, + int256(int128(params.amountIn)), + params.sqrtPriceLimitX96, + msgSender, + true, + true ); } function _swapExactInput(ExactInputParams memory params, address msgSender) private { for (uint256 i = 0; i < params.path.length; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); - uint128 amountOut = _swapExactInputPrivate( - poolKey, - zeroForOne, - params.amountIn, - 0, - msgSender, - i == 0, - i == params.path.length - 1 + uint128 amountOut = uint128( + -_swapExactPrivate( + poolKey, + zeroForOne, + int256(int128(params.amountIn)), + 0, + msgSender, + i == 0, + i == params.path.length - 1 + ) ); params.amountIn = amountOut; - params.currencyIn = params.path[i].currencyOut; + params.currencyIn = params.path[i].tradeCurrency; } if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } - function _swapExactInputPrivate( + function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { + for (uint256 i = params.path.length; i > 0; i--) { + (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); + uint128 amountIn = uint128( + -_swapExactPrivate( + poolKey, + !oneForZero, + -int256(int128(params.amountOut)), + 0, + msgSender, + i == 1, + i == params.path.length + ) + ); + + // console.log(amountIn); + + params.amountOut = amountIn; + params.currencyOut = params.path[i - 1].tradeCurrency; + } + if (params.amountOut > params.amountInMaximum) revert TooMuchRequested(); + } + + function _swapExactPrivate( PoolKey memory poolKey, bool zeroForOne, - uint128 amountIn, + int256 amountSpecified, uint160 sqrtPriceLimitX96, address msgSender, bool settle, bool take - ) private returns (uint128 amountOut) { + ) private returns (int128 reciprocalAmount) { BalanceDelta delta = poolManager.swap( poolKey, IPoolManager.SwapParams( zeroForOne, - int256(int128(amountIn)), + amountSpecified, sqrtPriceLimitX96 == 0 ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) : sqrtPriceLimitX96 @@ -133,13 +175,13 @@ abstract contract Routing { ); if (zeroForOne) { - amountOut = uint128(-delta.amount1()); + reciprocalAmount = amountSpecified > 0 ? delta.amount1() : -delta.amount0(); if (settle) _payAndSettle(poolKey.currency0, msgSender, delta.amount0()); - if (take) poolManager.take(poolKey.currency1, msgSender, uint256(amountOut)); + if (take) poolManager.take(poolKey.currency1, msgSender, uint128(-delta.amount1())); } else { - amountOut = uint128(-delta.amount0()); + reciprocalAmount = amountSpecified > 0 ? delta.amount0() : -delta.amount1(); if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); - if (take) poolManager.take(poolKey.currency0, msgSender, uint256(amountOut)); + if (take) poolManager.take(poolKey.currency0, msgSender, uint128(-delta.amount0())); } } @@ -149,7 +191,7 @@ abstract contract Routing { returns (PoolKey memory poolKey, bool zeroForOne) { (Currency currency0, Currency currency1) = - currencyIn < params.currencyOut ? (currencyIn, params.currencyOut) : (params.currencyOut, currencyIn); + currencyIn < params.tradeCurrency ? (currencyIn, params.tradeCurrency) : (params.tradeCurrency, currencyIn); zeroForOne = currencyIn == currency0; poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 589ce4ad..0918eeaf 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import "forge-std/console.sol"; import {Test} from "forge-std/Test.sol"; import {Deployers} from "@uniswap/v4-core/test/foundry-tests/utils/Deployers.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; @@ -198,6 +199,107 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(token3.balanceOf(address(router)), 0); } + function testRouter_swapExactOut_1Hop_zeroForOne() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(token0); + tokenPath.push(token1); + Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + snapStart("RouterExactOut1Hop"); + router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, expectedAmountIn); + assertEq(newBalance1 - prevBalance1, amountOut); + } + + function testRouter_swapExactOut_1Hop_oneForZero() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(token1); + tokenPath.push(token0); + Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + snapStart("RouterExactOut1Hop"); + router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance1 - newBalance1, expectedAmountIn); + assertEq(newBalance0 - prevBalance0, amountOut); + } + + function testRouter_swapExactOut_2Hops() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1016204441757464409; + + tokenPath.push(token0); + tokenPath.push(token1); + tokenPath.push(token2); + Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance2 = token2.balanceOf(address(this)); + + snapStart("RouterExactOut2Hops"); + router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance2 = token2.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, expectedAmountIn); + assertEq(prevBalance1 - newBalance1, 0); + assertEq(newBalance2 - prevBalance2, amountOut); + assertEq(token0.balanceOf(address(router)), 0); + assertEq(token1.balanceOf(address(router)), 0); + assertEq(token2.balanceOf(address(router)), 0); + } + + function testRouter_swapExactOut_3Hops() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1024467570922834110; + + tokenPath.push(token0); + tokenPath.push(token1); + tokenPath.push(token2); + tokenPath.push(token3); + Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance3 = token3.balanceOf(address(this)); + + snapStart("RouterExactOut3Hops"); + router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance3 = token3.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, expectedAmountIn); + assertEq(newBalance3 - prevBalance3, amountOut); + assertEq(token0.balanceOf(address(router)), 0); + assertEq(token1.balanceOf(address(router)), 0); + assertEq(token2.balanceOf(address(router)), 0); + assertEq(token3.balanceOf(address(router)), 0); + } + function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) 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(address(0))); @@ -230,4 +332,21 @@ contract RoutingTest is Test, Deployers, GasSnapshot { params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; } + + function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) + internal + view + returns (Routing.ExactOutputParams memory params) + { + Routing.PathKey[] memory path = new Routing.PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = Routing.PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0))); + } + + params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.path = path; + params.recipient = address(this); + params.amountOut = uint128(amountOut); + params.amountInMaximum = type(uint128).max; + } } From 607ea3bf4382dbd7f2607c895243adbb7018edc2 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 16:12:50 -0400 Subject: [PATCH 12/19] gas savings from not doing double negative number --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- contracts/Routing.sol | 9 ++++----- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 640a57a5..fbbc587a 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4855 \ No newline at end of file +4829 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 36daa7ec..ab704c89 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -193514 \ No newline at end of file +187374 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index b69e7ae1..b3d510d5 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -271891 \ No newline at end of file +271562 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 28db89fc..eeb3e65d 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -350296 \ No newline at end of file +349804 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index c5161060..91b6445a 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import "forge-std/console.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; @@ -134,7 +135,7 @@ abstract contract Routing { for (uint256 i = params.path.length; i > 0; i--) { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); uint128 amountIn = uint128( - -_swapExactPrivate( + _swapExactPrivate( poolKey, !oneForZero, -int256(int128(params.amountOut)), @@ -145,8 +146,6 @@ abstract contract Routing { ) ); - // console.log(amountIn); - params.amountOut = amountIn; params.currencyOut = params.path[i - 1].tradeCurrency; } @@ -175,11 +174,11 @@ abstract contract Routing { ); if (zeroForOne) { - reciprocalAmount = amountSpecified > 0 ? delta.amount1() : -delta.amount0(); + reciprocalAmount = amountSpecified > 0 ? delta.amount1() : delta.amount0(); if (settle) _payAndSettle(poolKey.currency0, msgSender, delta.amount0()); if (take) poolManager.take(poolKey.currency1, msgSender, uint128(-delta.amount1())); } else { - reciprocalAmount = amountSpecified > 0 ? delta.amount0() : -delta.amount1(); + reciprocalAmount = amountSpecified > 0 ? delta.amount0() : delta.amount1(); if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); if (take) poolManager.take(poolKey.currency0, msgSender, uint128(-delta.amount0())); } From cf825272053e321a3d5e451e7c50e6e65aa36bdf Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 16:19:14 -0400 Subject: [PATCH 13/19] gas savings from unchecked math --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInSingle.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- contracts/Routing.sol | 74 ++++++++++++----------- 9 files changed, 47 insertions(+), 43 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index fbbc587a..a44cea40 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4829 \ No newline at end of file +4676 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 02ca69ef..129f55e9 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -194162 \ No newline at end of file +193938 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 0bb7aa9d..4d93b0d6 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271461 \ No newline at end of file +271014 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index ae20bc3c..42b6e94b 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348765 \ No newline at end of file +348095 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInSingle.snap b/.forge-snapshots/RouterExactInSingle.snap index 3a60ad66..e4929481 100644 --- a/.forge-snapshots/RouterExactInSingle.snap +++ b/.forge-snapshots/RouterExactInSingle.snap @@ -1 +1 @@ -192195 \ No newline at end of file +192194 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index ab704c89..86fc2330 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -187374 \ No newline at end of file +193086 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index b3d510d5..d508b257 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -271562 \ No newline at end of file +271039 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index eeb3e65d..0bc57d45 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -349804 \ No newline at end of file +349020 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 91b6445a..52b07410 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -110,46 +110,50 @@ abstract contract Routing { } function _swapExactInput(ExactInputParams memory params, address msgSender) private { - for (uint256 i = 0; i < params.path.length; i++) { - (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); - uint128 amountOut = uint128( - -_swapExactPrivate( - poolKey, - zeroForOne, - int256(int128(params.amountIn)), - 0, - msgSender, - i == 0, - i == params.path.length - 1 - ) - ); - - params.amountIn = amountOut; - params.currencyIn = params.path[i].tradeCurrency; + unchecked { + for (uint256 i = 0; i < params.path.length; i++) { + (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); + uint128 amountOut = uint128( + -_swapExactPrivate( + poolKey, + zeroForOne, + int256(int128(params.amountIn)), + 0, + msgSender, + i == 0, + i == params.path.length - 1 + ) + ); + + params.amountIn = amountOut; + params.currencyIn = params.path[i].tradeCurrency; + } + + if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } - - if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); } function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { - for (uint256 i = params.path.length; i > 0; i--) { - (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); - uint128 amountIn = uint128( - _swapExactPrivate( - poolKey, - !oneForZero, - -int256(int128(params.amountOut)), - 0, - msgSender, - i == 1, - i == params.path.length - ) - ); - - params.amountOut = amountIn; - params.currencyOut = params.path[i - 1].tradeCurrency; + unchecked { + for (uint256 i = params.path.length; i > 0; i--) { + (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); + uint128 amountIn = uint128( + _swapExactPrivate( + poolKey, + !oneForZero, + -int256(int128(params.amountOut)), + 0, + msgSender, + i == 1, + i == params.path.length + ) + ); + + params.amountOut = amountIn; + params.currencyOut = params.path[i - 1].tradeCurrency; + } + if (params.amountOut > params.amountInMaximum) revert TooMuchRequested(); } - if (params.amountOut > params.amountInMaximum) revert TooMuchRequested(); } function _swapExactPrivate( From 9ebc9d536a9072569a5bbbd907861ee967fdef9f Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 16:56:32 -0400 Subject: [PATCH 14/19] add swapExactOuputSingle --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInSingle.snap | 1 - .forge-snapshots/RouterExactInputSingle.snap | 1 + .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- .forge-snapshots/RouterExactOutputSingle.snap | 1 + contracts/Routing.sol | 26 ++++++++++- test/Routing.t.sol | 46 +++++++++++++++++-- 12 files changed, 77 insertions(+), 12 deletions(-) delete mode 100644 .forge-snapshots/RouterExactInSingle.snap create mode 100644 .forge-snapshots/RouterExactInputSingle.snap create mode 100644 .forge-snapshots/RouterExactOutputSingle.snap diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index a44cea40..30871528 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4676 \ No newline at end of file +4838 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 129f55e9..50f9feba 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -193938 \ No newline at end of file +193914 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 4d93b0d6..7d138d1b 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -271014 \ No newline at end of file +270990 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 42b6e94b..bddc160b 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348095 \ No newline at end of file +348071 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInSingle.snap b/.forge-snapshots/RouterExactInSingle.snap deleted file mode 100644 index e4929481..00000000 --- a/.forge-snapshots/RouterExactInSingle.snap +++ /dev/null @@ -1 +0,0 @@ -192194 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap new file mode 100644 index 00000000..f6ecef3e --- /dev/null +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -0,0 +1 @@ +192342 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 86fc2330..a892a155 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -193086 \ No newline at end of file +193062 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index d508b257..c97dd76c 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -271039 \ No newline at end of file +271015 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 0bc57d45..6b00873a 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -349020 \ No newline at end of file +348996 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap new file mode 100644 index 00000000..4ed123bc --- /dev/null +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -0,0 +1 @@ +191562 \ No newline at end of file diff --git a/contracts/Routing.sol b/contracts/Routing.sol index 52b07410..45050509 100644 --- a/contracts/Routing.sol +++ b/contracts/Routing.sol @@ -52,6 +52,15 @@ abstract contract Routing { uint128 amountOutMinimum; } + struct ExactOutputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountOut; + uint128 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + struct ExactOutputParams { Currency currencyOut; PathKey[] path; @@ -90,6 +99,8 @@ abstract contract Routing { _swapExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactOutput) { _swapExactOutput(abi.decode(swapInfo.params, (ExactOutputParams)), swapInfo.msgSender); + } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { + _swapExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -133,10 +144,23 @@ abstract contract Routing { } } + function _swapExactOutputSingle(ExactOutputSingleParams memory params, address msgSender) private { + _swapExactPrivate( + params.poolKey, + params.zeroForOne, + -int256(int128(params.amountOut)), + params.sqrtPriceLimitX96, + msgSender, + true, + true + ); + } + function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { unchecked { for (uint256 i = params.path.length; i > 0; i--) { - (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); + (PoolKey memory poolKey, bool oneForZero) = + _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); uint128 amountIn = uint128( _swapExactPrivate( poolKey, diff --git a/test/Routing.t.sol b/test/Routing.t.sol index 0918eeaf..739b2680 100644 --- a/test/Routing.t.sol +++ b/test/Routing.t.sol @@ -61,7 +61,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { snapSize("RouterBytecode", address(router)); } - function testRouter_swapExactInSingle_zeroForOne() public { + function testRouter_swapExactInputSingle_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -71,7 +71,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - snapStart("RouterExactInSingle"); + snapStart("RouterExactInputSingle"); router.swap(Routing.SwapType.ExactInputSingle, abi.encode(params)); snapEnd(); @@ -82,7 +82,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, expectedAmountOut); } - function testRouter_swapExactInSingle_oneForZero() public { + function testRouter_swapExactInputSingle_oneForZero() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -199,6 +199,46 @@ contract RoutingTest is Test, Deployers, GasSnapshot { assertEq(token3.balanceOf(address(router)), 0); } + function testRouter_swapExactOutputSingle_zeroForOne() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + Routing.ExactOutputSingleParams memory params = + Routing.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + snapStart("RouterExactOutputSingle"); + router.swap(Routing.SwapType.ExactOutputSingle, abi.encode(params)); + snapEnd(); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance0 - newBalance0, expectedAmountIn); + assertEq(newBalance1 - prevBalance1, amountOut); + } + + function testRouter_swapExactOutputSingle_oneForZero() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + Routing.ExactOutputSingleParams memory params = + Routing.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0); + + uint256 prevBalance0 = token0.balanceOf(address(this)); + uint256 prevBalance1 = token1.balanceOf(address(this)); + + router.swap(Routing.SwapType.ExactOutputSingle, abi.encode(params)); + + uint256 newBalance0 = token0.balanceOf(address(this)); + uint256 newBalance1 = token1.balanceOf(address(this)); + + assertEq(prevBalance1 - newBalance1, expectedAmountIn); + assertEq(newBalance0 - prevBalance0, amountOut); + } + function testRouter_swapExactOut_1Hop_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; From 54a48e5897889d9018ee3cefc5dd87cd7da02426 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 5 Sep 2023 17:11:10 -0400 Subject: [PATCH 15/19] break out structs into interface --- contracts/{Routing.sol => V4Router.sol} | 63 +-------------- contracts/interfaces/IV4Router.sol | 76 ++++++++++++++++++ test/{Routing.t.sol => V4Router.t.sol} | 79 ++++++++++--------- ...ntation.sol => V4RouterImplementation.sol} | 6 +- 4 files changed, 121 insertions(+), 103 deletions(-) rename contracts/{Routing.sol => V4Router.sol} (83%) create mode 100644 contracts/interfaces/IV4Router.sol rename test/{Routing.t.sol => V4Router.t.sol} (80%) rename test/shared/implementation/{RoutingImplementation.sol => V4RouterImplementation.sol} (75%) diff --git a/contracts/Routing.sol b/contracts/V4Router.sol similarity index 83% rename from contracts/Routing.sol rename to contracts/V4Router.sol index 45050509..cafadde1 100644 --- a/contracts/Routing.sol +++ b/contracts/V4Router.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import "forge-std/console.sol"; import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol"; @@ -9,73 +8,15 @@ import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; +import {IV4Router} from "./interfaces/IV4Router.sol"; /// @title UniswapV4Routing /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -abstract contract Routing { +abstract contract V4Router is IV4Router { using CurrencyLibrary for Currency; IPoolManager immutable poolManager; - error NotPoolManager(); - error InvalidSwapType(); - error TooLittleReceived(); - error TooMuchRequested(); - - struct SwapInfo { - SwapType swapType; - address msgSender; - bytes params; - } - - struct PathKey { - Currency tradeCurrency; - uint24 fee; - int24 tickSpacing; - IHooks hooks; - } - - struct ExactInputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountIn; - uint128 amountOutMinimum; - uint160 sqrtPriceLimitX96; - } - - struct ExactInputParams { - Currency currencyIn; - PathKey[] path; - address recipient; - uint128 amountIn; - uint128 amountOutMinimum; - } - - struct ExactOutputSingleParams { - PoolKey poolKey; - bool zeroForOne; - address recipient; - uint128 amountOut; - uint128 amountInMaximum; - uint160 sqrtPriceLimitX96; - } - - struct ExactOutputParams { - Currency currencyOut; - PathKey[] path; - address recipient; - uint128 amountOut; - uint128 amountInMaximum; - } - - enum SwapType { - ExactInput, - ExactInputSingle, - ExactOutput, - ExactOutputSingle - } - /// @dev Only the pool manager may call this function modifier poolManagerOnly() { if (msg.sender != address(poolManager)) revert NotPoolManager(); diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol new file mode 100644 index 00000000..186d92ca --- /dev/null +++ b/contracts/interfaces/IV4Router.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import "forge-std/console.sol"; +import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.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/contracts/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; + +/// @title UniswapV4Routing +/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools +interface IV4Router { + error NotPoolManager(); + error InvalidSwapType(); + error TooLittleReceived(); + error TooMuchRequested(); + + struct SwapInfo { + SwapType swapType; + address msgSender; + bytes params; + } + + struct PathKey { + Currency tradeCurrency; + uint24 fee; + int24 tickSpacing; + IHooks hooks; + } + + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountIn; + uint128 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + struct ExactInputParams { + Currency currencyIn; + PathKey[] path; + address recipient; + uint128 amountIn; + uint128 amountOutMinimum; + } + + struct ExactOutputSingleParams { + PoolKey poolKey; + bool zeroForOne; + address recipient; + uint128 amountOut; + uint128 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + struct ExactOutputParams { + Currency currencyOut; + PathKey[] path; + address recipient; + uint128 amountOut; + uint128 amountInMaximum; + } + + enum SwapType { + ExactInput, + ExactInputSingle, + ExactOutput, + ExactOutputSingle + } + + function lockAcquired(bytes calldata encodedSwapInfo) external returns (bytes memory); +} diff --git a/test/Routing.t.sol b/test/V4Router.t.sol similarity index 80% rename from test/Routing.t.sol rename to test/V4Router.t.sol index 739b2680..bb8eba5d 100644 --- a/test/Routing.t.sol +++ b/test/V4Router.t.sol @@ -9,18 +9,19 @@ import {MockERC20} from "@uniswap/v4-core/test/foundry-tests/utils/MockERC20.sol import {PoolModifyPositionTest} from "@uniswap/v4-core/contracts/test/PoolModifyPositionTest.sol"; import {PoolManager} from "@uniswap/v4-core/contracts/PoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; -import {Routing} from "../contracts/Routing.sol"; -import {RoutingImplementation} from "./shared/implementation/RoutingImplementation.sol"; +import {V4Router} from "../contracts/V4Router.sol"; +import {IV4Router} from "../contracts/interfaces/IV4Router.sol"; +import {V4RouterImplementation} from "./shared/implementation/V4RouterImplementation.sol"; import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol"; -contract RoutingTest is Test, Deployers, GasSnapshot { +contract V4RouterTest is Test, Deployers, GasSnapshot { using CurrencyLibrary for Currency; PoolManager manager; PoolModifyPositionTest positionManager; - RoutingImplementation router; + V4RouterImplementation router; MockERC20 token0; MockERC20 token1; @@ -35,7 +36,7 @@ contract RoutingTest is Test, Deployers, GasSnapshot { function setUp() public { manager = new PoolManager(500000); - router = new RoutingImplementation(manager); + router = new V4RouterImplementation(manager); positionManager = new PoolModifyPositionTest(manager); token0 = new MockERC20("Test0", "0", 18, 2 ** 128); @@ -65,14 +66,14 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - Routing.ExactInputSingleParams memory params = - Routing.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0); + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactInputSingle"); - router.swap(Routing.SwapType.ExactInputSingle, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -86,13 +87,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - Routing.ExactInputSingleParams memory params = - Routing.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0); + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - router.swap(Routing.SwapType.ExactInputSingle, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); uint256 newBalance0 = token0.balanceOf(address(this)); uint256 newBalance1 = token1.balanceOf(address(this)); @@ -107,13 +108,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); tokenPath.push(token1); - Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactIn1Hop"); - router.swap(Routing.SwapType.ExactInput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -129,11 +130,11 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); tokenPath.push(token0); - Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - router.swap(Routing.SwapType.ExactInput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); uint256 newBalance0 = token0.balanceOf(address(this)); uint256 newBalance1 = token1.balanceOf(address(this)); @@ -149,14 +150,14 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); tokenPath.push(token1); tokenPath.push(token2); - Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); uint256 prevBalance2 = token2.balanceOf(address(this)); snapStart("RouterExactIn2Hops"); - router.swap(Routing.SwapType.ExactInput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -179,13 +180,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); tokenPath.push(token2); tokenPath.push(token3); - Routing.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance3 = token3.balanceOf(address(this)); snapStart("RouterExactIn3Hops"); - router.swap(Routing.SwapType.ExactInput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -203,14 +204,14 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - Routing.ExactOutputSingleParams memory params = - Routing.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0); + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactOutputSingle"); - router.swap(Routing.SwapType.ExactOutputSingle, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -224,13 +225,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - Routing.ExactOutputSingleParams memory params = - Routing.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0); + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); - router.swap(Routing.SwapType.ExactOutputSingle, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); uint256 newBalance0 = token0.balanceOf(address(this)); uint256 newBalance1 = token1.balanceOf(address(this)); @@ -245,13 +246,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); tokenPath.push(token1); - Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactOut1Hop"); - router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -267,13 +268,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); tokenPath.push(token0); - Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); snapStart("RouterExactOut1Hop"); - router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -290,14 +291,14 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token0); tokenPath.push(token1); tokenPath.push(token2); - Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); uint256 prevBalance2 = token2.balanceOf(address(this)); snapStart("RouterExactOut2Hops"); - router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -320,13 +321,13 @@ contract RoutingTest is Test, Deployers, GasSnapshot { tokenPath.push(token1); tokenPath.push(token2); tokenPath.push(token3); - Routing.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance3 = token3.balanceOf(address(this)); snapStart("RouterExactOut3Hops"); - router.swap(Routing.SwapType.ExactOutput, abi.encode(params)); + router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); snapEnd(); uint256 newBalance0 = token0.balanceOf(address(this)); @@ -359,11 +360,11 @@ contract RoutingTest is Test, Deployers, GasSnapshot { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal view - returns (Routing.ExactInputParams memory params) + returns (IV4Router.ExactInputParams memory params) { - Routing.PathKey[] memory path = new Routing.PathKey[](_tokenPath.length - 1); + IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = Routing.PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0))); + path[i] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0))); } params.currencyIn = Currency.wrap(address(_tokenPath[0])); @@ -376,11 +377,11 @@ contract RoutingTest is Test, Deployers, GasSnapshot { function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal view - returns (Routing.ExactOutputParams memory params) + returns (IV4Router.ExactOutputParams memory params) { - Routing.PathKey[] memory path = new Routing.PathKey[](_tokenPath.length - 1); + IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = Routing.PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0))); + path[i - 1] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0))); } params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); diff --git a/test/shared/implementation/RoutingImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol similarity index 75% rename from test/shared/implementation/RoutingImplementation.sol rename to test/shared/implementation/V4RouterImplementation.sol index f705db31..fb43b822 100644 --- a/test/shared/implementation/RoutingImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Routing} from "../../../contracts/Routing.sol"; +import {V4Router} from "../../../contracts/V4Router.sol"; import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol"; import {IERC20Minimal} from "@uniswap/v4-core/contracts/interfaces/external/IERC20Minimal.sol"; -contract RoutingImplementation is Routing { - constructor(IPoolManager _poolManager) Routing(_poolManager) {} +contract V4RouterImplementation is V4Router { + constructor(IPoolManager _poolManager) V4Router(_poolManager) {} function swap(SwapType swapType, bytes memory params) external { v4Swap(swapType, params); From 01a5db038e014657003f054b539fbcad86888146 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Mon, 25 Sep 2023 15:03:02 -0400 Subject: [PATCH 16/19] PR comments --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- contracts/V4Router.sol | 23 +++++++++++-------- contracts/interfaces/IV4Router.sol | 2 +- .../implementation/V4RouterImplementation.sol | 2 +- 10 files changed, 23 insertions(+), 18 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 30871528..b3bd10b7 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4838 \ No newline at end of file +4830 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 50f9feba..df6d09fc 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -193914 \ No newline at end of file +193895 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 7d138d1b..42b1ca5e 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -270990 \ No newline at end of file +270947 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index bddc160b..2de12e45 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348071 \ No newline at end of file +348004 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index a892a155..8061f77e 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -193062 \ No newline at end of file +193055 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index c97dd76c..cfeffe37 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -271015 \ No newline at end of file +270996 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 6b00873a..676f6a2f 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -348996 \ No newline at end of file +348965 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index cafadde1..5ea65038 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -27,7 +27,7 @@ abstract contract V4Router is IV4Router { poolManager = _poolManager; } - function v4Swap(SwapType swapType, bytes memory params) internal { + function _v4Swap(SwapType swapType, bytes memory params) internal { poolManager.lock(abi.encode(SwapInfo(swapType, msg.sender, params))); } @@ -63,7 +63,9 @@ abstract contract V4Router is IV4Router { function _swapExactInput(ExactInputParams memory params, address msgSender) private { unchecked { - for (uint256 i = 0; i < params.path.length; i++) { + uint256 pathLength = params.path.length; + + for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); uint128 amountOut = uint128( -_swapExactPrivate( @@ -73,12 +75,12 @@ abstract contract V4Router is IV4Router { 0, msgSender, i == 0, - i == params.path.length - 1 + i == pathLength - 1 ) ); params.amountIn = amountOut; - params.currencyIn = params.path[i].tradeCurrency; + params.currencyIn = params.path[i].intermediateCurrency; } if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); @@ -99,7 +101,9 @@ abstract contract V4Router is IV4Router { function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { unchecked { - for (uint256 i = params.path.length; i > 0; i--) { + uint256 pathLength = params.path.length; + + for (uint256 i = pathLength; i > 0; i--) { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); uint128 amountIn = uint128( @@ -110,12 +114,12 @@ abstract contract V4Router is IV4Router { 0, msgSender, i == 1, - i == params.path.length + i == pathLength ) ); params.amountOut = amountIn; - params.currencyOut = params.path[i - 1].tradeCurrency; + params.currencyOut = params.path[i - 1].intermediateCurrency; } if (params.amountOut > params.amountInMaximum) revert TooMuchRequested(); } @@ -158,8 +162,9 @@ abstract contract V4Router is IV4Router { pure returns (PoolKey memory poolKey, bool zeroForOne) { - (Currency currency0, Currency currency1) = - currencyIn < params.tradeCurrency ? (currencyIn, params.tradeCurrency) : (params.tradeCurrency, currencyIn); + (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/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index 186d92ca..ecdd2686 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -25,7 +25,7 @@ interface IV4Router { } struct PathKey { - Currency tradeCurrency; + Currency intermediateCurrency; uint24 fee; int24 tickSpacing; IHooks hooks; diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index fb43b822..a6f0564f 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -9,7 +9,7 @@ contract V4RouterImplementation is V4Router { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} function swap(SwapType swapType, bytes memory params) external { - v4Swap(swapType, params); + _v4Swap(swapType, params); } function _pay(address token, address payer, address recipient, uint256 amount) internal override { From 31cbfe5c4097186589fdd63c2db09b9317c97b39 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Tue, 26 Sep 2023 10:11:58 -0400 Subject: [PATCH 17/19] pass hook data along --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactInputSingle.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- .forge-snapshots/RouterExactOutputSingle.snap | 2 +- contracts/V4Router.sol | 17 +++++++++----- contracts/interfaces/IV4Router.sol | 3 +++ test/V4Router.t.sol | 22 +++++++++---------- 12 files changed, 34 insertions(+), 26 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index b3bd10b7..91dca67b 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -4830 \ No newline at end of file +5038 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index df6d09fc..ecaa36aa 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -193895 \ No newline at end of file +195455 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 42b1ca5e..10960142 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -270947 \ No newline at end of file +274012 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 2de12e45..d115b27f 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -348004 \ No newline at end of file +352580 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index f6ecef3e..e3f86b30 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -192342 \ No newline at end of file +193868 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 8061f77e..6c5c48dd 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -193055 \ No newline at end of file +194621 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index cfeffe37..b298698c 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -270996 \ No newline at end of file +274074 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 676f6a2f..9886747d 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -348965 \ No newline at end of file +353558 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 4ed123bc..edef0430 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -191562 \ No newline at end of file +193088 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 5ea65038..d3880c17 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -57,7 +57,8 @@ abstract contract V4Router is IV4Router { params.sqrtPriceLimitX96, msgSender, true, - true + true, + params.hookData ); } @@ -75,7 +76,8 @@ abstract contract V4Router is IV4Router { 0, msgSender, i == 0, - i == pathLength - 1 + i == pathLength - 1, + params.path[i].hookData ) ); @@ -95,7 +97,8 @@ abstract contract V4Router is IV4Router { params.sqrtPriceLimitX96, msgSender, true, - true + true, + params.hookData ); } @@ -114,7 +117,8 @@ abstract contract V4Router is IV4Router { 0, msgSender, i == 1, - i == pathLength + i == pathLength, + params.path[i - 1].hookData ) ); @@ -132,7 +136,8 @@ abstract contract V4Router is IV4Router { uint160 sqrtPriceLimitX96, address msgSender, bool settle, - bool take + bool take, + bytes memory hookData ) private returns (int128 reciprocalAmount) { BalanceDelta delta = poolManager.swap( poolKey, @@ -143,7 +148,7 @@ abstract contract V4Router is IV4Router { ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) : sqrtPriceLimitX96 ), - bytes("") + hookData ); if (zeroForOne) { diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index ecdd2686..27ff3b62 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -29,6 +29,7 @@ interface IV4Router { uint24 fee; int24 tickSpacing; IHooks hooks; + bytes hookData; } struct ExactInputSingleParams { @@ -38,6 +39,7 @@ interface IV4Router { uint128 amountIn; uint128 amountOutMinimum; uint160 sqrtPriceLimitX96; + bytes hookData; } struct ExactInputParams { @@ -55,6 +57,7 @@ interface IV4Router { uint128 amountOut; uint128 amountInMaximum; uint160 sqrtPriceLimitX96; + bytes hookData; } struct ExactOutputParams { diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index bb8eba5d..8279c96f 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -44,9 +44,9 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { token2 = new MockERC20("Test2", "2", 18, 2 ** 128); token3 = new MockERC20("Test3", "3", 18, 2 ** 128); - key0 = createPoolKey(token0, token1); - key1 = createPoolKey(token1, token2); - key2 = createPoolKey(token2, token3); + key0 = createPoolKey(token0, token1, address(0)); + key1 = createPoolKey(token1, token2, address(0)); + key2 = createPoolKey(token2, token3, address(0)); setupPool(key0); setupPool(key1); @@ -67,7 +67,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0); + IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); @@ -88,7 +88,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0); + IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); @@ -205,7 +205,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0); + IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); @@ -226,7 +226,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0); + IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = token0.balanceOf(address(this)); uint256 prevBalance1 = token1.balanceOf(address(this)); @@ -341,9 +341,9 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(token3.balanceOf(address(router)), 0); } - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB) internal pure returns (PoolKey memory) { + 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(address(0))); + return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); } function setupPool(PoolKey memory poolKey) internal { @@ -364,7 +364,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { { IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0))); + path[i] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); } params.currencyIn = Currency.wrap(address(_tokenPath[0])); @@ -381,7 +381,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { { IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0))); + path[i - 1] = IV4Router.PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); } params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); From d50f18d1b01cbe2d23e5d4d00afeb85c29cd645f Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Sun, 1 Oct 2023 21:03:15 -0400 Subject: [PATCH 18/19] gas and coherency optimization --- .forge-snapshots/RouterBytecode.snap | 2 +- .forge-snapshots/RouterExactIn1Hop.snap | 2 +- .forge-snapshots/RouterExactIn2Hops.snap | 2 +- .forge-snapshots/RouterExactIn3Hops.snap | 2 +- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- .forge-snapshots/RouterExactOut2Hops.snap | 2 +- .forge-snapshots/RouterExactOut3Hops.snap | 2 +- contracts/V4Router.sol | 10 ++++++---- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 91dca67b..0e5f6514 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5038 \ No newline at end of file +5027 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index ecaa36aa..1b93dc81 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -195455 \ No newline at end of file +195446 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 10960142..ec40998c 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -274012 \ No newline at end of file +273998 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index d115b27f..ae31ba59 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -352580 \ No newline at end of file +352561 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 6c5c48dd..3b2d8334 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -194621 \ No newline at end of file +194612 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index b298698c..ac992810 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -274074 \ No newline at end of file +274060 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 9886747d..341c7aa3 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -353558 \ No newline at end of file +353539 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index d3880c17..8c72c0c2 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -65,10 +65,11 @@ abstract contract V4Router is IV4Router { function _swapExactInput(ExactInputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; + uint128 amountOut; for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); - uint128 amountOut = uint128( + amountOut = uint128( -_swapExactPrivate( poolKey, zeroForOne, @@ -85,7 +86,7 @@ abstract contract V4Router is IV4Router { params.currencyIn = params.path[i].intermediateCurrency; } - if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived(); + if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } } @@ -105,11 +106,12 @@ abstract contract V4Router is IV4Router { function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; + uint128 amountIn; for (uint256 i = pathLength; i > 0; i--) { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); - uint128 amountIn = uint128( + amountIn = uint128( _swapExactPrivate( poolKey, !oneForZero, @@ -125,7 +127,7 @@ abstract contract V4Router is IV4Router { params.amountOut = amountIn; params.currencyOut = params.path[i - 1].intermediateCurrency; } - if (params.amountOut > params.amountInMaximum) revert TooMuchRequested(); + if (amountIn > params.amountInMaximum) revert TooMuchRequested(); } } From 4b010ff68485fea1e70b5dc811d8e9a1bc9976de Mon Sep 17 00:00:00 2001 From: Diana Kocsis Date: Tue, 19 Mar 2024 13:50:47 -0400 Subject: [PATCH 19/19] updated lib/v4-core submodule to main branch --- lib/v4-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/v4-core b/lib/v4-core index 73938802..89e7b9d4 160000 --- a/lib/v4-core +++ b/lib/v4-core @@ -1 +1 @@ -Subproject commit 73938802cad600beb07bd805cc9883e25bf87261 +Subproject commit 89e7b9d466279d459aa503c2620a66462d1c0ceb