From b6f8d3b42a249127a97fa196ce1537fcc73655b2 Mon Sep 17 00:00:00 2001 From: Emily Williams Date: Thu, 17 Aug 2023 10:16:59 -0400 Subject: [PATCH 01/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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 From aa148b367d384a4d38cd02e86c73edf70dca2045 Mon Sep 17 00:00:00 2001 From: Zach Yang Date: Fri, 28 Jun 2024 05:48:50 -0700 Subject: [PATCH 20/50] feat: abstract router (#86) * add PoolTicksCounter library * quoter exact input single * quoter test * return deltas instead * safe casting to correct types * QuoteExactInput skeleton * multiple entries * break handleRevert by type * quoteExactInput and unit tests * more QuoteExactInput tests * remove lgos * remove commented out struct * via-ir in ci * remove unused imports/functions * store iteration params locally instead of editing function input * pull out sqrtPriceLimit to its own function * PathKey to its own library * rename initializedTicksCrossed to initializedTicksLoaded * remove manual abi encoding in yul :p * fix linter warnings for Quoter * natspec for IQuoter * feat: update v4-core This commit updates v4 core to latest and fixes integration issues * fix: tests * style fixes * inheritdoc * ExactInSingleBatch * fix: update tests * fix: test router was borked * exact out * fix: alice comments * fix ExactOutput * add ExactOput unit tests * add quoteExactOutputBatch * remove solhint config * remove newline * add QuoteExactOutput in interface * refactor lockAcquired * move magic numbers to constants + doc * add more natspec * natspec * named imports * self-call branching * remove old code * remove console2 import * refactor PathKeyLib * amountOutCached * inherit ILockCallback * add base contracts and interfaces (#75) * remove unused errors * test lockAcquired reverts * remove ...Batch interface * REASON -> RESPONSE when valid * complete natspec * remove SwapInfo imports * rename to SwapParameters * move quoter structs into IQuoter interface * update to latest core * use prev values * change twamm to use pool getters * changes after merging main * use --via-ir in cli * fix formatting * fix FullRange/TWAMM hook * update ticks counter * update Quoter test * typo * typo * simplify handleRevertSingle * merge QuoteInput/OutputSingle structs * combine IQuoter structs * using ... ordering * update snapshots * start routing * start routing contract * naive swapExactIn impl * lint + bytecode snapshot * change concept of hops to token hops * UniswapV4Routing --> Routing * use PathKey * exactInputSingle * save DRY progress * no sqrtPriceLimit for multipool hops * exactOut implemented w awkward loops/int conversions single hops passing on exactOut * gas savings from not doing double negative number * gas savings from unchecked math * add swapExactOuputSingle * break out structs into interface * PR comments * pass hook data along * gas and coherency optimization * merge Quoter * remove SwapIntention * IV4Router structs * remove SwapIntention * remove logs * natspec * rebase onto latest core * simplify function naming * Routing diana (#104) * feat: Revert style quoter (#73) * add PoolTicksCounter library * quoter exact input single * quoter test * return deltas instead * safe casting to correct types * QuoteExactInput skeleton * multiple entries * break handleRevert by type * quoteExactInput and unit tests * more QuoteExactInput tests * remove lgos * remove commented out struct * via-ir in ci * remove unused imports/functions * store iteration params locally instead of editing function input * pull out sqrtPriceLimit to its own function * PathKey to its own library * rename initializedTicksCrossed to initializedTicksLoaded * remove manual abi encoding in yul :p * fix linter warnings for Quoter * natspec for IQuoter * feat: update v4-core This commit updates v4 core to latest and fixes integration issues * fix: tests * style fixes * inheritdoc * ExactInSingleBatch * fix: update tests * fix: test router was borked * exact out * fix: alice comments * fix ExactOutput * add ExactOput unit tests * add quoteExactOutputBatch * remove solhint config * remove newline * add QuoteExactOutput in interface * refactor lockAcquired * move magic numbers to constants + doc * add more natspec * natspec * named imports * self-call branching * remove old code * remove console2 import * refactor PathKeyLib * amountOutCached * inherit ILockCallback * add base contracts and interfaces (#75) * remove unused errors * test lockAcquired reverts * remove ...Batch interface * REASON -> RESPONSE when valid * complete natspec * remove SwapInfo imports * rename to SwapParameters * move quoter structs into IQuoter interface * update to latest core * use prev values * change twamm to use pool getters * changes after merging main * use --via-ir in cli * fix formatting * fix FullRange/TWAMM hook * update ticks counter * update Quoter test * typo * typo * simplify handleRevertSingle * merge QuoteInput/OutputSingle structs * combine IQuoter structs * using ... ordering * update snapshots * move amountOutCached into inner call * using PathKeyLib for PathKey * fix amountOutCached * remove console2 import * resurface revert reason * clean up validateRevert * update natsppec * remove unused --------- Co-authored-by: Mark Toda Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com> Co-authored-by: Sara Reynolds * (Quoter) Avoid IR (#93) * avoid stack too deep * pack local variables into structs; remove need for IR * reorg struct * snapshots * forge fmt * restore settings * remove IR * ensure tokens are ordered properly by using salts * gas snapshot * remove console logs * chore: update v4-core:latest (#89) * update v4-core * update to new liquidity hooks * forge fmt; reuse v4-core justfile * snapshots * rename getHooksCalls --> getHookPermissions * enforce permanent liquidity with beforeRemoveLiquidity * snapshot * update v4-core (again) * snapshots with new v4-core * v4-core:latest * pin 0.8.24 * merge in remote; regenerate snapshots * remove justfile * repin cancun * pin token addresses using vm.etch * snapshots * forge fmt * remove via-ir and custom solc from CI * test nit * Update v4-core submodule to use https (#97) Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> * chore: add semgrep (#94) * [Chore] Update v4-core:latest (#100) * Update v4-core * Update various examples, BaseHook, Quoter and tests * Remove nested locking for LimitOrder * Fix Quoter * update v4-core * fix: remove getLocker as its a bool now * update v4-core: flipped signs, push dynamic fees * fix: flip delta signs * flip delta signs * flip delta signs * flip delta signs * fix getSlot0 calls * snapshots * remove deadcode * remove unused param * update core * update for modifyLiquidity; misc doc updates * correct min int256 * allow for manual fee updates --------- Co-authored-by: saucepoint * changes with core update * fix casing * switch versions back * Updated lib/v4-core submodule to main branch * switch to 0.8.19 --------- Co-authored-by: Zach Yang Co-authored-by: Mark Toda Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com> Co-authored-by: Sara Reynolds Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> Co-authored-by: 0x57 Co-authored-by: mr-uniswap <144828035+mr-uniswap@users.noreply.github.com> Co-authored-by: 0x57 Co-authored-by: saucepoint Co-authored-by: Alice Henshaw * update imports --------- Co-authored-by: Mark Toda Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com> Co-authored-by: Sara Reynolds Co-authored-by: Emily Williams Co-authored-by: Alice Henshaw Co-authored-by: diana Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com> Co-authored-by: 0x57 Co-authored-by: mr-uniswap <144828035+mr-uniswap@users.noreply.github.com> Co-authored-by: 0x57 Co-authored-by: saucepoint --- .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 | 78 +++++++-------- contracts/hooks/examples/LimitOrder.sol | 2 +- contracts/interfaces/IPeripheryPayments.sol | 2 +- contracts/interfaces/IV4Router.sol | 29 ++---- test/FullRange.t.sol | 2 +- test/V4Router.t.sol | 95 +++++++++++-------- .../implementation/V4RouterImplementation.sol | 7 +- test/utils/HookEnabledSwapRouter.sol | 2 +- 17 files changed, 120 insertions(+), 115 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 0e5f6514..19b15e18 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5027 \ No newline at end of file +5925 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 1b93dc81..3bfc7e5a 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -195446 \ No newline at end of file +101468 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index ec40998c..6f6b2383 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -273998 \ No newline at end of file +160449 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index ae31ba59..3031f4e5 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -352561 \ No newline at end of file +212680 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index e3f86b30..495f97ed 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -193868 \ No newline at end of file +106771 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 3b2d8334..b08c4f7e 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -194612 \ No newline at end of file +102242 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index ac992810..f9da51bb 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -274060 \ No newline at end of file +159812 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 341c7aa3..7563d22f 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -353539 \ No newline at end of file +212732 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index edef0430..0a49af3a 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -193088 \ No newline at end of file +105380 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 8c72c0c2..49642064 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -1,47 +1,46 @@ // 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 {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"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PathKey} from "./libraries/PathKey.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; -/// @title UniswapV4Routing +/// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools abstract contract V4Router is IV4Router { using CurrencyLibrary for Currency; IPoolManager immutable poolManager; - /// @dev Only the pool manager may call this function - modifier poolManagerOnly() { - if (msg.sender != address(poolManager)) revert NotPoolManager(); - _; - } - constructor(IPoolManager _poolManager) { poolManager = _poolManager; } function _v4Swap(SwapType swapType, bytes memory params) internal { - poolManager.lock(abi.encode(SwapInfo(swapType, msg.sender, params))); + poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params))); } - function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly returns (bytes memory) { + /// @inheritdoc IUnlockCallback + function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) { + if (msg.sender != address(poolManager)) revert NotPoolManager(); + SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { - _swapExactInput(abi.decode(swapInfo.params, (ExactInputParams)), swapInfo.msgSender); + _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactInputSingle) { - _swapExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams)), swapInfo.msgSender); + _swapExactInputSingle(abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactOutput) { - _swapExactOutput(abi.decode(swapInfo.params, (ExactOutputParams)), swapInfo.msgSender); + _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - _swapExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams)), swapInfo.msgSender); + _swapExactOutputSingle(abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -49,11 +48,11 @@ abstract contract V4Router is IV4Router { return bytes(""); } - function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private { - _swapExactPrivate( + function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params, address msgSender) private { + _swap( params.poolKey, params.zeroForOne, - int256(int128(params.amountIn)), + int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, msgSender, true, @@ -62,7 +61,7 @@ abstract contract V4Router is IV4Router { ); } - function _swapExactInput(ExactInputParams memory params, address msgSender) private { + function _swapExactInput(IV4Router.ExactInputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; uint128 amountOut; @@ -70,10 +69,10 @@ abstract contract V4Router is IV4Router { for (uint256 i = 0; i < pathLength; i++) { (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); amountOut = uint128( - -_swapExactPrivate( + _swap( poolKey, zeroForOne, - int256(int128(params.amountIn)), + int256(-int128(params.amountIn)), 0, msgSender, i == 0, @@ -90,11 +89,11 @@ abstract contract V4Router is IV4Router { } } - function _swapExactOutputSingle(ExactOutputSingleParams memory params, address msgSender) private { - _swapExactPrivate( + function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params, address msgSender) private { + _swap( params.poolKey, params.zeroForOne, - -int256(int128(params.amountOut)), + int256(int128(params.amountOut)), params.sqrtPriceLimitX96, msgSender, true, @@ -103,7 +102,7 @@ abstract contract V4Router is IV4Router { ); } - function _swapExactOutput(ExactOutputParams memory params, address msgSender) private { + function _swapExactOutput(IV4Router.ExactOutputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; uint128 amountIn; @@ -112,10 +111,10 @@ abstract contract V4Router is IV4Router { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); amountIn = uint128( - _swapExactPrivate( + -_swap( poolKey, !oneForZero, - -int256(int128(params.amountOut)), + int256(int128(params.amountOut)), 0, msgSender, i == 1, @@ -131,7 +130,7 @@ abstract contract V4Router is IV4Router { } } - function _swapExactPrivate( + function _swap( PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, @@ -147,20 +146,20 @@ abstract contract V4Router is IV4Router { zeroForOne, amountSpecified, sqrtPriceLimitX96 == 0 - ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1) + ? (zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1) : sqrtPriceLimitX96 ), hookData ); 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())); + 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())); + if (take) poolManager.take(poolKey.currency0, msgSender, uint128(delta.amount0())); } } @@ -178,7 +177,8 @@ abstract contract V4Router is IV4Router { } function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { - _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(settleAmount))); + poolManager.sync(currency); + _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); poolManager.settle(currency); } diff --git a/contracts/hooks/examples/LimitOrder.sol b/contracts/hooks/examples/LimitOrder.sol index 2a8ca909..45ee8f3f 100644 --- a/contracts/hooks/examples/LimitOrder.sol +++ b/contracts/hooks/examples/LimitOrder.sol @@ -324,7 +324,7 @@ contract LimitOrder is BaseHook { ) external selfOnly returns (uint128 amount0Fee, uint128 amount1Fee) { int24 tickUpper = tickLower + key.tickSpacing; - // because `modifyPosition` includes not just principal value but also fees, we cannot allocate + // because `modifyLiquidity` includes not just principal value but also fees, we cannot allocate // the proceeds pro-rata. if we were to do so, users who have been in a limit order that's partially filled // could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees. // to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order. diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol index f3c24660..5fab998b 100644 --- a/contracts/interfaces/IPeripheryPayments.sol +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index 27ff3b62..387e2ff0 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -1,18 +1,19 @@ // 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"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PathKey} from "../libraries/PathKey.sol"; /// @title UniswapV4Routing /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -interface IV4Router { +interface IV4Router is IUnlockCallback { error NotPoolManager(); error InvalidSwapType(); error TooLittleReceived(); @@ -24,14 +25,6 @@ interface IV4Router { bytes params; } - struct PathKey { - Currency intermediateCurrency; - uint24 fee; - int24 tickSpacing; - IHooks hooks; - bytes hookData; - } - struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; @@ -74,6 +67,4 @@ interface IV4Router { ExactOutput, ExactOutputSingle } - - function lockAcquired(bytes calldata encodedSwapInfo) external returns (bytes memory); } diff --git a/test/FullRange.t.sol b/test/FullRange.t.sol index 5edec106..67c19a9f 100644 --- a/test/FullRange.t.sol +++ b/test/FullRange.t.sol @@ -36,7 +36,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot { int24 tickSpacing, IHooks hooks ); - event ModifyPosition( + event ModifyLiquidity( PoolId indexed poolId, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta ); event Swap( diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index 8279c96f..706d1712 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -1,26 +1,28 @@ // 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 {V4Router} from "../contracts/V4Router.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {IV4Router} from "../contracts/interfaces/IV4Router.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.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"; +import {PathKey} from "../contracts/libraries/PathKey.sol"; +import {UniswapV4ERC20} from "../contracts/libraries/UniswapV4ERC20.sol"; +import {HookEnabledSwapRouter} from "./utils/HookEnabledSwapRouter.sol"; contract V4RouterTest is Test, Deployers, GasSnapshot { using CurrencyLibrary for Currency; - PoolManager manager; - PoolModifyPositionTest positionManager; + PoolModifyLiquidityTest positionManager; V4RouterImplementation router; MockERC20 token0; @@ -35,14 +37,19 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { MockERC20[] tokenPath; function setUp() public { - manager = new PoolManager(500000); + deployFreshManagerAndRouters(); + router = new V4RouterImplementation(manager); - positionManager = new PoolModifyPositionTest(manager); + positionManager = new PoolModifyLiquidityTest(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); + token0 = new MockERC20("Test0", "0", 18); + token0.mint(address(this), 2 ** 128); + token1 = new MockERC20("Test1", "1", 18); + token1.mint(address(this), 2 ** 128); + token2 = new MockERC20("Test2", "2", 18); + token2.mint(address(this), 2 ** 128); + token3 = new MockERC20("Test3", "3", 18); + token3.mint(address(this), 2 ** 128); key0 = createPoolKey(token0, token1, address(0)); key1 = createPoolKey(token1, token2, address(0)); @@ -69,15 +76,15 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); snapStart("RouterExactInputSingle"); router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance0 - newBalance0, amountIn); assertEq(newBalance1 - prevBalance1, expectedAmountOut); @@ -90,13 +97,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance1 - newBalance1, amountIn); assertEq(newBalance0 - prevBalance0, expectedAmountOut); @@ -207,15 +214,15 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); snapStart("RouterExactOutputSingle"); router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance0 - newBalance0, expectedAmountIn); assertEq(newBalance1 - prevBalance1, amountOut); @@ -228,13 +235,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); + uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = key0.currency0.balanceOf(address(this)); + uint256 newBalance1 = key0.currency1.balanceOf(address(this)); assertEq(prevBalance1 - newBalance1, expectedAmountIn); assertEq(newBalance0 - prevBalance0, amountOut); @@ -341,16 +348,22 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(token3.balanceOf(address(router)), 0); } - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) 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(hookAddr)); } function setupPool(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_RATIO_1_1, ZERO_BYTES); + manager.initialize(poolKey, SQRT_PRICE_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)); + positionManager.modifyLiquidity( + poolKey, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether, 0), "0x" + ); } function toCurrency(MockERC20 token) internal pure returns (Currency) { @@ -362,9 +375,9 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { view returns (IV4Router.ExactInputParams memory params) { - IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); + PathKey[] memory path = new 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)), bytes("")); + path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); } params.currencyIn = Currency.wrap(address(_tokenPath[0])); @@ -379,9 +392,9 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { view returns (IV4Router.ExactOutputParams memory params) { - IV4Router.PathKey[] memory path = new IV4Router.PathKey[](_tokenPath.length - 1); + PathKey[] memory path = new 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)), bytes("")); + path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); } params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index a6f0564f..aac7fde4 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; +import {IV4Router} from "../../../contracts/interfaces/IV4Router.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 V4RouterImplementation is V4Router { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} - function swap(SwapType swapType, bytes memory params) external { + function swap(IV4Router.SwapType swapType, bytes memory params) external { _v4Swap(swapType, params); } diff --git a/test/utils/HookEnabledSwapRouter.sol b/test/utils/HookEnabledSwapRouter.sol index 4021f453..d414e4b4 100644 --- a/test/utils/HookEnabledSwapRouter.sol +++ b/test/utils/HookEnabledSwapRouter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; +pragma solidity ^0.8.19; import {CurrencyLibrary, Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; From 1586c0dbc6a56ebb2284976910bfe1cd066d3735 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 2 Jul 2024 17:24:37 +0100 Subject: [PATCH 21/50] allow payer and recipient to be different --- .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 | 60 ++++++++++++------- contracts/interfaces/IV4Router.sol | 11 ++-- test/V4Router.t.sol | 14 ++--- .../implementation/V4RouterImplementation.sol | 6 +- 13 files changed, 62 insertions(+), 47 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 19b15e18..17ac8e65 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5925 \ No newline at end of file +6067 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 3bfc7e5a..017f6b15 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101468 \ No newline at end of file +101510 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 6f6b2383..517bb875 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160449 \ No newline at end of file +160475 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 3031f4e5..028f0b10 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212680 \ No newline at end of file +212691 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 495f97ed..558ce33b 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106771 \ No newline at end of file +106775 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index b08c4f7e..727daa90 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -102242 \ No newline at end of file +102284 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index f9da51bb..2e896618 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159812 \ No newline at end of file +159838 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 7563d22f..344aedc5 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212732 \ No newline at end of file +212743 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 0a49af3a..91ebf967 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -105380 \ No newline at end of file +105384 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 49642064..f4266565 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -23,8 +23,10 @@ abstract contract V4Router is IV4Router { poolManager = _poolManager; } - function _v4Swap(SwapType swapType, bytes memory params) internal { - poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params))); + // @dev The contract inheriting from this contract, and calling _v4Swap must set the payer and recipient securely. + // Allowing any payer or recipient to be passed in could allow users to steal each others' tokens. + function _v4Swap(SwapType swapType, PaymentAddresses memory paymentAddresses, bytes memory params) internal { + poolManager.unlock(abi.encode(SwapInfo(swapType, paymentAddresses, params))); } /// @inheritdoc IUnlockCallback @@ -34,13 +36,17 @@ abstract contract V4Router is IV4Router { SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { - _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.msgSender); + _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.paymentAddresses); } else if (swapInfo.swapType == SwapType.ExactInputSingle) { - _swapExactInputSingle(abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.msgSender); + _swapExactInputSingle( + abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.paymentAddresses + ); } else if (swapInfo.swapType == SwapType.ExactOutput) { - _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.msgSender); + _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.paymentAddresses); } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - _swapExactOutputSingle(abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.msgSender); + _swapExactOutputSingle( + abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.paymentAddresses + ); } else { revert InvalidSwapType(); } @@ -48,20 +54,25 @@ abstract contract V4Router is IV4Router { return bytes(""); } - function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params, address msgSender) private { + function _swapExactInputSingle( + IV4Router.ExactInputSingleParams memory params, + PaymentAddresses memory paymentAddresses + ) private { _swap( params.poolKey, params.zeroForOne, int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, - msgSender, + paymentAddresses, true, true, params.hookData ); } - function _swapExactInput(IV4Router.ExactInputParams memory params, address msgSender) private { + function _swapExactInput(IV4Router.ExactInputParams memory params, PaymentAddresses memory paymentAddresses) + private + { unchecked { uint256 pathLength = params.path.length; uint128 amountOut; @@ -74,7 +85,7 @@ abstract contract V4Router is IV4Router { zeroForOne, int256(-int128(params.amountIn)), 0, - msgSender, + paymentAddresses, i == 0, i == pathLength - 1, params.path[i].hookData @@ -89,20 +100,25 @@ abstract contract V4Router is IV4Router { } } - function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params, address msgSender) private { + function _swapExactOutputSingle( + IV4Router.ExactOutputSingleParams memory params, + PaymentAddresses memory paymentAddresses + ) private { _swap( params.poolKey, params.zeroForOne, int256(int128(params.amountOut)), params.sqrtPriceLimitX96, - msgSender, + paymentAddresses, true, true, params.hookData ); } - function _swapExactOutput(IV4Router.ExactOutputParams memory params, address msgSender) private { + function _swapExactOutput(IV4Router.ExactOutputParams memory params, PaymentAddresses memory paymentAddresses) + private + { unchecked { uint256 pathLength = params.path.length; uint128 amountIn; @@ -116,7 +132,7 @@ abstract contract V4Router is IV4Router { !oneForZero, int256(int128(params.amountOut)), 0, - msgSender, + paymentAddresses, i == 1, i == pathLength, params.path[i - 1].hookData @@ -135,7 +151,7 @@ abstract contract V4Router is IV4Router { bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, - address msgSender, + PaymentAddresses memory paymentAddresses, bool settle, bool take, bytes memory hookData @@ -154,12 +170,12 @@ abstract contract V4Router is IV4Router { if (zeroForOne) { 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())); + if (settle) _payAndSettle(poolKey.currency0, paymentAddresses.payer, delta.amount0()); + if (take) poolManager.take(poolKey.currency1, paymentAddresses.recipient, uint128(delta.amount1())); } else { 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())); + if (settle) _payAndSettle(poolKey.currency1, paymentAddresses.payer, delta.amount1()); + if (take) poolManager.take(poolKey.currency0, paymentAddresses.recipient, uint128(delta.amount0())); } } @@ -176,11 +192,11 @@ abstract contract V4Router is IV4Router { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } - function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { + function _payAndSettle(Currency currency, address payer, int128 settleAmount) private { poolManager.sync(currency); - _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); + _pay(Currency.unwrap(currency), payer, uint256(uint128(-settleAmount))); poolManager.settle(currency); } - function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; + function _pay(address token, address payer, uint256 amount) internal virtual; } diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index 387e2ff0..d54000b6 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -21,14 +21,18 @@ interface IV4Router is IUnlockCallback { struct SwapInfo { SwapType swapType; - address msgSender; + PaymentAddresses paymentAddresses; bytes params; } + struct PaymentAddresses { + address payer; + address recipient; + } + struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; - address recipient; uint128 amountIn; uint128 amountOutMinimum; uint160 sqrtPriceLimitX96; @@ -38,7 +42,6 @@ interface IV4Router is IUnlockCallback { struct ExactInputParams { Currency currencyIn; PathKey[] path; - address recipient; uint128 amountIn; uint128 amountOutMinimum; } @@ -46,7 +49,6 @@ interface IV4Router is IUnlockCallback { struct ExactOutputSingleParams { PoolKey poolKey; bool zeroForOne; - address recipient; uint128 amountOut; uint128 amountInMaximum; uint160 sqrtPriceLimitX96; @@ -56,7 +58,6 @@ interface IV4Router is IUnlockCallback { struct ExactOutputParams { Currency currencyOut; PathKey[] path; - address recipient; uint128 amountOut; uint128 amountInMaximum; } diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index 706d1712..97cf39d8 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -74,7 +74,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -95,7 +95,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -212,7 +212,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -233,7 +233,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, false, uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -372,7 +372,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal - view + pure returns (IV4Router.ExactInputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -382,14 +382,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { params.currencyIn = Currency.wrap(address(_tokenPath[0])); params.path = path; - params.recipient = address(this); params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; } function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal - view + pure returns (IV4Router.ExactOutputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -399,7 +398,6 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { 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; } diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index aac7fde4..fada2418 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -10,10 +10,10 @@ contract V4RouterImplementation is V4Router { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} function swap(IV4Router.SwapType swapType, bytes memory params) external { - _v4Swap(swapType, params); + _v4Swap(swapType, PaymentAddresses({payer: msg.sender, recipient: msg.sender}), params); } - function _pay(address token, address payer, address recipient, uint256 amount) internal override { - IERC20Minimal(token).transferFrom(payer, recipient, amount); + function _pay(address token, address payer, uint256 amount) internal override { + IERC20Minimal(token).transferFrom(payer, address(poolManager), amount); } } From 06961bf7f93e9fd8896a2b9bdf81d81e4da726b9 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 5 Jul 2024 12:41:22 +0100 Subject: [PATCH 22/50] try to fix ci --- foundry.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/foundry.toml b/foundry.toml index 4e95a213..c914100a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,7 +7,4 @@ ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" -[profile.ci] -fuzz_runs = 100000 - # See more config options https://github.com/foundry-rs/foundry/tree/master/config From 528f15de8353fd14cc45bbbf3f36bbd770ab8c2b Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 5 Jul 2024 13:16:42 +0100 Subject: [PATCH 23/50] improve casting --- .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 | 16 ++++++++-------- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 17ac8e65..f4c8f894 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -6067 \ No newline at end of file +6182 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 017f6b15..1639aaac 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101510 \ No newline at end of file +101511 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 517bb875..e10d3462 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160475 \ No newline at end of file +160474 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 028f0b10..d99da6af 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212691 \ No newline at end of file +212688 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 558ce33b..be280256 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106775 \ No newline at end of file +106760 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 727daa90..92e7ac00 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -102284 \ No newline at end of file +102285 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 2e896618..25ca7e9a 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159838 \ No newline at end of file +159837 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 344aedc5..d8b21a69 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212743 \ No newline at end of file +212740 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 91ebf967..abe5fcde 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -105384 \ No newline at end of file +105385 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index f4266565..652e0639 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -61,7 +61,7 @@ abstract contract V4Router is IV4Router { _swap( params.poolKey, params.zeroForOne, - int256(-int128(params.amountIn)), + -int256(uint256(params.amountIn)), params.sqrtPriceLimitX96, paymentAddresses, true, @@ -83,7 +83,7 @@ abstract contract V4Router is IV4Router { _swap( poolKey, zeroForOne, - int256(-int128(params.amountIn)), + -int256(uint256(params.amountIn)), 0, paymentAddresses, i == 0, @@ -107,7 +107,7 @@ abstract contract V4Router is IV4Router { _swap( params.poolKey, params.zeroForOne, - int256(int128(params.amountOut)), + int256(uint256(params.amountOut)), params.sqrtPriceLimitX96, paymentAddresses, true, @@ -130,7 +130,7 @@ abstract contract V4Router is IV4Router { -_swap( poolKey, !oneForZero, - int256(int128(params.amountOut)), + int256(uint256(params.amountOut)), 0, paymentAddresses, i == 1, @@ -170,11 +170,11 @@ abstract contract V4Router is IV4Router { if (zeroForOne) { reciprocalAmount = amountSpecified < 0 ? delta.amount1() : delta.amount0(); - if (settle) _payAndSettle(poolKey.currency0, paymentAddresses.payer, delta.amount0()); + if (settle) _payAndSettle(poolKey.currency0, paymentAddresses.payer, uint128(-delta.amount0())); if (take) poolManager.take(poolKey.currency1, paymentAddresses.recipient, uint128(delta.amount1())); } else { reciprocalAmount = amountSpecified < 0 ? delta.amount0() : delta.amount1(); - if (settle) _payAndSettle(poolKey.currency1, paymentAddresses.payer, delta.amount1()); + if (settle) _payAndSettle(poolKey.currency1, paymentAddresses.payer, uint128(-delta.amount1())); if (take) poolManager.take(poolKey.currency0, paymentAddresses.recipient, uint128(delta.amount0())); } } @@ -192,9 +192,9 @@ abstract contract V4Router is IV4Router { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } - function _payAndSettle(Currency currency, address payer, int128 settleAmount) private { + function _payAndSettle(Currency currency, address payer, uint256 settleAmount) private { poolManager.sync(currency); - _pay(Currency.unwrap(currency), payer, uint256(uint128(-settleAmount))); + _pay(Currency.unwrap(currency), payer, settleAmount); poolManager.settle(currency); } From 0ac638a77fb4eee7cdcc1220ebf59f83f6539566 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 15:49:32 +0100 Subject: [PATCH 24/50] reverting 3 commits to make branch --- .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 | 68 +++++++------------ contracts/interfaces/IV4Router.sol | 11 ++- foundry.toml | 3 + test/V4Router.t.sol | 14 ++-- .../implementation/V4RouterImplementation.sol | 6 +- 14 files changed, 54 insertions(+), 66 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index f4c8f894..19b15e18 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -6182 \ No newline at end of file +5925 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 1639aaac..3bfc7e5a 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101511 \ No newline at end of file +101468 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index e10d3462..6f6b2383 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160474 \ No newline at end of file +160449 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index d99da6af..3031f4e5 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212688 \ No newline at end of file +212680 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index be280256..495f97ed 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106760 \ No newline at end of file +106771 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 92e7ac00..b08c4f7e 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -102285 \ No newline at end of file +102242 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 25ca7e9a..f9da51bb 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159837 \ No newline at end of file +159812 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index d8b21a69..7563d22f 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212740 \ No newline at end of file +212732 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index abe5fcde..0a49af3a 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -105385 \ No newline at end of file +105380 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 652e0639..49642064 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -23,10 +23,8 @@ abstract contract V4Router is IV4Router { poolManager = _poolManager; } - // @dev The contract inheriting from this contract, and calling _v4Swap must set the payer and recipient securely. - // Allowing any payer or recipient to be passed in could allow users to steal each others' tokens. - function _v4Swap(SwapType swapType, PaymentAddresses memory paymentAddresses, bytes memory params) internal { - poolManager.unlock(abi.encode(SwapInfo(swapType, paymentAddresses, params))); + function _v4Swap(SwapType swapType, bytes memory params) internal { + poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params))); } /// @inheritdoc IUnlockCallback @@ -36,17 +34,13 @@ abstract contract V4Router is IV4Router { SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); if (swapInfo.swapType == SwapType.ExactInput) { - _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.paymentAddresses); + _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactInputSingle) { - _swapExactInputSingle( - abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.paymentAddresses - ); + _swapExactInputSingle(abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactOutput) { - _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.paymentAddresses); + _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.msgSender); } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - _swapExactOutputSingle( - abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.paymentAddresses - ); + _swapExactOutputSingle(abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.msgSender); } else { revert InvalidSwapType(); } @@ -54,25 +48,20 @@ abstract contract V4Router is IV4Router { return bytes(""); } - function _swapExactInputSingle( - IV4Router.ExactInputSingleParams memory params, - PaymentAddresses memory paymentAddresses - ) private { + function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params, address msgSender) private { _swap( params.poolKey, params.zeroForOne, - -int256(uint256(params.amountIn)), + int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, - paymentAddresses, + msgSender, true, true, params.hookData ); } - function _swapExactInput(IV4Router.ExactInputParams memory params, PaymentAddresses memory paymentAddresses) - private - { + function _swapExactInput(IV4Router.ExactInputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; uint128 amountOut; @@ -83,9 +72,9 @@ abstract contract V4Router is IV4Router { _swap( poolKey, zeroForOne, - -int256(uint256(params.amountIn)), + int256(-int128(params.amountIn)), 0, - paymentAddresses, + msgSender, i == 0, i == pathLength - 1, params.path[i].hookData @@ -100,25 +89,20 @@ abstract contract V4Router is IV4Router { } } - function _swapExactOutputSingle( - IV4Router.ExactOutputSingleParams memory params, - PaymentAddresses memory paymentAddresses - ) private { + function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params, address msgSender) private { _swap( params.poolKey, params.zeroForOne, - int256(uint256(params.amountOut)), + int256(int128(params.amountOut)), params.sqrtPriceLimitX96, - paymentAddresses, + msgSender, true, true, params.hookData ); } - function _swapExactOutput(IV4Router.ExactOutputParams memory params, PaymentAddresses memory paymentAddresses) - private - { + function _swapExactOutput(IV4Router.ExactOutputParams memory params, address msgSender) private { unchecked { uint256 pathLength = params.path.length; uint128 amountIn; @@ -130,9 +114,9 @@ abstract contract V4Router is IV4Router { -_swap( poolKey, !oneForZero, - int256(uint256(params.amountOut)), + int256(int128(params.amountOut)), 0, - paymentAddresses, + msgSender, i == 1, i == pathLength, params.path[i - 1].hookData @@ -151,7 +135,7 @@ abstract contract V4Router is IV4Router { bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, - PaymentAddresses memory paymentAddresses, + address msgSender, bool settle, bool take, bytes memory hookData @@ -170,12 +154,12 @@ abstract contract V4Router is IV4Router { if (zeroForOne) { reciprocalAmount = amountSpecified < 0 ? delta.amount1() : delta.amount0(); - if (settle) _payAndSettle(poolKey.currency0, paymentAddresses.payer, uint128(-delta.amount0())); - if (take) poolManager.take(poolKey.currency1, paymentAddresses.recipient, uint128(delta.amount1())); + 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(); - if (settle) _payAndSettle(poolKey.currency1, paymentAddresses.payer, uint128(-delta.amount1())); - if (take) poolManager.take(poolKey.currency0, paymentAddresses.recipient, uint128(delta.amount0())); + if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); + if (take) poolManager.take(poolKey.currency0, msgSender, uint128(delta.amount0())); } } @@ -192,11 +176,11 @@ abstract contract V4Router is IV4Router { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } - function _payAndSettle(Currency currency, address payer, uint256 settleAmount) private { + function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { poolManager.sync(currency); - _pay(Currency.unwrap(currency), payer, settleAmount); + _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); poolManager.settle(currency); } - function _pay(address token, address payer, uint256 amount) internal virtual; + function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; } diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index d54000b6..387e2ff0 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -21,18 +21,14 @@ interface IV4Router is IUnlockCallback { struct SwapInfo { SwapType swapType; - PaymentAddresses paymentAddresses; + address msgSender; bytes params; } - struct PaymentAddresses { - address payer; - address recipient; - } - struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; + address recipient; uint128 amountIn; uint128 amountOutMinimum; uint160 sqrtPriceLimitX96; @@ -42,6 +38,7 @@ interface IV4Router is IUnlockCallback { struct ExactInputParams { Currency currencyIn; PathKey[] path; + address recipient; uint128 amountIn; uint128 amountOutMinimum; } @@ -49,6 +46,7 @@ interface IV4Router is IUnlockCallback { struct ExactOutputSingleParams { PoolKey poolKey; bool zeroForOne; + address recipient; uint128 amountOut; uint128 amountInMaximum; uint160 sqrtPriceLimitX96; @@ -58,6 +56,7 @@ interface IV4Router is IUnlockCallback { struct ExactOutputParams { Currency currencyOut; PathKey[] path; + address recipient; uint128 amountOut; uint128 amountInMaximum; } diff --git a/foundry.toml b/foundry.toml index c914100a..4e95a213 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,4 +7,7 @@ ffi = true fs_permissions = [{ access = "read-write", path = ".forge-snapshots/"}] evm_version = "cancun" +[profile.ci] +fuzz_runs = 100000 + # See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index 97cf39d8..706d1712 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -74,7 +74,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -95,7 +95,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -212,7 +212,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -233,7 +233,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, false, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -372,7 +372,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal - pure + view returns (IV4Router.ExactInputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -382,13 +382,14 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { params.currencyIn = Currency.wrap(address(_tokenPath[0])); params.path = path; + params.recipient = address(this); params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; } function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal - pure + view returns (IV4Router.ExactOutputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -398,6 +399,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { 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; } diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index fada2418..aac7fde4 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -10,10 +10,10 @@ contract V4RouterImplementation is V4Router { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} function swap(IV4Router.SwapType swapType, bytes memory params) external { - _v4Swap(swapType, PaymentAddresses({payer: msg.sender, recipient: msg.sender}), params); + _v4Swap(swapType, params); } - function _pay(address token, address payer, uint256 amount) internal override { - IERC20Minimal(token).transferFrom(payer, address(poolManager), amount); + function _pay(address token, address payer, address recipient, uint256 amount) internal override { + IERC20Minimal(token).transferFrom(payer, recipient, amount); } } From 1844d702a11e6165f22b4ba2d12bd624ef2c655c Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 15:57:26 +0100 Subject: [PATCH 25/50] remove unused recipient param --- .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/interfaces/IV4Router.sol | 4 ---- test/Quoter.t.sol | 6 ++---- test/V4Router.t.sol | 14 ++++++-------- 12 files changed, 17 insertions(+), 25 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 19b15e18..e27478bc 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5925 \ No newline at end of file +5914 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 3bfc7e5a..a850e360 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101468 \ No newline at end of file +101052 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 6f6b2383..e97a90c4 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160449 \ No newline at end of file +160017 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 3031f4e5..8eb43faa 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212680 \ No newline at end of file +212233 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 495f97ed..e0984aef 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106771 \ No newline at end of file +106320 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index b08c4f7e..9acca061 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -102242 \ No newline at end of file +101826 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index f9da51bb..6a438851 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159812 \ No newline at end of file +159380 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 7563d22f..021ccb55 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212732 \ No newline at end of file +212285 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 0a49af3a..26a83bc6 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -105380 \ No newline at end of file +104929 \ No newline at end of file diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index 387e2ff0..804b7fe9 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -28,7 +28,6 @@ interface IV4Router is IUnlockCallback { struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; - address recipient; uint128 amountIn; uint128 amountOutMinimum; uint160 sqrtPriceLimitX96; @@ -38,7 +37,6 @@ interface IV4Router is IUnlockCallback { struct ExactInputParams { Currency currencyIn; PathKey[] path; - address recipient; uint128 amountIn; uint128 amountOutMinimum; } @@ -46,7 +44,6 @@ interface IV4Router is IUnlockCallback { struct ExactOutputSingleParams { PoolKey poolKey; bool zeroForOne; - address recipient; uint128 amountOut; uint128 amountInMaximum; uint160 sqrtPriceLimitX96; @@ -56,7 +53,6 @@ interface IV4Router is IUnlockCallback { struct ExactOutputParams { Currency currencyOut; PathKey[] path; - address recipient; uint128 amountOut; uint128 amountInMaximum; } diff --git a/test/Quoter.t.sol b/test/Quoter.t.sol index 0767cadd..6e737dcd 100644 --- a/test/Quoter.t.sol +++ b/test/Quoter.t.sol @@ -639,7 +639,7 @@ contract QuoterTest is Test, Deployers { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal - view + pure returns (IQuoter.QuoteExactParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -649,13 +649,12 @@ contract QuoterTest is Test, Deployers { params.exactCurrency = Currency.wrap(address(_tokenPath[0])); params.path = path; - params.recipient = address(this); params.exactAmount = uint128(amountIn); } function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal - view + pure returns (IQuoter.QuoteExactParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -665,7 +664,6 @@ contract QuoterTest is Test, Deployers { params.exactCurrency = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); params.path = path; - params.recipient = address(this); params.exactAmount = uint128(amountOut); } } diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index 706d1712..97cf39d8 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -74,7 +74,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, true, address(this), uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -95,7 +95,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountOut = 992054607780215625; IV4Router.ExactInputSingleParams memory params = - IV4Router.ExactInputSingleParams(key0, false, address(this), uint128(amountIn), 0, 0, bytes("")); + IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -212,7 +212,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, address(this), uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -233,7 +233,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 expectedAmountIn = 1008049273448486163; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, false, address(this), uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, false, uint128(amountOut), 0, 0, bytes("")); uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); @@ -372,7 +372,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) internal - view + pure returns (IV4Router.ExactInputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -382,14 +382,13 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { params.currencyIn = Currency.wrap(address(_tokenPath[0])); params.path = path; - params.recipient = address(this); params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; } function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) internal - view + pure returns (IV4Router.ExactOutputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); @@ -399,7 +398,6 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { 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 86039e4c6e0d8e0ee8bc1e17a3723d2ebfeddc51 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 17:35:48 +0100 Subject: [PATCH 26/50] remove payment logic from swap logic --- .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 | 99 +++++++++---------- 10 files changed, 58 insertions(+), 59 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index e27478bc..b14e0fe1 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5914 \ No newline at end of file +6068 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index a850e360..2ed2b7b1 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101052 \ No newline at end of file +102877 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index e97a90c4..e07f290c 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160017 \ No newline at end of file +161735 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 8eb43faa..72549382 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212233 \ No newline at end of file +213882 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index e0984aef..3bc7354d 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106320 \ No newline at end of file +108037 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 9acca061..8ae3471f 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -101826 \ No newline at end of file +103551 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 6a438851..e80c1af3 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159380 \ No newline at end of file +161013 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 021ccb55..4c5dbf00 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212285 \ No newline at end of file +213832 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 26a83bc6..3d24129b 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -104929 \ No newline at end of file +106646 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 49642064..107f7635 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.19; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; @@ -16,6 +17,7 @@ import {IV4Router} from "./interfaces/IV4Router.sol"; /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools abstract contract V4Router is IV4Router { using CurrencyLibrary for Currency; + using TransientStateLibrary for IPoolManager; IPoolManager immutable poolManager; @@ -33,53 +35,73 @@ abstract contract V4Router is IV4Router { SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); + Currency inputCurrency; + Currency outputCurrency; + if (swapInfo.swapType == SwapType.ExactInput) { - _swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.msgSender); + IV4Router.ExactInputParams memory params = abi.decode(swapInfo.params, (IV4Router.ExactInputParams)); + inputCurrency = params.currencyIn; + outputCurrency = params.path[params.path.length - 1].intermediateCurrency; + + _swapExactInput(params); } else if (swapInfo.swapType == SwapType.ExactInputSingle) { - _swapExactInputSingle(abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.msgSender); + IV4Router.ExactInputSingleParams memory params = + abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)); + (inputCurrency, outputCurrency) = params.zeroForOne + ? (params.poolKey.currency0, params.poolKey.currency1) + : (params.poolKey.currency1, params.poolKey.currency0); + + _swapExactInputSingle(params); } else if (swapInfo.swapType == SwapType.ExactOutput) { - _swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.msgSender); + IV4Router.ExactOutputParams memory params = abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)); + inputCurrency = params.path[0].intermediateCurrency; + outputCurrency = params.currencyOut; + + _swapExactOutput(params); } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - _swapExactOutputSingle(abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.msgSender); + IV4Router.ExactOutputSingleParams memory params = + abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)); + (inputCurrency, outputCurrency) = params.zeroForOne + ? (params.poolKey.currency0, params.poolKey.currency1) + : (params.poolKey.currency1, params.poolKey.currency0); + + _swapExactOutputSingle(params); } else { revert InvalidSwapType(); } + // settle + int256 delta = poolManager.currencyDelta(address(this), inputCurrency); + if (delta > 0) revert(); + _payAndSettle(inputCurrency, swapInfo.msgSender, uint256(-delta)); + + // take + delta = poolManager.currencyDelta(address(this), outputCurrency); + if (delta < 0) revert(); + poolManager.take(outputCurrency, swapInfo.msgSender, uint256(delta)); + return bytes(""); } - function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params, address msgSender) private { + function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params) private { _swap( params.poolKey, params.zeroForOne, int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, - msgSender, - true, - true, params.hookData ); } - function _swapExactInput(IV4Router.ExactInputParams memory params, address msgSender) private { + function _swapExactInput(IV4Router.ExactInputParams memory params) 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); - amountOut = uint128( - _swap( - poolKey, - zeroForOne, - int256(-int128(params.amountIn)), - 0, - msgSender, - i == 0, - i == pathLength - 1, - params.path[i].hookData - ) - ); + amountOut = + uint128(_swap(poolKey, zeroForOne, int256(-int128(params.amountIn)), 0, params.path[i].hookData)); params.amountIn = amountOut; params.currencyIn = params.path[i].intermediateCurrency; @@ -89,20 +111,17 @@ abstract contract V4Router is IV4Router { } } - function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params, address msgSender) private { + function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params) private { _swap( params.poolKey, params.zeroForOne, int256(int128(params.amountOut)), params.sqrtPriceLimitX96, - msgSender, - true, - true, params.hookData ); } - function _swapExactOutput(IV4Router.ExactOutputParams memory params, address msgSender) private { + function _swapExactOutput(IV4Router.ExactOutputParams memory params) private { unchecked { uint256 pathLength = params.path.length; uint128 amountIn; @@ -111,16 +130,7 @@ abstract contract V4Router is IV4Router { (PoolKey memory poolKey, bool oneForZero) = _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); amountIn = uint128( - -_swap( - poolKey, - !oneForZero, - int256(int128(params.amountOut)), - 0, - msgSender, - i == 1, - i == pathLength, - params.path[i - 1].hookData - ) + -_swap(poolKey, !oneForZero, int256(int128(params.amountOut)), 0, params.path[i - 1].hookData) ); params.amountOut = amountIn; @@ -135,9 +145,6 @@ abstract contract V4Router is IV4Router { bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, - address msgSender, - bool settle, - bool take, bytes memory hookData ) private returns (int128 reciprocalAmount) { BalanceDelta delta = poolManager.swap( @@ -152,15 +159,7 @@ abstract contract V4Router is IV4Router { hookData ); - if (zeroForOne) { - 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(); - if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1()); - if (take) poolManager.take(poolKey.currency0, msgSender, uint128(delta.amount0())); - } + reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); } function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) @@ -176,9 +175,9 @@ abstract contract V4Router is IV4Router { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } - function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { + function _payAndSettle(Currency currency, address payer, uint256 settleAmount) private { poolManager.sync(currency); - _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); + _pay(Currency.unwrap(currency), payer, address(poolManager), settleAmount); poolManager.settle(currency); } From 90f3f352eeae79115cad440d7c379ab240748fe5 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 17:39:45 +0100 Subject: [PATCH 27/50] factor out payment functions --- .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 | 22 ++++++++++++------- 10 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index b14e0fe1..071d558a 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -6068 \ No newline at end of file +6078 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 2ed2b7b1..7f99925b 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -102877 \ No newline at end of file +102898 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index e07f290c..c5627dd9 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -161735 \ No newline at end of file +161756 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 72549382..9f58155f 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -213882 \ No newline at end of file +213903 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 3bc7354d..38b8a18d 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -108037 \ No newline at end of file +108058 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 8ae3471f..a114d149 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -103551 \ No newline at end of file +103572 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index e80c1af3..5380a87b 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -161013 \ No newline at end of file +161034 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 4c5dbf00..8bcf6d17 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -213832 \ No newline at end of file +213853 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 3d24129b..837d433f 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -106646 \ No newline at end of file +106667 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 107f7635..0584175f 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -71,14 +71,10 @@ abstract contract V4Router is IV4Router { } // settle - int256 delta = poolManager.currencyDelta(address(this), inputCurrency); - if (delta > 0) revert(); - _payAndSettle(inputCurrency, swapInfo.msgSender, uint256(-delta)); + _payAndSettle(inputCurrency, swapInfo.msgSender); // take - delta = poolManager.currencyDelta(address(this), outputCurrency); - if (delta < 0) revert(); - poolManager.take(outputCurrency, swapInfo.msgSender, uint256(delta)); + _take(outputCurrency, swapInfo.msgSender); return bytes(""); } @@ -175,9 +171,19 @@ abstract contract V4Router is IV4Router { poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); } - function _payAndSettle(Currency currency, address payer, uint256 settleAmount) private { + function _take(Currency currency, address recipient) private { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta < 0) revert(); + + poolManager.take(currency, recipient, uint256(delta)); + } + + function _payAndSettle(Currency currency, address payer) private { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta > 0) revert(); + poolManager.sync(currency); - _pay(Currency.unwrap(currency), payer, address(poolManager), settleAmount); + _pay(Currency.unwrap(currency), payer, address(poolManager), uint256(-delta)); poolManager.settle(currency); } From 50372c71abe53300a11211e1d311f12b911079ec Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 17:58:22 +0100 Subject: [PATCH 28/50] gas opt removing struct --- .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 | 27 +++++++++---------- contracts/interfaces/IV4Router.sol | 6 ----- 11 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 071d558a..0cfc5138 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -6078 \ No newline at end of file +5910 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 7f99925b..c74be1f1 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -102898 \ No newline at end of file +102517 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index c5627dd9..e0a6da3d 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -161756 \ No newline at end of file +161374 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 9f58155f..eebf4da7 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -213903 \ No newline at end of file +213520 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 38b8a18d..251cce84 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -108058 \ No newline at end of file +107674 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index a114d149..43c45e7c 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -103572 \ No newline at end of file +103185 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 5380a87b..5225186c 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -161034 \ No newline at end of file +160646 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 8bcf6d17..137345c1 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -213853 \ No newline at end of file +213464 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 837d433f..3d31075e 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -106667 \ No newline at end of file +106277 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 0584175f..82178dc1 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -26,41 +26,40 @@ abstract contract V4Router is IV4Router { } function _v4Swap(SwapType swapType, bytes memory params) internal { - poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params))); + poolManager.unlock(abi.encode(swapType, msg.sender, params)); } /// @inheritdoc IUnlockCallback function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) { if (msg.sender != address(poolManager)) revert NotPoolManager(); - SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo)); + (SwapType swapType, address msgSender, bytes memory params) = + abi.decode(encodedSwapInfo, (SwapType, address, bytes)); Currency inputCurrency; Currency outputCurrency; - if (swapInfo.swapType == SwapType.ExactInput) { - IV4Router.ExactInputParams memory params = abi.decode(swapInfo.params, (IV4Router.ExactInputParams)); + if (swapType == SwapType.ExactInput) { + IV4Router.ExactInputParams memory params = abi.decode(params, (IV4Router.ExactInputParams)); inputCurrency = params.currencyIn; outputCurrency = params.path[params.path.length - 1].intermediateCurrency; _swapExactInput(params); - } else if (swapInfo.swapType == SwapType.ExactInputSingle) { - IV4Router.ExactInputSingleParams memory params = - abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)); + } else if (swapType == SwapType.ExactInputSingle) { + IV4Router.ExactInputSingleParams memory params = abi.decode(params, (IV4Router.ExactInputSingleParams)); (inputCurrency, outputCurrency) = params.zeroForOne ? (params.poolKey.currency0, params.poolKey.currency1) : (params.poolKey.currency1, params.poolKey.currency0); _swapExactInputSingle(params); - } else if (swapInfo.swapType == SwapType.ExactOutput) { - IV4Router.ExactOutputParams memory params = abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)); + } else if (swapType == SwapType.ExactOutput) { + IV4Router.ExactOutputParams memory params = abi.decode(params, (IV4Router.ExactOutputParams)); inputCurrency = params.path[0].intermediateCurrency; outputCurrency = params.currencyOut; _swapExactOutput(params); - } else if (swapInfo.swapType == SwapType.ExactOutputSingle) { - IV4Router.ExactOutputSingleParams memory params = - abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)); + } else if (swapType == SwapType.ExactOutputSingle) { + IV4Router.ExactOutputSingleParams memory params = abi.decode(params, (IV4Router.ExactOutputSingleParams)); (inputCurrency, outputCurrency) = params.zeroForOne ? (params.poolKey.currency0, params.poolKey.currency1) : (params.poolKey.currency1, params.poolKey.currency0); @@ -71,10 +70,10 @@ abstract contract V4Router is IV4Router { } // settle - _payAndSettle(inputCurrency, swapInfo.msgSender); + _payAndSettle(inputCurrency, msgSender); // take - _take(outputCurrency, swapInfo.msgSender); + _take(outputCurrency, msgSender); return bytes(""); } diff --git a/contracts/interfaces/IV4Router.sol b/contracts/interfaces/IV4Router.sol index 804b7fe9..b4e180b9 100644 --- a/contracts/interfaces/IV4Router.sol +++ b/contracts/interfaces/IV4Router.sol @@ -19,12 +19,6 @@ interface IV4Router is IUnlockCallback { error TooLittleReceived(); error TooMuchRequested(); - struct SwapInfo { - SwapType swapType; - address msgSender; - bytes params; - } - struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; From 64f4645d14e6d187ef79a7f00d604144050ae599 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 17 Jul 2024 18:05:53 +0100 Subject: [PATCH 29/50] gas opt: decode in assembly --- .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 | 23 +++++++--- contracts/libraries/BytesLib.sol | 43 +++++++++++++++++++ 11 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 contracts/libraries/BytesLib.sol diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 0cfc5138..e92efd9e 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5910 \ No newline at end of file +5934 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index c74be1f1..58e3ec1a 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -102517 \ No newline at end of file +101953 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index e0a6da3d..5359e52d 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -161374 \ No newline at end of file +160726 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index eebf4da7..cd812f31 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -213520 \ No newline at end of file +212787 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 251cce84..d8acb324 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -107674 \ No newline at end of file +107145 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 43c45e7c..24fff6f2 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -103185 \ No newline at end of file +102621 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 5225186c..df5dae23 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -160646 \ No newline at end of file +159998 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 137345c1..d47d8732 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -213464 \ No newline at end of file +212731 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 3d31075e..7074b435 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -106277 \ No newline at end of file +105748 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 82178dc1..9c7c9bf0 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -11,6 +11,7 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {PathKey} from "./libraries/PathKey.sol"; +import {BytesLib} from "./libraries/BytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; /// @title UniswapV4Router @@ -18,6 +19,7 @@ import {IV4Router} from "./interfaces/IV4Router.sol"; abstract contract V4Router is IV4Router { using CurrencyLibrary for Currency; using TransientStateLibrary for IPoolManager; + using BytesLib for bytes; IPoolManager immutable poolManager; @@ -33,33 +35,41 @@ abstract contract V4Router is IV4Router { function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) { if (msg.sender != address(poolManager)) revert NotPoolManager(); - (SwapType swapType, address msgSender, bytes memory params) = - abi.decode(encodedSwapInfo, (SwapType, address, bytes)); + SwapType swapType; + address msgSender; + // TODO dont decode to swapParams at all, just decode directly to the struct in each if statement + bytes calldata swapParams = encodedSwapInfo.toBytes(2); + assembly { + swapType := calldataload(encodedSwapInfo.offset) + msgSender := calldataload(add(encodedSwapInfo.offset, 0x20)) + } Currency inputCurrency; Currency outputCurrency; if (swapType == SwapType.ExactInput) { - IV4Router.ExactInputParams memory params = abi.decode(params, (IV4Router.ExactInputParams)); + IV4Router.ExactInputParams memory params = abi.decode(swapParams, (IV4Router.ExactInputParams)); inputCurrency = params.currencyIn; outputCurrency = params.path[params.path.length - 1].intermediateCurrency; _swapExactInput(params); } else if (swapType == SwapType.ExactInputSingle) { - IV4Router.ExactInputSingleParams memory params = abi.decode(params, (IV4Router.ExactInputSingleParams)); + IV4Router.ExactInputSingleParams memory params = + abi.decode(swapParams, (IV4Router.ExactInputSingleParams)); (inputCurrency, outputCurrency) = params.zeroForOne ? (params.poolKey.currency0, params.poolKey.currency1) : (params.poolKey.currency1, params.poolKey.currency0); _swapExactInputSingle(params); } else if (swapType == SwapType.ExactOutput) { - IV4Router.ExactOutputParams memory params = abi.decode(params, (IV4Router.ExactOutputParams)); + IV4Router.ExactOutputParams memory params = abi.decode(swapParams, (IV4Router.ExactOutputParams)); inputCurrency = params.path[0].intermediateCurrency; outputCurrency = params.currencyOut; _swapExactOutput(params); } else if (swapType == SwapType.ExactOutputSingle) { - IV4Router.ExactOutputSingleParams memory params = abi.decode(params, (IV4Router.ExactOutputSingleParams)); + IV4Router.ExactOutputSingleParams memory params = + abi.decode(swapParams, (IV4Router.ExactOutputSingleParams)); (inputCurrency, outputCurrency) = params.zeroForOne ? (params.poolKey.currency0, params.poolKey.currency1) : (params.poolKey.currency1, params.poolKey.currency0); @@ -177,6 +187,7 @@ abstract contract V4Router is IV4Router { poolManager.take(currency, recipient, uint256(delta)); } + // TODO native support !! function _payAndSettle(Currency currency, address payer) private { int256 delta = poolManager.currencyDelta(address(this), currency); if (delta > 0) revert(); diff --git a/contracts/libraries/BytesLib.sol b/contracts/libraries/BytesLib.sol new file mode 100644 index 00000000..ea59f9b8 --- /dev/null +++ b/contracts/libraries/BytesLib.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/// @title Library for Bytes Manipulation +pragma solidity ^0.8.24; + +library BytesLib { + error SliceOutOfBounds(); + + /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array + /// @dev The decoding of `length` and `offset` is universal, + /// whereas the type declaration of `res` instructs the compiler how to read it. + /// @param _bytes The input bytes string to slice + /// @param _arg The index of the argument to extract + /// @return length Length of the array + /// @return offset Pointer to the data part of the array + function toLengthOffset(bytes calldata _bytes, uint256 _arg) + internal + pure + returns (uint256 length, uint256 offset) + { + uint256 relativeOffset; + assembly { + // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. + // shl(5, x) is equivalent to mul(32, x) + let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg)))) + length := calldataload(lengthPtr) + offset := add(lengthPtr, 0x20) + relativeOffset := sub(offset, _bytes.offset) + } + if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds(); + } + + /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` + /// @param _bytes The input bytes string to extract a bytes string from + /// @param _arg The index of the argument to extract + function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { + (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); + assembly { + res.length := length + res.offset := offset + } + } +} From 8283fb6046418477d2c68ebee4e73499074764f9 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 18 Jul 2024 15:57:22 +0100 Subject: [PATCH 30/50] merge error --- .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 | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 19b15e18..3abf9377 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5925 \ No newline at end of file +5944 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 3bfc7e5a..dd116464 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -101468 \ No newline at end of file +104894 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 6f6b2383..750ee01d 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -160449 \ No newline at end of file +155912 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 3031f4e5..f54557f0 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -212680 \ No newline at end of file +213793 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 495f97ed..69cb18d2 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -106771 \ No newline at end of file +103706 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index b08c4f7e..fe2af9f9 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -102242 \ No newline at end of file +103576 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index f9da51bb..47a90f8e 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -159812 \ No newline at end of file +155229 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 7563d22f..3c17076d 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -212732 \ No newline at end of file +211706 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 0a49af3a..88ee60d2 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -105380 \ No newline at end of file +102298 \ No newline at end of file diff --git a/contracts/V4Router.sol b/contracts/V4Router.sol index 49642064..20b62912 100644 --- a/contracts/V4Router.sol +++ b/contracts/V4Router.sol @@ -179,7 +179,7 @@ abstract contract V4Router is IV4Router { function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private { poolManager.sync(currency); _pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount))); - poolManager.settle(currency); + poolManager.settle(); } function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; From 903d5fa2e5a2700edf8191b37cc56d3c1b7412e7 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 23 Jul 2024 13:14:57 +0100 Subject: [PATCH 31/50] use base actions router in v4router --- .../BaseActionsRouter_mock10commands.snap | 2 +- .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 +- src/V4Router.sol | 111 +++---- src/interfaces/IV4Router.sol | 21 +- src/libraries/Actions.sol | 11 +- test/BaseActionsRouter.t.sol | 20 +- test/V4Router.t.sol | 294 +++++++++++------- test/mocks/MockBaseActionsRouter.sol | 4 +- .../implementation/V4RouterImplementation.sol | 14 +- 17 files changed, 265 insertions(+), 230 deletions(-) diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index d99d3dfd..283c59f2 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -62824 \ No newline at end of file +34844 \ No newline at end of file diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 433ec759..9d0caed6 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5962 \ No newline at end of file +6056 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 65ad7fa8..65c99116 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -138538 \ No newline at end of file +105785 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index b52abd51..a62dd7d1 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -190617 \ No newline at end of file +156189 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 13c946ae..8f75dd27 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -249572 \ No newline at end of file +213433 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index ab4e04ec..c47031c9 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -137112 \ No newline at end of file +104778 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 581f9578..ce6c95e1 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -137340 \ No newline at end of file +104413 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 08a49b5b..29d1dfec 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -190043 \ No newline at end of file +155441 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index d9a61961..775b2ce5 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -247600 \ No newline at end of file +211287 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 0fb0695f..1685f997 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -135698 \ No newline at end of file +103312 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index d38e3802..3da8be40 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -1,90 +1,68 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {PathKey} from "./libraries/PathKey.sol"; import {BytesLib} from "./libraries/BytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; +import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; +import {Actions} from "./libraries/Actions.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -abstract contract V4Router is IV4Router { +/// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` +/// An inheriting contract should call _executeActions at the point that they wish actions to be executed +abstract contract V4Router is IV4Router, BaseActionsRouter { using CurrencyLibrary for Currency; using TransientStateLibrary for IPoolManager; using BytesLib for bytes; - IPoolManager immutable poolManager; - - constructor(IPoolManager _poolManager) { - poolManager = _poolManager; - } - - function _v4Swap(SwapType swapType, bytes memory params) internal { - poolManager.unlock(abi.encode(swapType, msg.sender, params)); - } - - /// @inheritdoc IUnlockCallback - function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) { - if (msg.sender != address(poolManager)) revert NotPoolManager(); - - SwapType swapType; - address msgSender; - // TODO dont decode to swapParams at all, just decode directly to the struct in each if statement - bytes calldata swapParams = encodedSwapInfo.toBytes(2); - assembly { - swapType := calldataload(encodedSwapInfo.offset) - msgSender := calldataload(add(encodedSwapInfo.offset, 0x20)) - } - - Currency inputCurrency; - Currency outputCurrency; - - if (swapType == SwapType.ExactInput) { - IV4Router.ExactInputParams memory params = abi.decode(swapParams, (IV4Router.ExactInputParams)); - inputCurrency = params.currencyIn; - outputCurrency = params.path[params.path.length - 1].intermediateCurrency; - - _swapExactInput(params); - } else if (swapType == SwapType.ExactInputSingle) { - IV4Router.ExactInputSingleParams memory params = abi.decode(swapParams, (IV4Router.ExactInputSingleParams)); - (inputCurrency, outputCurrency) = params.zeroForOne - ? (params.poolKey.currency0, params.poolKey.currency1) - : (params.poolKey.currency1, params.poolKey.currency0); - - _swapExactInputSingle(params); - } else if (swapType == SwapType.ExactOutput) { - IV4Router.ExactOutputParams memory params = abi.decode(swapParams, (IV4Router.ExactOutputParams)); - inputCurrency = params.path[0].intermediateCurrency; - outputCurrency = params.currencyOut; - - _swapExactOutput(params); - } else if (swapType == SwapType.ExactOutputSingle) { - IV4Router.ExactOutputSingleParams memory params = - abi.decode(swapParams, (IV4Router.ExactOutputSingleParams)); - (inputCurrency, outputCurrency) = params.zeroForOne - ? (params.poolKey.currency0, params.poolKey.currency1) - : (params.poolKey.currency1, params.poolKey.currency0); - - _swapExactOutputSingle(params); + constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} + + function _handleAction(uint256 action, bytes calldata params) internal override { + // swap actions and payment actions in different blocks for gas efficiency + if (action < 0x10) { + if (action == Actions.SWAP_EXACT_IN) { + _swapExactInput(abi.decode(params, (IV4Router.ExactInputParams))); + } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { + _swapExactInputSingle(abi.decode(params, (IV4Router.ExactInputSingleParams))); + } else if (action == Actions.SWAP_EXACT_OUT) { + _swapExactOutput(abi.decode(params, (IV4Router.ExactOutputParams))); + } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { + _swapExactOutputSingle(abi.decode(params, (IV4Router.ExactOutputSingleParams))); + } else { + revert UnsupportedAction(action); + } } else { - revert InvalidSwapType(); + if (action == Actions.SETTLE) { + // equivalent: abi.decode(params, (Currency)) + Currency currency; + assembly ("memory-safe") { + currency := calldataload(params.offset) + } + + // TODO support address(this) paying too + _payAndSettle(currency, _msgSender()); + } else if (action == Actions.TAKE) { + // equivalent: abi.decode(params, (Currency, address)) + Currency currency; + address recipient; + assembly ("memory-safe") { + currency := calldataload(params.offset) + recipient := calldataload(add(params.offset, 0x20)) + } + + // TODO add min amount?? + _take(currency, recipient); + } else { + revert UnsupportedAction(action); + } } - - // settle - _payAndSettle(inputCurrency, msgSender); - - // take - _take(outputCurrency, msgSender); - - return bytes(""); } function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params) private { @@ -187,6 +165,7 @@ abstract contract V4Router is IV4Router { } // TODO native support !! + // TODO use currency settle take library function _payAndSettle(Currency currency, address payer) private { int256 delta = poolManager.currencyDelta(address(this), currency); if (delta > 0) revert(); diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index b4e180b9..b0b15544 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -1,21 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {PathKey} from "../libraries/PathKey.sol"; -/// @title UniswapV4Routing -/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools -interface IV4Router is IUnlockCallback { - error NotPoolManager(); - error InvalidSwapType(); +/// @title IV4Router +/// @notice Interface containing all the structs and errors for different v4 swap types +interface IV4Router { error TooLittleReceived(); error TooMuchRequested(); @@ -50,11 +42,4 @@ interface IV4Router is IUnlockCallback { uint128 amountOut; uint128 amountInMaximum; } - - enum SwapType { - ExactInput, - ExactInputSingle, - ExactOutput, - ExactOutputSingle - } } diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index ca3e29fc..3892a7ae 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -5,10 +5,13 @@ pragma solidity ^0.8.24; /// @dev These are suggested common commands, however additional commands should be defined as required library Actions { // pool actions - uint256 constant SWAP = 0x00; - uint256 constant INCREASE_LIQUIDITY = 0x01; - uint256 constant DECREASE_LIQUIDITY = 0x02; - uint256 constant DONATE = 0x03; + uint256 constant INCREASE_LIQUIDITY = 0x00; + uint256 constant DECREASE_LIQUIDITY = 0x01; + uint256 constant SWAP_EXACT_IN_SINGLE = 0x02; + uint256 constant SWAP_EXACT_IN = 0x03; + uint256 constant SWAP_EXACT_OUT_SINGLE = 0x04; + uint256 constant SWAP_EXACT_OUT = 0x05; + uint256 constant DONATE = 0x06; // closing deltas on the pool manager uint256 constant SETTLE = 0x10; diff --git a/test/BaseActionsRouter.t.sol b/test/BaseActionsRouter.t.sol index 52666268..4ee2a878 100644 --- a/test/BaseActionsRouter.t.sol +++ b/test/BaseActionsRouter.t.sol @@ -22,14 +22,14 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { function test_swap_suceeds() public { Plan memory plan = ActionsRouterPlanner.init(); for (uint256 i = 0; i < 10; i++) { - plan.add(Actions.SWAP, ""); + plan.add(Actions.SWAP_EXACT_IN, ""); } bytes memory data = plan.encode(); assertEq(router.swapCount(), 0); - router.executeAction(data); + router.executeActions(data); snapLastCall("BaseActionsRouter_mock10commands"); assertEq(router.swapCount(), 10); } @@ -43,7 +43,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.increaseLiqCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.increaseLiqCount(), 10); } @@ -56,7 +56,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.decreaseLiqCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.decreaseLiqCount(), 10); } @@ -69,7 +69,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.donateCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.donateCount(), 10); } @@ -82,7 +82,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.clearCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.clearCount(), 10); } @@ -95,7 +95,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.settleCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.settleCount(), 10); } @@ -108,7 +108,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.takeCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.takeCount(), 10); } @@ -121,7 +121,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.mintCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.mintCount(), 10); } @@ -134,7 +134,7 @@ contract BaseActionsRouterTest is Test, Deployers, GasSnapshot { assertEq(router.burnCount(), 0); bytes memory data = plan.encode(); - router.executeAction(data); + router.executeActions(data); assertEq(router.burnCount(), 10); } } diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index c287bc99..fc9a348b 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -2,37 +2,41 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; +import {console2} from "forge-std/console2.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {IV4Router} from "../src/interfaces/IV4Router.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; + +import {IV4Router} from "../src/interfaces/IV4Router.sol"; import {V4RouterImplementation} from "./shared/implementation/V4RouterImplementation.sol"; +import {Plan, ActionsRouterPlanner} from "./utils/ActionsRouterPlanner.sol"; import {PathKey} from "../src/libraries/PathKey.sol"; +import {Actions} from "../src/libraries/Actions.sol"; contract V4RouterTest is Test, Deployers, GasSnapshot { using CurrencyLibrary for Currency; + using ActionsRouterPlanner for Plan; PoolModifyLiquidityTest positionManager; V4RouterImplementation router; - MockERC20 token0; - MockERC20 token1; - MockERC20 token2; - MockERC20 token3; + // currency0 and currency1 are defined in Deployers.sol + Currency currency2; + Currency currency3; PoolKey key0; PoolKey key1; PoolKey key2; - MockERC20[] tokenPath; + Currency[] tokenPath; + + Plan plan; function setUp() public { deployFreshManagerAndRouters(); @@ -40,14 +44,21 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { router = new V4RouterImplementation(manager); positionManager = new PoolModifyLiquidityTest(manager); - token0 = new MockERC20("Test0", "0", 18); + MockERC20 token0 = new MockERC20("Test0", "0", 18); token0.mint(address(this), 2 ** 128); - token1 = new MockERC20("Test1", "1", 18); + currency0 = Currency.wrap(address(token0)); + + MockERC20 token1 = new MockERC20("Test1", "1", 18); token1.mint(address(this), 2 ** 128); - token2 = new MockERC20("Test2", "2", 18); + currency1 = Currency.wrap(address(token1)); + + MockERC20 token2 = new MockERC20("Test2", "2", 18); token2.mint(address(this), 2 ** 128); - token3 = new MockERC20("Test3", "3", 18); + currency2 = Currency.wrap(address(token2)); + + MockERC20 token3 = new MockERC20("Test3", "3", 18); token3.mint(address(this), 2 ** 128); + currency3 = Currency.wrap(address(token3)); key0 = createPoolKey(token0, token1, address(0)); key1 = createPoolKey(token1, token2, address(0)); @@ -61,6 +72,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { token1.approve(address(router), type(uint256).max); token2.approve(address(router), type(uint256).max); token3.approve(address(router), type(uint256).max); + + plan = ActionsRouterPlanner.init(); } function testRouter_bytecodeSize() public { @@ -77,8 +90,12 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.encode(); + snapStart("RouterExactInputSingle"); - router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); + router.executeActions(data); snapEnd(); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); @@ -98,7 +115,11 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); - router.swap(IV4Router.SwapType.ExactInputSingle, abi.encode(params)); + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency1, key0.currency0, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -111,19 +132,23 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - tokenPath.push(token0); - tokenPath.push(token1); - IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency1, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactIn1Hop"); - router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); assertEq(prevBalance0 - newBalance0, amountIn); assertEq(newBalance1 - prevBalance1, expectedAmountOut); @@ -133,16 +158,20 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; - tokenPath.push(token1); - tokenPath.push(token0); - IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); - router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency1, currency0, address(this)); + bytes memory data = plan.encode(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + router.executeActions(data); + + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); assertEq(prevBalance1 - newBalance1, amountIn); assertEq(newBalance0 - prevBalance0, expectedAmountOut); @@ -152,57 +181,65 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 984211133872795298; - tokenPath.push(token0); - tokenPath.push(token1); - tokenPath.push(token2); - IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); + uint256 prevBalance2 = currency2.balanceOfSelf(); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); - uint256 prevBalance2 = token2.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency2, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactIn2Hops"); - router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); - uint256 newBalance2 = token2.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); + uint256 newBalance2 = currency2.balanceOfSelf(); 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); + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + assertEq(currency2.balanceOf(address(router)), 0); } function testRouter_swapExactIn_3Hops() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 976467664490096191; - tokenPath.push(token0); - tokenPath.push(token1); - tokenPath.push(token2); - tokenPath.push(token3); - IV4Router.ExactInputParams memory params = getExactInputParams(tokenPath, amountIn); + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance3 = token3.balanceOf(address(this)); + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance3 = currency3.balanceOfSelf(); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency3, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactIn3Hops"); - router.swap(IV4Router.SwapType.ExactInput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance3 = token3.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance3 = currency3.balanceOfSelf(); 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); + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + assertEq(currency2.balanceOf(address(router)), 0); + assertEq(currency3.balanceOf(address(router)), 0); } function testRouter_swapExactOutputSingle_zeroForOne() public { @@ -215,8 +252,12 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.encode(); + snapStart("RouterExactOutputSingle"); - router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); + router.executeActions(data); snapEnd(); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); @@ -236,7 +277,11 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 prevBalance0 = key0.currency0.balanceOf(address(this)); uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); - router.swap(IV4Router.SwapType.ExactOutputSingle, abi.encode(params)); + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency1, key0.currency0, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -249,19 +294,23 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - tokenPath.push(token0); - tokenPath.push(token1); - IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency1, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactOut1Hop"); - router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); assertEq(prevBalance0 - newBalance0, expectedAmountIn); assertEq(newBalance1 - prevBalance1, amountOut); @@ -271,19 +320,23 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - tokenPath.push(token1); - tokenPath.push(token0); - IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance1 = token1.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency1, currency0, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactOut1Hop"); - router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); assertEq(prevBalance1 - newBalance1, expectedAmountIn); assertEq(newBalance0 - prevBalance0, amountOut); @@ -293,57 +346,65 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1016204441757464409; - tokenPath.push(token0); - tokenPath.push(token1); - tokenPath.push(token2); - IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + 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)); + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance1 = currency1.balanceOfSelf(); + uint256 prevBalance2 = currency2.balanceOfSelf(); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency2, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactOut2Hops"); - router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance1 = token1.balanceOf(address(this)); - uint256 newBalance2 = token2.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance1 = currency1.balanceOfSelf(); + uint256 newBalance2 = currency2.balanceOfSelf(); 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); + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + assertEq(currency2.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); - IV4Router.ExactOutputParams memory params = getExactOutputParams(tokenPath, amountOut); + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + uint256 prevBalance0 = currency0.balanceOfSelf(); + uint256 prevBalance3 = currency3.balanceOfSelf(); - uint256 prevBalance0 = token0.balanceOf(address(this)); - uint256 prevBalance3 = token3.balanceOf(address(this)); + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency3, address(this)); + bytes memory data = plan.encode(); snapStart("RouterExactOut3Hops"); - router.swap(IV4Router.SwapType.ExactOutput, abi.encode(params)); + router.executeActions(data); snapEnd(); - uint256 newBalance0 = token0.balanceOf(address(this)); - uint256 newBalance3 = token3.balanceOf(address(this)); + uint256 newBalance0 = currency0.balanceOfSelf(); + uint256 newBalance3 = currency3.balanceOfSelf(); 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); + assertEq(currency0.balanceOf(address(router)), 0); + assertEq(currency1.balanceOf(address(router)), 0); + assertEq(currency2.balanceOf(address(router)), 0); + assertEq(currency3.balanceOf(address(router)), 0); } function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) @@ -364,39 +425,40 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { ); } - function toCurrency(MockERC20 token) internal pure returns (Currency) { - return Currency.wrap(address(token)); - } - - function getExactInputParams(MockERC20[] memory _tokenPath, uint256 amountIn) + function _getExactInputParams(Currency[] memory _tokenPath, uint256 amountIn) internal pure returns (IV4Router.ExactInputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = PathKey(Currency.wrap(address(_tokenPath[i + 1])), 3000, 60, IHooks(address(0)), bytes("")); + path[i] = PathKey(_tokenPath[i + 1], 3000, 60, IHooks(address(0)), bytes("")); } - params.currencyIn = Currency.wrap(address(_tokenPath[0])); + params.currencyIn = _tokenPath[0]; params.path = path; params.amountIn = uint128(amountIn); params.amountOutMinimum = 0; } - function getExactOutputParams(MockERC20[] memory _tokenPath, uint256 amountOut) + function _getExactOutputParams(Currency[] memory _tokenPath, uint256 amountOut) internal pure returns (IV4Router.ExactOutputParams memory params) { PathKey[] memory path = new PathKey[](_tokenPath.length - 1); for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = PathKey(Currency.wrap(address(_tokenPath[i - 1])), 3000, 60, IHooks(address(0)), bytes("")); + path[i - 1] = PathKey(_tokenPath[i - 1], 3000, 60, IHooks(address(0)), bytes("")); } - params.currencyOut = Currency.wrap(address(_tokenPath[_tokenPath.length - 1])); + params.currencyOut = _tokenPath[_tokenPath.length - 1]; params.path = path; params.amountOut = uint128(amountOut); params.amountInMaximum = type(uint128).max; } + + function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { + plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency)); + plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, recipient)); + } } diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index 5e1a1c8c..fc17acaf 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -21,13 +21,13 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} - function executeAction(bytes calldata params) external isNotLocked { + function executeActions(bytes calldata params) external isNotLocked { _executeActions(params); } function _handleAction(uint256 action, bytes calldata params) internal override { if (action < Actions.SETTLE) { - if (action == Actions.SWAP) _swap(params); + if (action == Actions.SWAP_EXACT_IN) _swap(params); else if (action == Actions.INCREASE_LIQUIDITY) _increaseLiquidity(params); else if (action == Actions.DECREASE_LIQUIDITY) _decreaseLiquidity(params); else if (action == Actions.DONATE) _donate(params); diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index 703367bc..93beb0ee 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -3,17 +3,23 @@ pragma solidity ^0.8.19; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; -import {IV4Router} from "../../../src/interfaces/IV4Router.sol"; import {V4Router} from "../../../src/V4Router.sol"; +import {ReentrancyLock} from "../../../src/base/ReentrancyLock.sol"; +import {Locker} from "../../../src/libraries/Locker.sol"; -contract V4RouterImplementation is V4Router { +contract V4RouterImplementation is V4Router, ReentrancyLock { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} - function swap(IV4Router.SwapType swapType, bytes memory params) external { - _v4Swap(swapType, params); + function executeActions(bytes calldata params) external isNotLocked { + _executeActions(params); } function _pay(address token, address payer, address recipient, uint256 amount) internal override { IERC20Minimal(token).transferFrom(payer, recipient, amount); } + + // TODO would love to have a function in reentrancy lock instead of Locker.get + function _msgSender() internal view override returns (address) { + return Locker.get(); + } } From f1c4746eee158dc2e193aafdc4478145286328a1 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 23 Jul 2024 13:17:10 +0100 Subject: [PATCH 32/50] use isolate --- .../BaseActionsRouter_mock10commands.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 +- test/V4Router.t.sol | 27 +++++++------------ 10 files changed, 18 insertions(+), 27 deletions(-) diff --git a/.forge-snapshots/BaseActionsRouter_mock10commands.snap b/.forge-snapshots/BaseActionsRouter_mock10commands.snap index 283c59f2..9bb19996 100644 --- a/.forge-snapshots/BaseActionsRouter_mock10commands.snap +++ b/.forge-snapshots/BaseActionsRouter_mock10commands.snap @@ -1 +1 @@ -34844 \ No newline at end of file +62960 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 65c99116..26c40684 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -105785 \ No newline at end of file +136041 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index a62dd7d1..766d2c3c 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -156189 \ No newline at end of file +187549 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 8f75dd27..7b2ac7c7 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -213433 \ No newline at end of file +245979 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index c47031c9..cc0e3b7c 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -104778 \ No newline at end of file +134891 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index ce6c95e1..0df6d144 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -104413 \ No newline at end of file +130064 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index 29d1dfec..a38b9d7d 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -155441 \ No newline at end of file +186993 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 775b2ce5..de5f3b45 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -211287 \ No newline at end of file +244025 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 1685f997..12a20e20 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -103312 \ No newline at end of file +133425 \ No newline at end of file diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index fc9a348b..e8120b3e 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -94,9 +94,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(key0.currency0, key0.currency1, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactInputSingle"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactInputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -143,9 +142,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency1, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactIn1Hop"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactIn1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -194,9 +192,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency2, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactIn2Hops"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactIn2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -227,9 +224,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency3, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactIn3Hops"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactIn3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); @@ -256,9 +252,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(key0.currency0, key0.currency1, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactOutputSingle"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactOutputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -305,9 +300,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency1, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactOut1Hop"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -331,9 +325,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency1, currency0, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactOut1Hop"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -359,9 +352,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency2, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactOut2Hops"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactOut2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -392,9 +384,8 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { _finalizePlan(currency0, currency3, address(this)); bytes memory data = plan.encode(); - snapStart("RouterExactOut3Hops"); router.executeActions(data); - snapEnd(); + snapLastCall("RouterExactOut3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); From 9c11de489a7fc421148777dccd5ac3d031519ce0 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 23 Jul 2024 13:32:55 +0100 Subject: [PATCH 33/50] merge conflicts --- .forge-snapshots/RouterExactOut1Hop.snap | 2 +- lib/forge-gas-snapshot | 1 + lib/forge-std | 1 + lib/openzeppelin-contracts | 1 + lib/solmate | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 lib/forge-gas-snapshot create mode 160000 lib/forge-std create mode 160000 lib/openzeppelin-contracts create mode 160000 lib/solmate diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 0df6d144..cf631355 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -130064 \ No newline at end of file +134861 \ No newline at end of file diff --git a/lib/forge-gas-snapshot b/lib/forge-gas-snapshot new file mode 160000 index 00000000..9161f7c0 --- /dev/null +++ b/lib/forge-gas-snapshot @@ -0,0 +1 @@ +Subproject commit 9161f7c0b6c6788a89081e2b3b9c67592b71e689 diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..3d8086d4 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3d8086d4911b36c1874531ce8c367e6cfd028e80 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 00000000..5ae63068 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 5ae630684a0f57de400ef69499addab4c32ac8fb diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 00000000..bfc9c258 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit bfc9c25865a274a7827fea5abf6e4fb64fc64e6c From b2fdb51b92857ecf68444b5552f91e3a3662724e Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Tue, 23 Jul 2024 13:36:12 +0100 Subject: [PATCH 34/50] removing submodules --- lib/forge-gas-snapshot | 1 - lib/forge-std | 1 - lib/openzeppelin-contracts | 1 - lib/solmate | 1 - 4 files changed, 4 deletions(-) delete mode 160000 lib/forge-gas-snapshot delete mode 160000 lib/forge-std delete mode 160000 lib/openzeppelin-contracts delete mode 160000 lib/solmate diff --git a/lib/forge-gas-snapshot b/lib/forge-gas-snapshot deleted file mode 160000 index 9161f7c0..00000000 --- a/lib/forge-gas-snapshot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9161f7c0b6c6788a89081e2b3b9c67592b71e689 diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index 3d8086d4..00000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d8086d4911b36c1874531ce8c367e6cfd028e80 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index 5ae63068..00000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5ae630684a0f57de400ef69499addab4c32ac8fb diff --git a/lib/solmate b/lib/solmate deleted file mode 160000 index bfc9c258..00000000 --- a/lib/solmate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bfc9c25865a274a7827fea5abf6e4fb64fc64e6c From 963c36037b40ab8001c1c8b6481c1d4bffdfa691 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 24 Jul 2024 14:21:38 +0100 Subject: [PATCH 35/50] renaming to stop clashes in UR --- src/V4Router.sol | 4 ++-- src/base/BaseActionsRouter.sol | 8 ++++---- src/libraries/{BytesLib.sol => CalldataBytesLib.sol} | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename src/libraries/{BytesLib.sol => CalldataBytesLib.sol} (99%) diff --git a/src/V4Router.sol b/src/V4Router.sol index 3da8be40..1c2bcee2 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -8,7 +8,7 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {PathKey} from "./libraries/PathKey.sol"; -import {BytesLib} from "./libraries/BytesLib.sol"; +import {CalldataBytesLib} from "./libraries/CalldataBytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {Actions} from "./libraries/Actions.sol"; @@ -20,7 +20,7 @@ import {Actions} from "./libraries/Actions.sol"; abstract contract V4Router is IV4Router, BaseActionsRouter { using CurrencyLibrary for Currency; using TransientStateLibrary for IPoolManager; - using BytesLib for bytes; + using CalldataBytesLib for bytes; constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index db270447..127b10ef 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -3,15 +3,15 @@ pragma solidity ^0.8.24; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {SafeCallback} from "./SafeCallback.sol"; -import {BytesLib} from "../libraries/BytesLib.sol"; +import {CalldataBytesLib} from "../libraries/CalldataBytesLib.sol"; /// @notice Abstract contract for performing a combination of actions on Uniswap v4. /// @dev Suggested uint256 action values are defined in Actions.sol, however any definition can be used abstract contract BaseActionsRouter is SafeCallback { - using BytesLib for bytes; + using CalldataBytesLib for bytes; /// @notice emitted when different numbers of parameters and actions are provided - error LengthMismatch(); + error InputLengthMismatch(); /// @notice emitted when an inheriting contract does not support an action error UnsupportedAction(uint256 action); @@ -30,7 +30,7 @@ abstract contract BaseActionsRouter is SafeCallback { (uint256[] calldata actions, bytes[] calldata params) = data.decodeInCalldata(); uint256 numActions = actions.length; - if (numActions != params.length) revert LengthMismatch(); + if (numActions != params.length) revert InputLengthMismatch(); for (uint256 actionIndex = 0; actionIndex < numActions; actionIndex++) { uint256 action = actions[actionIndex]; diff --git a/src/libraries/BytesLib.sol b/src/libraries/CalldataBytesLib.sol similarity index 99% rename from src/libraries/BytesLib.sol rename to src/libraries/CalldataBytesLib.sol index 79dd066c..971a4a78 100644 --- a/src/libraries/BytesLib.sol +++ b/src/libraries/CalldataBytesLib.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; /// @title Library for Bytes Manipulation -library BytesLib { +library CalldataBytesLib { error SliceOutOfBounds(); /// @notice equivalent to SliceOutOfBounds.selector From 22c0879a6a89b5059c4bfc8cf2713a59481c8bd7 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 14:18:54 +0100 Subject: [PATCH 36/50] PR comments and gas optimisations --- .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 +- src/V4Router.sol | 78 +++++++++---------- src/interfaces/IV4Router.sol | 6 ++ src/libraries/PathKey.sol | 6 +- test/V4Router.t.sol | 3 +- 13 files changed, 56 insertions(+), 55 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 9d0caed6..08eefa46 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -6056 \ No newline at end of file +5836 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 26c40684..9bc7bcfc 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -136041 \ No newline at end of file +135939 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index 766d2c3c..d0a15488 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -187549 \ No newline at end of file +187196 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 7b2ac7c7..0caf6ad3 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -245979 \ No newline at end of file +245374 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index cc0e3b7c..8c02b30b 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -134891 \ No newline at end of file +134797 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index cf631355..0d74cc4d 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -134861 \ No newline at end of file +134757 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index a38b9d7d..b3aeb62d 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -186993 \ No newline at end of file +186629 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index de5f3b45..1950fe74 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -244025 \ No newline at end of file +243406 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index 12a20e20..deb9d876 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -133425 \ No newline at end of file +133331 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 1c2bcee2..177aa171 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -7,7 +7,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; -import {PathKey} from "./libraries/PathKey.sol"; +import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataBytesLib} from "./libraries/CalldataBytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; @@ -18,6 +18,7 @@ import {Actions} from "./libraries/Actions.sol"; /// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` /// An inheriting contract should call _executeActions at the point that they wish actions to be executed abstract contract V4Router is IV4Router, BaseActionsRouter { + using PathKeyLib for PathKey; using CurrencyLibrary for Currency; using TransientStateLibrary for IPoolManager; using CalldataBytesLib for bytes; @@ -26,7 +27,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency - if (action < 0x10) { + if (action < Actions.SETTLE) { if (action == Actions.SWAP_EXACT_IN) { _swapExactInput(abi.decode(params, (IV4Router.ExactInputParams))); } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { @@ -77,16 +78,20 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { function _swapExactInput(IV4Router.ExactInputParams memory params) private { unchecked { + // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountOut; + uint128 amountIn = params.amountIn; + Currency currencyIn = params.currencyIn; + PathKey memory pathKey; for (uint256 i = 0; i < pathLength; i++) { - (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn); - amountOut = - uint128(_swap(poolKey, zeroForOne, int256(-int128(params.amountIn)), 0, params.path[i].hookData)); + pathKey = params.path[i]; + (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); + amountOut = uint128(_swap(poolKey, zeroForOne, int256(-int128(amountIn)), 0, pathKey.hookData)); - params.amountIn = amountOut; - params.currencyIn = params.path[i].intermediateCurrency; + amountIn = amountOut; + currencyIn = pathKey.intermediateCurrency; } if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); @@ -105,18 +110,20 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { function _swapExactOutput(IV4Router.ExactOutputParams memory params) private { unchecked { + // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountIn; + uint128 amountOut = params.amountOut; + Currency currencyOut = params.currencyOut; + PathKey memory pathKey; for (uint256 i = pathLength; i > 0; i--) { - (PoolKey memory poolKey, bool oneForZero) = - _getPoolAndSwapDirection(params.path[i - 1], params.currencyOut); - amountIn = uint128( - -_swap(poolKey, !oneForZero, int256(int128(params.amountOut)), 0, params.path[i - 1].hookData) - ); - - params.amountOut = amountIn; - params.currencyOut = params.path[i - 1].intermediateCurrency; + pathKey = params.path[i - 1]; + (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); + amountIn = uint128(-_swap(poolKey, !oneForZero, int256(int128(amountOut)), 0, pathKey.hookData)); + + amountOut = amountIn; + currencyOut = pathKey.intermediateCurrency; } if (amountIn > params.amountInMaximum) revert TooMuchRequested(); } @@ -129,32 +136,21 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { uint160 sqrtPriceLimitX96, bytes memory hookData ) private returns (int128 reciprocalAmount) { - BalanceDelta delta = poolManager.swap( - poolKey, - IPoolManager.SwapParams( - zeroForOne, - amountSpecified, - sqrtPriceLimitX96 == 0 - ? (zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1) - : sqrtPriceLimitX96 - ), - hookData - ); - - reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); - } - - function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) - private - pure - returns (PoolKey memory poolKey, bool zeroForOne) - { - (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency - ? (currencyIn, params.intermediateCurrency) - : (params.intermediateCurrency, currencyIn); - - zeroForOne = currencyIn == currency0; - poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); + unchecked { + BalanceDelta delta = poolManager.swap( + poolKey, + IPoolManager.SwapParams( + zeroForOne, + amountSpecified, + sqrtPriceLimitX96 == 0 + ? (zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1) + : sqrtPriceLimitX96 + ), + hookData + ); + + reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); + } } function _take(Currency currency, address recipient) private { diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index b0b15544..3dfc4d88 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -8,9 +8,12 @@ import {PathKey} from "../libraries/PathKey.sol"; /// @title IV4Router /// @notice Interface containing all the structs and errors for different v4 swap types interface IV4Router { + /// @notice Emitted when an exactInput swap does not receive its minAmountOut error TooLittleReceived(); + /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn error TooMuchRequested(); + /// @notice Parameters for a single-hop exact-input swap struct ExactInputSingleParams { PoolKey poolKey; bool zeroForOne; @@ -20,6 +23,7 @@ interface IV4Router { bytes hookData; } + /// @notice Parameters for a multi-hop exact-input swap struct ExactInputParams { Currency currencyIn; PathKey[] path; @@ -27,6 +31,7 @@ interface IV4Router { uint128 amountOutMinimum; } + /// @notice Parameters for a single-hop exact-output swap struct ExactOutputSingleParams { PoolKey poolKey; bool zeroForOne; @@ -36,6 +41,7 @@ interface IV4Router { bytes hookData; } + /// @notice Parameters for a multi-hop exact-output swap struct ExactOutputParams { Currency currencyOut; PathKey[] path; diff --git a/src/libraries/PathKey.sol b/src/libraries/PathKey.sol index 352d0a30..38884eb4 100644 --- a/src/libraries/PathKey.sol +++ b/src/libraries/PathKey.sol @@ -19,9 +19,9 @@ library PathKeyLib { pure returns (PoolKey memory poolKey, bool zeroForOne) { - (Currency currency0, Currency currency1) = currencyIn < params.intermediateCurrency - ? (currencyIn, params.intermediateCurrency) - : (params.intermediateCurrency, currencyIn); + Currency currencyOut = params.intermediateCurrency; + (Currency currency0, Currency currency1) = + currencyIn < currencyOut ? (currencyIn, currencyOut) : (currencyOut, currencyIn); zeroForOne = currencyIn == currency0; poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks); diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index e8120b3e..cedfb1a3 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; -import {console2} from "forge-std/console2.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; @@ -15,7 +14,7 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {IV4Router} from "../src/interfaces/IV4Router.sol"; import {V4RouterImplementation} from "./shared/implementation/V4RouterImplementation.sol"; -import {Plan, ActionsRouterPlanner} from "./utils/ActionsRouterPlanner.sol"; +import {Plan, ActionsRouterPlanner} from "./shared/ActionsRouterPlanner.sol"; import {PathKey} from "../src/libraries/PathKey.sol"; import {Actions} from "../src/libraries/Actions.sol"; From 3a4c36db9558e0a3b2320b3c2759a9b7b97f6b42 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 14:22:18 +0100 Subject: [PATCH 37/50] add _getLocker --- src/base/BaseActionsRouter.sol | 2 +- src/base/ReentrancyLock.sol | 4 ++++ test/mocks/MockBaseActionsRouter.sol | 3 +-- test/shared/implementation/V4RouterImplementation.sol | 4 +--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/base/BaseActionsRouter.sol b/src/base/BaseActionsRouter.sol index 127b10ef..ffb7b4fd 100644 --- a/src/base/BaseActionsRouter.sol +++ b/src/base/BaseActionsRouter.sol @@ -49,6 +49,6 @@ abstract contract BaseActionsRouter is SafeCallback { /// @dev The other context functions, _msgData and _msgValue, are not supported by this contract /// In many contracts this will be the address that calls the initial entry point that calls `_executeActions` /// `msg.sender` shouldnt be used, as this will be the v4 pool manager contract that calls `unlockCallback` - /// If using ReentrancyLock.sol, this function can return Locker.get() - locker of the contract + /// If using ReentrancyLock.sol, this function can return _getLocker() function _msgSender() internal view virtual returns (address); } diff --git a/src/base/ReentrancyLock.sol b/src/base/ReentrancyLock.sol index 9f5a4ea2..29fa3d34 100644 --- a/src/base/ReentrancyLock.sol +++ b/src/base/ReentrancyLock.sol @@ -13,4 +13,8 @@ contract ReentrancyLock { _; Locker.set(address(0)); } + + function _getLocker() internal view returns (address) { + return Locker.get(); + } } diff --git a/test/mocks/MockBaseActionsRouter.sol b/test/mocks/MockBaseActionsRouter.sol index fc17acaf..5b137622 100644 --- a/test/mocks/MockBaseActionsRouter.sol +++ b/test/mocks/MockBaseActionsRouter.sol @@ -6,7 +6,6 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BaseActionsRouter} from "../../src/base/BaseActionsRouter.sol"; import {Actions} from "../../src/libraries/Actions.sol"; import {ReentrancyLock} from "../../src/base/ReentrancyLock.sol"; -import {Locker} from "../../src/libraries/Locker.sol"; contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { uint256 public swapCount; @@ -43,7 +42,7 @@ contract MockBaseActionsRouter is BaseActionsRouter, ReentrancyLock { } function _msgSender() internal view override returns (address) { - return Locker.get(); + return _getLocker(); } function _settle(bytes calldata /* params **/ ) internal { diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index 93beb0ee..c5c55b45 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -5,7 +5,6 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {V4Router} from "../../../src/V4Router.sol"; import {ReentrancyLock} from "../../../src/base/ReentrancyLock.sol"; -import {Locker} from "../../../src/libraries/Locker.sol"; contract V4RouterImplementation is V4Router, ReentrancyLock { constructor(IPoolManager _poolManager) V4Router(_poolManager) {} @@ -18,8 +17,7 @@ contract V4RouterImplementation is V4Router, ReentrancyLock { IERC20Minimal(token).transferFrom(payer, recipient, amount); } - // TODO would love to have a function in reentrancy lock instead of Locker.get function _msgSender() internal view override returns (address) { - return Locker.get(); + return _getLocker(); } } From e86036bdc57cd0bb4c09dc7e5dbd16a7b52a4cd5 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 15:16:16 +0100 Subject: [PATCH 38/50] Amount field on settle and take --- .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 +- src/V4Router.sol | 43 +++++++++++++------ src/interfaces/IV4Router.sol | 5 +++ test/V4Router.t.sol | 4 +- 12 files changed, 45 insertions(+), 25 deletions(-) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/RouterBytecode.snap index 08eefa46..9a0adb29 100644 --- a/.forge-snapshots/RouterBytecode.snap +++ b/.forge-snapshots/RouterBytecode.snap @@ -1 +1 @@ -5836 \ No newline at end of file +5989 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/RouterExactIn1Hop.snap index 9bc7bcfc..702a666d 100644 --- a/.forge-snapshots/RouterExactIn1Hop.snap +++ b/.forge-snapshots/RouterExactIn1Hop.snap @@ -1 +1 @@ -135939 \ No newline at end of file +136299 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/RouterExactIn2Hops.snap index d0a15488..3f21923d 100644 --- a/.forge-snapshots/RouterExactIn2Hops.snap +++ b/.forge-snapshots/RouterExactIn2Hops.snap @@ -1 +1 @@ -187196 \ No newline at end of file +187558 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/RouterExactIn3Hops.snap index 0caf6ad3..d0517e01 100644 --- a/.forge-snapshots/RouterExactIn3Hops.snap +++ b/.forge-snapshots/RouterExactIn3Hops.snap @@ -1 +1 @@ -245374 \ No newline at end of file +245734 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/RouterExactInputSingle.snap index 8c02b30b..a5acebe3 100644 --- a/.forge-snapshots/RouterExactInputSingle.snap +++ b/.forge-snapshots/RouterExactInputSingle.snap @@ -1 +1 @@ -134797 \ No newline at end of file +135157 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/RouterExactOut1Hop.snap index 0d74cc4d..6e60e37a 100644 --- a/.forge-snapshots/RouterExactOut1Hop.snap +++ b/.forge-snapshots/RouterExactOut1Hop.snap @@ -1 +1 @@ -134757 \ No newline at end of file +135117 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/RouterExactOut2Hops.snap index b3aeb62d..ccce1b21 100644 --- a/.forge-snapshots/RouterExactOut2Hops.snap +++ b/.forge-snapshots/RouterExactOut2Hops.snap @@ -1 +1 @@ -186629 \ No newline at end of file +186991 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/RouterExactOut3Hops.snap index 1950fe74..13250d9b 100644 --- a/.forge-snapshots/RouterExactOut3Hops.snap +++ b/.forge-snapshots/RouterExactOut3Hops.snap @@ -1 +1 @@ -243406 \ No newline at end of file +243766 \ No newline at end of file diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/RouterExactOutputSingle.snap index deb9d876..ba3e3599 100644 --- a/.forge-snapshots/RouterExactOutputSingle.snap +++ b/.forge-snapshots/RouterExactOutputSingle.snap @@ -1 +1 @@ -133331 \ No newline at end of file +133691 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 177aa171..f67aec11 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -7,6 +7,7 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; + import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataBytesLib} from "./libraries/CalldataBytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; @@ -25,6 +26,9 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} + /// @inheritdoc IV4Router + uint256 public constant override ENTIRE_OPEN_DELTA = 0; + function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency if (action < Actions.SETTLE) { @@ -43,23 +47,27 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { if (action == Actions.SETTLE) { // equivalent: abi.decode(params, (Currency)) Currency currency; + uint256 amount; assembly ("memory-safe") { currency := calldataload(params.offset) + amount := calldataload(add(params.offset, 0x20)) } // TODO support address(this) paying too - _payAndSettle(currency, _msgSender()); + _payAndSettle(currency, _msgSender(), amount); } else if (action == Actions.TAKE) { - // equivalent: abi.decode(params, (Currency, address)) + // equivalent: abi.decode(params, (Currency, address, uint256)) Currency currency; address recipient; + uint256 amount; assembly ("memory-safe") { currency := calldataload(params.offset) recipient := calldataload(add(params.offset, 0x20)) + amount := calldataload(add(params.offset, 0x40)) } - // TODO add min amount?? - _take(currency, recipient); + // TODO should _take have a minAmountOut added slippage check? + _take(currency, recipient, amount); } else { revert UnsupportedAction(action); } @@ -153,21 +161,28 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { } } - function _take(Currency currency, address recipient) private { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta < 0) revert(); - - poolManager.take(currency, recipient, uint256(delta)); + // TODO use DeltaResolver + function _take(Currency currency, address recipient, uint256 amount) private { + if (amount == ENTIRE_OPEN_DELTA) { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta < 0) revert InvalidDeltaForAction(); + amount = uint256(delta); + } + poolManager.take(currency, recipient, amount); } // TODO native support !! - // TODO use currency settle take library - function _payAndSettle(Currency currency, address payer) private { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta > 0) revert(); + // TODO should it have a maxAmountOut added slippage protection? + // TODO use DeltaResolver + function _payAndSettle(Currency currency, address payer, uint256 amount) private { + if (amount == ENTIRE_OPEN_DELTA) { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta > 0) revert InvalidDeltaForAction(); + amount = uint256(-delta); + } poolManager.sync(currency); - _pay(Currency.unwrap(currency), payer, address(poolManager), uint256(-delta)); + _pay(Currency.unwrap(currency), payer, address(poolManager), amount); poolManager.settle(); } diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 3dfc4d88..8bbccebb 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -12,6 +12,11 @@ interface IV4Router { error TooLittleReceived(); /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn error TooMuchRequested(); + /// @notice Emitted trying to settle a positive delta, or take a negative delta + error InvalidDeltaForAction(); + + /// @notice Value used to signal that an entire open delta should be taken/settled + function ENTIRE_OPEN_DELTA() external view virtual returns (uint256); /// @notice Parameters for a single-hop exact-input swap struct ExactInputSingleParams { diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index cedfb1a3..bf270528 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -448,7 +448,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { } function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { - plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency)); - plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, recipient)); + plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency, router.ENTIRE_OPEN_DELTA())); + plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, recipient, router.ENTIRE_OPEN_DELTA())); } } From 22368124b6c756164adbd7ed020bd6d3f80b171f Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 15:22:23 +0100 Subject: [PATCH 39/50] rename snaps --- ...erBytecode.snap => V4Router_Bytecode.snap} | 0 ...tIn1Hop.snap => V4Router_ExactIn1Hop.snap} | 0 ...n2Hops.snap => V4Router_ExactIn2Hops.snap} | 0 ...n3Hops.snap => V4Router_ExactIn3Hops.snap} | 0 ...le.snap => V4Router_ExactInputSingle.snap} | 0 ...ut1Hop.snap => V4Router_ExactOut1Hop.snap} | 0 ...2Hops.snap => V4Router_ExactOut2Hops.snap} | 0 ...3Hops.snap => V4Router_ExactOut3Hops.snap} | 0 ...e.snap => V4Router_ExactOutputSingle.snap} | 0 test/V4Router.t.sol | 46 +++++++++---------- 10 files changed, 23 insertions(+), 23 deletions(-) rename .forge-snapshots/{RouterBytecode.snap => V4Router_Bytecode.snap} (100%) rename .forge-snapshots/{RouterExactIn1Hop.snap => V4Router_ExactIn1Hop.snap} (100%) rename .forge-snapshots/{RouterExactIn2Hops.snap => V4Router_ExactIn2Hops.snap} (100%) rename .forge-snapshots/{RouterExactIn3Hops.snap => V4Router_ExactIn3Hops.snap} (100%) rename .forge-snapshots/{RouterExactInputSingle.snap => V4Router_ExactInputSingle.snap} (100%) rename .forge-snapshots/{RouterExactOut1Hop.snap => V4Router_ExactOut1Hop.snap} (100%) rename .forge-snapshots/{RouterExactOut2Hops.snap => V4Router_ExactOut2Hops.snap} (100%) rename .forge-snapshots/{RouterExactOut3Hops.snap => V4Router_ExactOut3Hops.snap} (100%) rename .forge-snapshots/{RouterExactOutputSingle.snap => V4Router_ExactOutputSingle.snap} (100%) diff --git a/.forge-snapshots/RouterBytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap similarity index 100% rename from .forge-snapshots/RouterBytecode.snap rename to .forge-snapshots/V4Router_Bytecode.snap diff --git a/.forge-snapshots/RouterExactIn1Hop.snap b/.forge-snapshots/V4Router_ExactIn1Hop.snap similarity index 100% rename from .forge-snapshots/RouterExactIn1Hop.snap rename to .forge-snapshots/V4Router_ExactIn1Hop.snap diff --git a/.forge-snapshots/RouterExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap similarity index 100% rename from .forge-snapshots/RouterExactIn2Hops.snap rename to .forge-snapshots/V4Router_ExactIn2Hops.snap diff --git a/.forge-snapshots/RouterExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap similarity index 100% rename from .forge-snapshots/RouterExactIn3Hops.snap rename to .forge-snapshots/V4Router_ExactIn3Hops.snap diff --git a/.forge-snapshots/RouterExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap similarity index 100% rename from .forge-snapshots/RouterExactInputSingle.snap rename to .forge-snapshots/V4Router_ExactInputSingle.snap diff --git a/.forge-snapshots/RouterExactOut1Hop.snap b/.forge-snapshots/V4Router_ExactOut1Hop.snap similarity index 100% rename from .forge-snapshots/RouterExactOut1Hop.snap rename to .forge-snapshots/V4Router_ExactOut1Hop.snap diff --git a/.forge-snapshots/RouterExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap similarity index 100% rename from .forge-snapshots/RouterExactOut2Hops.snap rename to .forge-snapshots/V4Router_ExactOut2Hops.snap diff --git a/.forge-snapshots/RouterExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap similarity index 100% rename from .forge-snapshots/RouterExactOut3Hops.snap rename to .forge-snapshots/V4Router_ExactOut3Hops.snap diff --git a/.forge-snapshots/RouterExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap similarity index 100% rename from .forge-snapshots/RouterExactOutputSingle.snap rename to .forge-snapshots/V4Router_ExactOutputSingle.snap diff --git a/test/V4Router.t.sol b/test/V4Router.t.sol index bf270528..dd6035ce 100644 --- a/test/V4Router.t.sol +++ b/test/V4Router.t.sol @@ -75,11 +75,11 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { plan = ActionsRouterPlanner.init(); } - function testRouter_bytecodeSize() public { - snapSize("RouterBytecode", address(router)); + function test_gas_bytecodeSize() public { + snapSize("V4Router_Bytecode", address(router)); } - function testRouter_swapExactInputSingle_zeroForOne() public { + function test_gas_swapExactInputSingle_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -94,7 +94,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactInputSingle"); + snapLastCall("V4Router_ExactInputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -103,7 +103,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, expectedAmountOut); } - function testRouter_swapExactInputSingle_oneForZero() public { + function test_swapExactInputSingle_oneForZero() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -126,7 +126,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance0 - prevBalance0, expectedAmountOut); } - function testRouter_swapExactIn_1Hop_zeroForOne() public { + function test_gas_swapExactIn_1Hop_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -142,7 +142,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactIn1Hop"); + snapLastCall("V4Router_ExactIn1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -151,7 +151,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, expectedAmountOut); } - function testRouter_swapExactIn_1Hop_oneForZero() public { + function test_swapExactIn_1Hop_oneForZero() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -174,7 +174,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance0 - prevBalance0, expectedAmountOut); } - function testRouter_swapExactIn_2Hops() public { + function test_gas_swapExactIn_2Hops() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 984211133872795298; @@ -192,7 +192,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactIn2Hops"); + snapLastCall("V4Router_ExactIn2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -206,7 +206,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(currency2.balanceOf(address(router)), 0); } - function testRouter_swapExactIn_3Hops() public { + function test_gas_swapExactIn_3Hops() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 976467664490096191; @@ -224,7 +224,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactIn3Hops"); + snapLastCall("V4Router_ExactIn3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); @@ -237,7 +237,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(currency3.balanceOf(address(router)), 0); } - function testRouter_swapExactOutputSingle_zeroForOne() public { + function test_gas_swapExactOutputSingle_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -252,7 +252,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactOutputSingle"); + snapLastCall("V4Router_ExactOutputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -261,7 +261,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, amountOut); } - function testRouter_swapExactOutputSingle_oneForZero() public { + function test_swapExactOutputSingle_oneForZero() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -284,7 +284,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance0 - prevBalance0, amountOut); } - function testRouter_swapExactOut_1Hop_zeroForOne() public { + function test_gas_swapExactOut_1Hop_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -300,7 +300,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactOut1Hop"); + snapLastCall("V4Router_ExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -309,7 +309,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance1 - prevBalance1, amountOut); } - function testRouter_swapExactOut_1Hop_oneForZero() public { + function test_gas_swapExactOut_1Hop_oneForZero() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -325,7 +325,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactOut1Hop"); + snapLastCall("V4Router_ExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -334,7 +334,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(newBalance0 - prevBalance0, amountOut); } - function testRouter_swapExactOut_2Hops() public { + function test_gas_swapExactOut_2Hops() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1016204441757464409; @@ -352,7 +352,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactOut2Hops"); + snapLastCall("V4Router_ExactOut2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -366,7 +366,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(currency2.balanceOf(address(router)), 0); } - function testRouter_swapExactOut_3Hops() public { + function test_gas_swapExactOut_3Hops() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1024467570922834110; @@ -384,7 +384,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("RouterExactOut3Hops"); + snapLastCall("V4Router_ExactOut3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); From 456d0c8bd07fe7b3b86a4140b60d26b7b139989b Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 17:00:50 +0100 Subject: [PATCH 40/50] correcy casting order --- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- src/V4Router.sol | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 9a0adb29..3bb51f31 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -5989 \ No newline at end of file +6019 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop.snap b/.forge-snapshots/V4Router_ExactIn1Hop.snap index 702a666d..3b92e4cb 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop.snap @@ -1 +1 @@ -136299 \ No newline at end of file +136297 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 3f21923d..287528b9 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187558 \ No newline at end of file +187554 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index d0517e01..a56201a8 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -245734 \ No newline at end of file +245728 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop.snap b/.forge-snapshots/V4Router_ExactOut1Hop.snap index 6e60e37a..08fc8edf 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop.snap @@ -1 +1 @@ -135117 \ No newline at end of file +135115 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index ccce1b21..26e7b14b 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186991 \ No newline at end of file +186987 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 13250d9b..34ee5d24 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -243766 \ No newline at end of file +243760 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index f67aec11..36895c5a 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -96,7 +96,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { for (uint256 i = 0; i < pathLength; i++) { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); - amountOut = uint128(_swap(poolKey, zeroForOne, int256(-int128(amountIn)), 0, pathKey.hookData)); + amountOut = uint128(_swap(poolKey, zeroForOne, -int256(uint256(amountIn)), 0, pathKey.hookData)); amountIn = amountOut; currencyIn = pathKey.intermediateCurrency; @@ -128,7 +128,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); - amountIn = uint128(-_swap(poolKey, !oneForZero, int256(int128(amountOut)), 0, pathKey.hookData)); + amountIn = uint128(-_swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData)); amountOut = amountIn; currencyOut = pathKey.intermediateCurrency; From 5ceaadf8f323fe9f73e3f13bdc76408f8e113501 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Thu, 25 Jul 2024 19:36:14 +0100 Subject: [PATCH 41/50] Add deltaresolver to router --- .forge-snapshots/PositionManager_collect.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_decreaseLiquidity.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...sitionManager_increaseLiquidity_erc20.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- .forge-snapshots/PositionManager_mint.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- src/V4Router.sol | 42 +++---------------- src/base/DeltaResolver.sol | 22 ++++++++++ src/interfaces/IV4Router.sol | 5 --- .../implementation/V4RouterImplementation.sol | 5 ++- 31 files changed, 58 insertions(+), 70 deletions(-) diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect.snap index 0a40b6de..cfb8ac8b 100644 --- a/.forge-snapshots/PositionManager_collect.snap +++ b/.forge-snapshots/PositionManager_collect.snap @@ -1 +1 @@ -162252 \ No newline at end of file +162329 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index b1d6c390..6754592f 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -153467 \ No newline at end of file +153544 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index 0a40b6de..cfb8ac8b 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -162252 \ No newline at end of file +162329 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity.snap index ea1902b0..7af9d048 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity.snap @@ -1 +1 @@ -127630 \ No newline at end of file +127707 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index 84428c12..c9afcedf 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -119010 \ No newline at end of file +119087 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index 815856df..e907471b 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -140511 \ No newline at end of file +140588 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap index ef3e78d9..8e523bcc 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap @@ -1 +1 @@ -156808 \ No newline at end of file +156885 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 2f8ac785..15aef1a8 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -142246 \ No newline at end of file +142323 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index bec5766b..3a9ee4a0 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -148668 \ No newline at end of file +148711 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 77843aea..4fd45a56 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -184822 \ No newline at end of file +184899 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap index 7d0d1998..1ba4dc4d 100644 --- a/.forge-snapshots/PositionManager_mint.snap +++ b/.forge-snapshots/PositionManager_mint.snap @@ -1 +1 @@ -417817 \ No newline at end of file +417894 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index e72e5797..9da7e959 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -366027 \ No newline at end of file +366104 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap index 8d174bbb..068a69ca 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap @@ -1 +1 @@ -372965 \ No newline at end of file +373042 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index c50781f2..7d357acb 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -360499 \ No newline at end of file +360576 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index df62dc1e..46118fa5 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -361141 \ No newline at end of file +361218 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index b2ba0b46..a9911773 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -286723 \ No newline at end of file +286800 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index f7484251..8c5961f7 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -366517 \ No newline at end of file +366594 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 7ab9a287..1b83b94b 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -462307 \ No newline at end of file +462384 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 3bb51f31..6fb634b7 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6019 \ No newline at end of file +6189 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop.snap b/.forge-snapshots/V4Router_ExactIn1Hop.snap index 3b92e4cb..5d86ac58 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop.snap @@ -1 +1 @@ -136297 \ No newline at end of file +136315 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 287528b9..cc6c4227 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187554 \ No newline at end of file +187572 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index a56201a8..7e4854c8 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -245728 \ No newline at end of file +245746 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index a5acebe3..a668f384 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -135157 \ No newline at end of file +135175 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop.snap b/.forge-snapshots/V4Router_ExactOut1Hop.snap index 08fc8edf..95bb97c7 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop.snap @@ -1 +1 @@ -135115 \ No newline at end of file +135133 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 26e7b14b..8fea4ea5 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186987 \ No newline at end of file +187005 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 34ee5d24..7fb4b5ee 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -243760 \ No newline at end of file +243778 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index ba3e3599..506482b3 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -133691 \ No newline at end of file +133709 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 36895c5a..a70a7d68 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; @@ -12,23 +11,20 @@ import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataBytesLib} from "./libraries/CalldataBytesLib.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; +import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools /// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` /// An inheriting contract should call _executeActions at the point that they wish actions to be executed -abstract contract V4Router is IV4Router, BaseActionsRouter { +abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using PathKeyLib for PathKey; - using CurrencyLibrary for Currency; - using TransientStateLibrary for IPoolManager; using CalldataBytesLib for bytes; constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} - /// @inheritdoc IV4Router - uint256 public constant override ENTIRE_OPEN_DELTA = 0; - + // TODO native support !! function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency if (action < Actions.SETTLE) { @@ -45,7 +41,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { } } else { if (action == Actions.SETTLE) { - // equivalent: abi.decode(params, (Currency)) + // equivalent: abi.decode(params, (Currency, uint256)) Currency currency; uint256 amount; assembly ("memory-safe") { @@ -54,7 +50,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { } // TODO support address(this) paying too - _payAndSettle(currency, _msgSender(), amount); + // TODO should it have a maxAmountOut added slippage protection? + _settle(currency, _msgSender(), amount); } else if (action == Actions.TAKE) { // equivalent: abi.decode(params, (Currency, address, uint256)) Currency currency; @@ -160,31 +157,4 @@ abstract contract V4Router is IV4Router, BaseActionsRouter { reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); } } - - // TODO use DeltaResolver - function _take(Currency currency, address recipient, uint256 amount) private { - if (amount == ENTIRE_OPEN_DELTA) { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta < 0) revert InvalidDeltaForAction(); - amount = uint256(delta); - } - poolManager.take(currency, recipient, amount); - } - - // TODO native support !! - // TODO should it have a maxAmountOut added slippage protection? - // TODO use DeltaResolver - function _payAndSettle(Currency currency, address payer, uint256 amount) private { - if (amount == ENTIRE_OPEN_DELTA) { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta > 0) revert InvalidDeltaForAction(); - amount = uint256(-delta); - } - - poolManager.sync(currency); - _pay(Currency.unwrap(currency), payer, address(poolManager), amount); - poolManager.settle(); - } - - function _pay(address token, address payer, address recipient, uint256 amount) internal virtual; } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index 289acc10..c088eb68 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -1,17 +1,33 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {ImmutableState} from "./ImmutableState.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; /// @notice Abstract contract used to sync, send, and settle funds to the pool manager /// @dev Note that sync() is called before any erc-20 transfer in `settle`. abstract contract DeltaResolver is ImmutableState { + using TransientStateLibrary for IPoolManager; + + /// @notice Value used to signal that an entire open delta should be taken/settled + uint256 public constant ENTIRE_OPEN_DELTA = 0; + + /// @notice Emitted trying to settle a positive delta, or take a negative delta + error InvalidDeltaForAction(); + /// @notice Take an amount of currency out of the PoolManager /// @param currency Currency to take /// @param recipient Address to receive the currency /// @param amount Amount to take function _take(Currency currency, address recipient, uint256 amount) internal { + if (amount == ENTIRE_OPEN_DELTA) { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta < 0) revert InvalidDeltaForAction(); + amount = uint256(delta); + } + poolManager.take(currency, recipient, amount); } @@ -21,6 +37,12 @@ abstract contract DeltaResolver is ImmutableState { /// @param payer Address of the payer /// @param amount Amount to send function _settle(Currency currency, address payer, uint256 amount) internal { + if (amount == ENTIRE_OPEN_DELTA) { + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta > 0) revert InvalidDeltaForAction(); + amount = uint256(-delta); + } + if (currency.isNative()) { poolManager.settle{value: amount}(); } else { diff --git a/src/interfaces/IV4Router.sol b/src/interfaces/IV4Router.sol index 8bbccebb..3dfc4d88 100644 --- a/src/interfaces/IV4Router.sol +++ b/src/interfaces/IV4Router.sol @@ -12,11 +12,6 @@ interface IV4Router { error TooLittleReceived(); /// @notice Emitted when an exactOutput is asked for more than its maxAmountIn error TooMuchRequested(); - /// @notice Emitted trying to settle a positive delta, or take a negative delta - error InvalidDeltaForAction(); - - /// @notice Value used to signal that an entire open delta should be taken/settled - function ENTIRE_OPEN_DELTA() external view virtual returns (uint256); /// @notice Parameters for a single-hop exact-input swap struct ExactInputSingleParams { diff --git a/test/shared/implementation/V4RouterImplementation.sol b/test/shared/implementation/V4RouterImplementation.sol index c5c55b45..eaaaf9ef 100644 --- a/test/shared/implementation/V4RouterImplementation.sol +++ b/test/shared/implementation/V4RouterImplementation.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.19; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {IERC20Minimal} from "@uniswap/v4-core/src/interfaces/external/IERC20Minimal.sol"; import {V4Router} from "../../../src/V4Router.sol"; import {ReentrancyLock} from "../../../src/base/ReentrancyLock.sol"; @@ -13,8 +14,8 @@ contract V4RouterImplementation is V4Router, ReentrancyLock { _executeActions(params); } - function _pay(address token, address payer, address recipient, uint256 amount) internal override { - IERC20Minimal(token).transferFrom(payer, recipient, amount); + function _pay(Currency token, address payer, uint256 amount) internal override { + IERC20Minimal(Currency.unwrap(token)).transferFrom(payer, address(poolManager), amount); } function _msgSender() internal view override returns (address) { From 20224d76e12dc3665bf3d83034d16800c6f2011f Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 26 Jul 2024 14:53:55 +0100 Subject: [PATCH 42/50] take_all command, remove from delta resolver --- .forge-snapshots/PositionManager_collect.snap | 2 +- .../PositionManager_collect_native.snap | 2 +- .../PositionManager_collect_sameRange.snap | 2 +- .../PositionManager_decreaseLiquidity.snap | 2 +- ...itionManager_decreaseLiquidity_native.snap | 2 +- ...nager_decrease_sameRange_allLiquidity.snap | 2 +- ...sitionManager_increaseLiquidity_erc20.snap | 2 +- ...itionManager_increaseLiquidity_native.snap | 2 +- ...crease_autocompoundExactUnclaimedFees.snap | 2 +- ...increase_autocompoundExcessFeesCredit.snap | 2 +- .forge-snapshots/PositionManager_mint.snap | 2 +- .../PositionManager_mint_native.snap | 2 +- .../PositionManager_mint_nativeWithSweep.snap | 2 +- .../PositionManager_mint_onSameTickLower.snap | 2 +- .../PositionManager_mint_onSameTickUpper.snap | 2 +- .../PositionManager_mint_sameRange.snap | 2 +- ...anager_mint_warmedPool_differentRange.snap | 2 +- ...tionManager_multicall_initialize_mint.snap | 2 +- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .forge-snapshots/V4Router_ExactIn1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .forge-snapshots/V4Router_ExactOut1Hop.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- src/V4Router.sol | 28 +++++++++------ src/base/DeltaResolver.sol | 19 ---------- src/libraries/Actions.sol | 13 ++++--- src/libraries/CalldataBytesLib.sol | 35 ------------------- test/{ => router}/V4Router.t.sol | 14 ++++---- 32 files changed, 61 insertions(+), 102 deletions(-) rename test/{ => router}/V4Router.t.sol (97%) diff --git a/.forge-snapshots/PositionManager_collect.snap b/.forge-snapshots/PositionManager_collect.snap index cfb8ac8b..0a40b6de 100644 --- a/.forge-snapshots/PositionManager_collect.snap +++ b/.forge-snapshots/PositionManager_collect.snap @@ -1 +1 @@ -162329 \ No newline at end of file +162252 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_native.snap b/.forge-snapshots/PositionManager_collect_native.snap index 6754592f..b1d6c390 100644 --- a/.forge-snapshots/PositionManager_collect_native.snap +++ b/.forge-snapshots/PositionManager_collect_native.snap @@ -1 +1 @@ -153544 \ No newline at end of file +153467 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_collect_sameRange.snap b/.forge-snapshots/PositionManager_collect_sameRange.snap index cfb8ac8b..0a40b6de 100644 --- a/.forge-snapshots/PositionManager_collect_sameRange.snap +++ b/.forge-snapshots/PositionManager_collect_sameRange.snap @@ -1 +1 @@ -162329 \ No newline at end of file +162252 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity.snap b/.forge-snapshots/PositionManager_decreaseLiquidity.snap index 7af9d048..ea1902b0 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity.snap @@ -1 +1 @@ -127707 \ No newline at end of file +127630 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap index c9afcedf..84428c12 100644 --- a/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_decreaseLiquidity_native.snap @@ -1 +1 @@ -119087 \ No newline at end of file +119010 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap index e907471b..815856df 100644 --- a/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap +++ b/.forge-snapshots/PositionManager_decrease_sameRange_allLiquidity.snap @@ -1 +1 @@ -140588 \ No newline at end of file +140511 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap index 8e523bcc..ef3e78d9 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap @@ -1 +1 @@ -156885 \ No newline at end of file +156808 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap index 15aef1a8..2f8ac785 100644 --- a/.forge-snapshots/PositionManager_increaseLiquidity_native.snap +++ b/.forge-snapshots/PositionManager_increaseLiquidity_native.snap @@ -1 +1 @@ -142323 \ No newline at end of file +142246 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap index 3a9ee4a0..bec5766b 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExactUnclaimedFees.snap @@ -1 +1 @@ -148711 \ No newline at end of file +148668 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap index 4fd45a56..77843aea 100644 --- a/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap +++ b/.forge-snapshots/PositionManager_increase_autocompoundExcessFeesCredit.snap @@ -1 +1 @@ -184899 \ No newline at end of file +184822 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint.snap index 1ba4dc4d..7d0d1998 100644 --- a/.forge-snapshots/PositionManager_mint.snap +++ b/.forge-snapshots/PositionManager_mint.snap @@ -1 +1 @@ -417894 \ No newline at end of file +417817 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_native.snap b/.forge-snapshots/PositionManager_mint_native.snap index 9da7e959..e72e5797 100644 --- a/.forge-snapshots/PositionManager_mint_native.snap +++ b/.forge-snapshots/PositionManager_mint_native.snap @@ -1 +1 @@ -366104 \ No newline at end of file +366027 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap index 068a69ca..8d174bbb 100644 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap @@ -1 +1 @@ -373042 \ No newline at end of file +372965 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap index 7d357acb..c50781f2 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickLower.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickLower.snap @@ -1 +1 @@ -360576 \ No newline at end of file +360499 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap index 46118fa5..df62dc1e 100644 --- a/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap +++ b/.forge-snapshots/PositionManager_mint_onSameTickUpper.snap @@ -1 +1 @@ -361218 \ No newline at end of file +361141 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_sameRange.snap b/.forge-snapshots/PositionManager_mint_sameRange.snap index a9911773..b2ba0b46 100644 --- a/.forge-snapshots/PositionManager_mint_sameRange.snap +++ b/.forge-snapshots/PositionManager_mint_sameRange.snap @@ -1 +1 @@ -286800 \ No newline at end of file +286723 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap index 8c5961f7..f7484251 100644 --- a/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap +++ b/.forge-snapshots/PositionManager_mint_warmedPool_differentRange.snap @@ -1 +1 @@ -366594 \ No newline at end of file +366517 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap index 1b83b94b..7ab9a287 100644 --- a/.forge-snapshots/PositionManager_multicall_initialize_mint.snap +++ b/.forge-snapshots/PositionManager_multicall_initialize_mint.snap @@ -1 +1 @@ -462384 \ No newline at end of file +462307 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 6fb634b7..f3a22372 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6189 \ No newline at end of file +6129 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop.snap b/.forge-snapshots/V4Router_ExactIn1Hop.snap index 5d86ac58..432cfe4e 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop.snap @@ -1 +1 @@ -136315 \ No newline at end of file +135962 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index cc6c4227..2f04aeb6 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187572 \ No newline at end of file +187217 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 7e4854c8..50fc054a 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -245746 \ No newline at end of file +245393 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index a668f384..258b5b38 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -135175 \ No newline at end of file +134822 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop.snap b/.forge-snapshots/V4Router_ExactOut1Hop.snap index 95bb97c7..f62773b2 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop.snap @@ -1 +1 @@ -135133 \ No newline at end of file +134780 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 8fea4ea5..0e826974 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -187005 \ No newline at end of file +186650 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 7fb4b5ee..82ffae14 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -243778 \ No newline at end of file +243425 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index 506482b3..a9f7fc11 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -133709 \ No newline at end of file +133356 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index a70a7d68..92e53d6d 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -6,6 +6,8 @@ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataBytesLib} from "./libraries/CalldataBytesLib.sol"; @@ -21,6 +23,7 @@ import {Actions} from "./libraries/Actions.sol"; abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using PathKeyLib for PathKey; using CalldataBytesLib for bytes; + using TransientStateLibrary for IPoolManager; constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} @@ -40,31 +43,34 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { revert UnsupportedAction(action); } } else { - if (action == Actions.SETTLE) { - // equivalent: abi.decode(params, (Currency, uint256)) + if (action == Actions.SETTLE_ALL) { + // equivalent: abi.decode(params, (Currency)) Currency currency; - uint256 amount; assembly ("memory-safe") { currency := calldataload(params.offset) - amount := calldataload(add(params.offset, 0x20)) } + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta > 0) revert InvalidDeltaForAction(); + // TODO support address(this) paying too // TODO should it have a maxAmountOut added slippage protection? - _settle(currency, _msgSender(), amount); - } else if (action == Actions.TAKE) { - // equivalent: abi.decode(params, (Currency, address, uint256)) + _settle(currency, _msgSender(), uint256(-delta)); + } else if (action == Actions.TAKE_ALL) { + // equivalent: abi.decode(params, (Currency, address)) Currency currency; address recipient; - uint256 amount; assembly ("memory-safe") { currency := calldataload(params.offset) recipient := calldataload(add(params.offset, 0x20)) - amount := calldataload(add(params.offset, 0x40)) } + int256 delta = poolManager.currencyDelta(address(this), currency); + if (delta < 0) revert InvalidDeltaForAction(); + // TODO should _take have a minAmountOut added slippage check? - _take(currency, recipient, amount); + // TODO recipient mapping + _take(currency, recipient, uint256(delta)); } else { revert UnsupportedAction(action); } @@ -157,4 +163,6 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); } } + + function _mapAmount(uint256 amount) internal returns (uint256) {} } diff --git a/src/base/DeltaResolver.sol b/src/base/DeltaResolver.sol index c088eb68..e67721cc 100644 --- a/src/base/DeltaResolver.sol +++ b/src/base/DeltaResolver.sol @@ -1,19 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.24; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {ImmutableState} from "./ImmutableState.sol"; -import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; /// @notice Abstract contract used to sync, send, and settle funds to the pool manager /// @dev Note that sync() is called before any erc-20 transfer in `settle`. abstract contract DeltaResolver is ImmutableState { - using TransientStateLibrary for IPoolManager; - - /// @notice Value used to signal that an entire open delta should be taken/settled - uint256 public constant ENTIRE_OPEN_DELTA = 0; - /// @notice Emitted trying to settle a positive delta, or take a negative delta error InvalidDeltaForAction(); @@ -22,12 +15,6 @@ abstract contract DeltaResolver is ImmutableState { /// @param recipient Address to receive the currency /// @param amount Amount to take function _take(Currency currency, address recipient, uint256 amount) internal { - if (amount == ENTIRE_OPEN_DELTA) { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta < 0) revert InvalidDeltaForAction(); - amount = uint256(delta); - } - poolManager.take(currency, recipient, amount); } @@ -37,12 +24,6 @@ abstract contract DeltaResolver is ImmutableState { /// @param payer Address of the payer /// @param amount Amount to send function _settle(Currency currency, address payer, uint256 amount) internal { - if (amount == ENTIRE_OPEN_DELTA) { - int256 delta = poolManager.currencyDelta(address(this), currency); - if (delta > 0) revert InvalidDeltaForAction(); - amount = uint256(-delta); - } - if (currency.isNative()) { poolManager.settle{value: amount}(); } else { diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 3892a7ae..ee8300b3 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -15,10 +15,15 @@ library Actions { // closing deltas on the pool manager uint256 constant SETTLE = 0x10; - uint256 constant TAKE = 0x11; - uint256 constant CLOSE_CURRENCY = 0x12; - uint256 constant CLOSE_PAIR = 0x13; - uint256 constant CLEAR = 0x14; + uint256 constant SETTLE_ALL = 0x11; + + uint256 constant TAKE = 0x12; + uint256 constant TAKE_ALL = 0x13; + uint256 constant TAKE_PORTION = 0x14; + + uint256 constant CLOSE_CURRENCY = 0x15; + uint256 constant CLOSE_PAIR = 0x16; + uint256 constant CLEAR = 0x17; // minting/burning 6909s to close deltas uint256 constant MINT_6909 = 0x20; diff --git a/src/libraries/CalldataBytesLib.sol b/src/libraries/CalldataBytesLib.sol index 971a4a78..59d3737d 100644 --- a/src/libraries/CalldataBytesLib.sol +++ b/src/libraries/CalldataBytesLib.sol @@ -8,41 +8,6 @@ library CalldataBytesLib { /// @notice equivalent to SliceOutOfBounds.selector bytes4 constant SLICE_ERROR_SELECTOR = 0x3b99b53d; - /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array - /// @dev The decoding of `length` and `offset` is universal, - /// whereas the type declaration of `res` instructs the compiler how to read it. - /// @param _bytes The input bytes string to slice - /// @param _arg The index of the argument to extract - /// @return length Length of the array - /// @return offset Pointer to the data part of the array - function toLengthOffset(bytes calldata _bytes, uint256 _arg) - internal - pure - returns (uint256 length, uint256 offset) - { - uint256 relativeOffset; - assembly { - // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. - // shl(5, x) is equivalent to mul(32, x) - let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg)))) - length := calldataload(lengthPtr) - offset := add(lengthPtr, 0x20) - relativeOffset := sub(offset, _bytes.offset) - } - if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds(); - } - - /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` - /// @param _bytes The input bytes string to extract a bytes string from - /// @param _arg The index of the argument to extract - function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { - (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); - assembly { - res.length := length - res.offset := offset - } - } - /// @notice Performs the equivalent of `abi.decode(data, (uint256[], bytes[]))` in calldata /// @param _bytes The input bytes string to extract input arrays from /// @return actions The uint256 calldata array of actions diff --git a/test/V4Router.t.sol b/test/router/V4Router.t.sol similarity index 97% rename from test/V4Router.t.sol rename to test/router/V4Router.t.sol index dd6035ce..11629a71 100644 --- a/test/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -12,11 +12,11 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; -import {IV4Router} from "../src/interfaces/IV4Router.sol"; -import {V4RouterImplementation} from "./shared/implementation/V4RouterImplementation.sol"; -import {Plan, ActionsRouterPlanner} from "./shared/ActionsRouterPlanner.sol"; -import {PathKey} from "../src/libraries/PathKey.sol"; -import {Actions} from "../src/libraries/Actions.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {V4RouterImplementation} from "../shared/implementation/V4RouterImplementation.sol"; +import {Plan, ActionsRouterPlanner} from "../shared/ActionsRouterPlanner.sol"; +import {PathKey} from "../../src/libraries/PathKey.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; contract V4RouterTest is Test, Deployers, GasSnapshot { using CurrencyLibrary for Currency; @@ -448,7 +448,7 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { } function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { - plan = plan.add(Actions.SETTLE, abi.encode(inputCurrency, router.ENTIRE_OPEN_DELTA())); - plan = plan.add(Actions.TAKE, abi.encode(outputCurrency, recipient, router.ENTIRE_OPEN_DELTA())); + plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); } } From d02049b3edc78b266768f013f2b3ab2e60919822 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 26 Jul 2024 15:29:50 +0100 Subject: [PATCH 43/50] Routing test helper --- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- src/libraries/Actions.sol | 2 +- .../{Gas.t.sol => PositionManager.gas.t.sol} | 2 +- test/router/V4Router.t.sol | 113 +---------------- test/shared/RoutingTestHelpers.sol | 115 ++++++++++++++++++ 8 files changed, 124 insertions(+), 116 deletions(-) rename test/position-managers/{Gas.t.sol => PositionManager.gas.t.sol} (99%) create mode 100644 test/shared/RoutingTestHelpers.sol diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 2f04aeb6..412a7a96 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187217 \ No newline at end of file +187241 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 50fc054a..35f13a5b 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -245393 \ No newline at end of file +238549 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 0e826974..70c1d010 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186650 \ No newline at end of file +186674 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index 82ffae14..a8031d46 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -243425 \ No newline at end of file +238612 \ No newline at end of file diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index ee8300b3..53b4f680 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -16,7 +16,7 @@ library Actions { // closing deltas on the pool manager uint256 constant SETTLE = 0x10; uint256 constant SETTLE_ALL = 0x11; - + uint256 constant TAKE = 0x12; uint256 constant TAKE_ALL = 0x13; uint256 constant TAKE_PORTION = 0x14; diff --git a/test/position-managers/Gas.t.sol b/test/position-managers/PositionManager.gas.t.sol similarity index 99% rename from test/position-managers/Gas.t.sol rename to test/position-managers/PositionManager.gas.t.sol index 9d36833f..2d2af709 100644 --- a/test/position-managers/Gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -23,7 +23,7 @@ import {IMulticall} from "../../src/interfaces/IMulticall.sol"; import {Planner} from "../shared/Planner.sol"; import {PosmTestSetup} from "../shared/PosmTestSetup.sol"; -contract GasTest is Test, PosmTestSetup, GasSnapshot { +contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { using FixedPointMathLib for uint256; using CurrencyLibrary for Currency; using PoolIdLibrary for PoolKey; diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 11629a71..79ebcaae 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -3,75 +3,23 @@ pragma solidity ^0.8.19; import {Test} from "forge-std/Test.sol"; import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; -import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; -import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; -import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; -import {V4RouterImplementation} from "../shared/implementation/V4RouterImplementation.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, ActionsRouterPlanner} from "../shared/ActionsRouterPlanner.sol"; -import {PathKey} from "../../src/libraries/PathKey.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -contract V4RouterTest is Test, Deployers, GasSnapshot { +contract V4RouterTest is RoutingTestHelpers, GasSnapshot { using CurrencyLibrary for Currency; using ActionsRouterPlanner for Plan; - PoolModifyLiquidityTest positionManager; - V4RouterImplementation router; - - // currency0 and currency1 are defined in Deployers.sol - Currency currency2; - Currency currency3; - - PoolKey key0; - PoolKey key1; - PoolKey key2; - - Currency[] tokenPath; - - Plan plan; - function setUp() public { - deployFreshManagerAndRouters(); - - router = new V4RouterImplementation(manager); - positionManager = new PoolModifyLiquidityTest(manager); - - MockERC20 token0 = new MockERC20("Test0", "0", 18); - token0.mint(address(this), 2 ** 128); - currency0 = Currency.wrap(address(token0)); - - MockERC20 token1 = new MockERC20("Test1", "1", 18); - token1.mint(address(this), 2 ** 128); - currency1 = Currency.wrap(address(token1)); - - MockERC20 token2 = new MockERC20("Test2", "2", 18); - token2.mint(address(this), 2 ** 128); - currency2 = Currency.wrap(address(token2)); - - MockERC20 token3 = new MockERC20("Test3", "3", 18); - token3.mint(address(this), 2 ** 128); - currency3 = Currency.wrap(address(token3)); - - key0 = createPoolKey(token0, token1, address(0)); - key1 = createPoolKey(token1, token2, address(0)); - key2 = createPoolKey(token2, token3, address(0)); - - 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); - + setupRouterCurrenciesAndPoolsWithLiquidity(); plan = ActionsRouterPlanner.init(); } @@ -396,59 +344,4 @@ contract V4RouterTest is Test, Deployers, GasSnapshot { assertEq(currency2.balanceOf(address(router)), 0); assertEq(currency3.balanceOf(address(router)), 0); } - - function createPoolKey(MockERC20 tokenA, MockERC20 tokenB, address hookAddr) - internal - pure - returns (PoolKey memory) - { - if (address(tokenA) > address(tokenB)) (tokenA, tokenB) = (tokenB, tokenA); - return PoolKey(Currency.wrap(address(tokenA)), Currency.wrap(address(tokenB)), 3000, 60, IHooks(hookAddr)); - } - - function setupPool(PoolKey memory poolKey) internal { - manager.initialize(poolKey, SQRT_PRICE_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.modifyLiquidity( - poolKey, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether, 0), "0x" - ); - } - - function _getExactInputParams(Currency[] memory _tokenPath, uint256 amountIn) - internal - pure - returns (IV4Router.ExactInputParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = 0; i < _tokenPath.length - 1; i++) { - path[i] = PathKey(_tokenPath[i + 1], 3000, 60, IHooks(address(0)), bytes("")); - } - - params.currencyIn = _tokenPath[0]; - params.path = path; - params.amountIn = uint128(amountIn); - params.amountOutMinimum = 0; - } - - function _getExactOutputParams(Currency[] memory _tokenPath, uint256 amountOut) - internal - pure - returns (IV4Router.ExactOutputParams memory params) - { - PathKey[] memory path = new PathKey[](_tokenPath.length - 1); - for (uint256 i = _tokenPath.length - 1; i > 0; i--) { - path[i - 1] = PathKey(_tokenPath[i - 1], 3000, 60, IHooks(address(0)), bytes("")); - } - - params.currencyOut = _tokenPath[_tokenPath.length - 1]; - params.path = path; - params.amountOut = uint128(amountOut); - params.amountInMaximum = type(uint128).max; - } - - function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { - plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); - } } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol new file mode 100644 index 00000000..6a971cee --- /dev/null +++ b/test/shared/RoutingTestHelpers.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; +import {V4RouterImplementation} from "../shared/implementation/V4RouterImplementation.sol"; +import {Plan, ActionsRouterPlanner} from "../shared/ActionsRouterPlanner.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {PathKey} from "../../src/libraries/PathKey.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; + +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {PositionManager} from "../../src/PositionManager.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {LiquidityOperations} from "./LiquidityOperations.sol"; +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; + +/// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. +contract RoutingTestHelpers is Test, Deployers { + using ActionsRouterPlanner for Plan; + + PoolModifyLiquidityTest positionManager; + V4RouterImplementation router; + + PoolKey key0; + PoolKey key1; + PoolKey key2; + + // currency0 and currency1 are defined in Deployers.sol + Currency currency2; + Currency currency3; + + Currency[] tokenPath; + Plan plan; + + function setupRouterCurrenciesAndPoolsWithLiquidity() public { + deployFreshManager(); + + router = new V4RouterImplementation(manager); + positionManager = new PoolModifyLiquidityTest(manager); + + MockERC20[] memory tokens = deployTokensMintAndApprove(4); + + currency0 = Currency.wrap(address(tokens[0])); + currency1 = Currency.wrap(address(tokens[1])); + currency2 = Currency.wrap(address(tokens[2])); + currency3 = Currency.wrap(address(tokens[3])); + + key0 = createPoolWithLiquidity(currency0, currency1, address(0)); + key1 = createPoolWithLiquidity(currency1, currency2, address(0)); + key2 = createPoolWithLiquidity(currency2, currency3, address(0)); + } + + function deployTokensMintAndApprove(uint8 count) internal returns (MockERC20[] memory) { + MockERC20[] memory tokens = deployTokens(count, 2 ** 128); + for (uint256 i = 0; i < count; i++) { + tokens[i].approve(address(router), type(uint256).max); + } + return tokens; + } + + function createPoolWithLiquidity(Currency currencyA, Currency currencyB, address hookAddr) + internal + returns (PoolKey memory _key) + { + if (Currency.unwrap(currencyA) > Currency.unwrap(currencyB)) (currencyA, currencyB) = (currencyB, currencyA); + _key = PoolKey(currencyA, currencyB, 3000, 60, IHooks(hookAddr)); + + manager.initialize(_key, SQRT_PRICE_1_1, ZERO_BYTES); + MockERC20(Currency.unwrap(currencyA)).approve(address(positionManager), type(uint256).max); + MockERC20(Currency.unwrap(currencyB)).approve(address(positionManager), type(uint256).max); + positionManager.modifyLiquidity(_key, IPoolManager.ModifyLiquidityParams(-887220, 887220, 200 ether, 0), "0x"); + } + + function _getExactInputParams(Currency[] memory _tokenPath, uint256 amountIn) + internal + pure + returns (IV4Router.ExactInputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = 0; i < _tokenPath.length - 1; i++) { + path[i] = PathKey(_tokenPath[i + 1], 3000, 60, IHooks(address(0)), bytes("")); + } + + params.currencyIn = _tokenPath[0]; + params.path = path; + params.amountIn = uint128(amountIn); + params.amountOutMinimum = 0; + } + + function _getExactOutputParams(Currency[] memory _tokenPath, uint256 amountOut) + internal + pure + returns (IV4Router.ExactOutputParams memory params) + { + PathKey[] memory path = new PathKey[](_tokenPath.length - 1); + for (uint256 i = _tokenPath.length - 1; i > 0; i--) { + path[i - 1] = PathKey(_tokenPath[i - 1], 3000, 60, IHooks(address(0)), bytes("")); + } + + params.currencyOut = _tokenPath[_tokenPath.length - 1]; + params.path = path; + params.amountOut = uint128(amountOut); + params.amountInMaximum = type(uint128).max; + } + + function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { + plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); + } +} From ff206ec0d5ed2b6c4303fca676a332cf192f0703 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 26 Jul 2024 15:35:04 +0100 Subject: [PATCH 44/50] Separate gas tests and regular tests --- test/router/V4Router.gas.t.sol | 163 +++++++++++++++++++++++++++++++++ test/router/V4Router.t.sol | 22 +---- 2 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 test/router/V4Router.gas.t.sol diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol new file mode 100644 index 00000000..284f07ef --- /dev/null +++ b/test/router/V4Router.gas.t.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; + +import {IV4Router} from "../../src/interfaces/IV4Router.sol"; +import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; +import {Plan, ActionsRouterPlanner} from "../shared/ActionsRouterPlanner.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; + +contract V4RouterTest is RoutingTestHelpers, GasSnapshot { + using CurrencyLibrary for Currency; + using ActionsRouterPlanner for Plan; + + function setUp() public { + setupRouterCurrenciesAndPoolsWithLiquidity(); + plan = ActionsRouterPlanner.init(); + } + + function test_gas_bytecodeSize() public { + snapSize("V4Router_Bytecode", address(router)); + } + + function test_gas_swapExactInputSingle_zeroForOne() public { + uint256 amountIn = 1 ether; + + IV4Router.ExactInputSingleParams memory params = + IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactInputSingle"); + } + + function test_gas_swapExactIn_1Hop_zeroForOne() public { + uint256 amountIn = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency1, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactIn1Hop"); + } + + function test_gas_swapExactIn_2Hops() public { + uint256 amountIn = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency2, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactIn2Hops"); + } + + function test_gas_swapExactIn_3Hops() public { + uint256 amountIn = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency0, currency3, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactIn3Hops"); + } + + function test_gas_swapExactOutputSingle_zeroForOne() public { + uint256 amountOut = 1 ether; + + IV4Router.ExactOutputSingleParams memory params = + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + _finalizePlan(key0.currency0, key0.currency1, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactOutputSingle"); + } + + function test_gas_swapExactOut_1Hop_zeroForOne() public { + uint256 amountOut = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency1, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactOut1Hop"); + } + + function test_gas_swapExactOut_1Hop_oneForZero() public { + uint256 amountOut = 1 ether; + + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency1, currency0, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactOut1Hop"); + } + + function test_gas_swapExactOut_2Hops() public { + uint256 amountOut = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency2, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactOut2Hops"); + } + + function test_gas_swapExactOut_3Hops() public { + uint256 amountOut = 1 ether; + + tokenPath.push(currency0); + tokenPath.push(currency1); + tokenPath.push(currency2); + tokenPath.push(currency3); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + _finalizePlan(currency0, currency3, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactOut3Hops"); + } +} diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 79ebcaae..89dd07db 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -1,20 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; -import {Test} from "forge-std/Test.sol"; -import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; - -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; -import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; - import {IV4Router} from "../../src/interfaces/IV4Router.sol"; import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol"; import {Plan, ActionsRouterPlanner} from "../shared/ActionsRouterPlanner.sol"; import {Actions} from "../../src/libraries/Actions.sol"; -contract V4RouterTest is RoutingTestHelpers, GasSnapshot { +contract V4RouterTest is RoutingTestHelpers { using CurrencyLibrary for Currency; using ActionsRouterPlanner for Plan; @@ -23,10 +16,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { plan = ActionsRouterPlanner.init(); } - function test_gas_bytecodeSize() public { - snapSize("V4Router_Bytecode", address(router)); - } - function test_gas_swapExactInputSingle_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -42,7 +31,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactInputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -90,7 +78,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactIn1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -140,7 +127,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactIn2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -172,7 +158,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactIn3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); @@ -200,7 +185,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOutputSingle"); uint256 newBalance0 = key0.currency0.balanceOf(address(this)); uint256 newBalance1 = key0.currency1.balanceOf(address(this)); @@ -248,7 +232,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -273,7 +256,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut1Hop"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -300,7 +282,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut2Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance1 = currency1.balanceOfSelf(); @@ -332,7 +313,6 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut3Hops"); uint256 newBalance0 = currency0.balanceOfSelf(); uint256 newBalance3 = currency3.balanceOfSelf(); From 09b2e238467c42895e6d0ec63de014720a4b1f5d Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 26 Jul 2024 15:38:22 +0100 Subject: [PATCH 45/50] remove duplicate snapshot name --- .../V4Router_ExactOut1Hop_oneForZero.snap | 1 + ...p => V4Router_ExactOut1Hop_zeroForOne.snap} | 0 test/router/V4Router.gas.t.sol | 4 ++-- test/router/V4Router.t.sol | 18 +++++++++--------- 4 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 .forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap rename .forge-snapshots/{V4Router_ExactOut1Hop.snap => V4Router_ExactOut1Hop_zeroForOne.snap} (100%) diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap new file mode 100644 index 00000000..ae44165a --- /dev/null +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -0,0 +1 @@ +129979 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap similarity index 100% rename from .forge-snapshots/V4Router_ExactOut1Hop.snap rename to .forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 284f07ef..50131fbd 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -110,7 +110,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut1Hop"); + snapLastCall("V4Router_ExactOut1Hop_zeroForOne"); } function test_gas_swapExactOut_1Hop_oneForZero() public { @@ -125,7 +125,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactOut1Hop"); + snapLastCall("V4Router_ExactOut1Hop_oneForZero"); } function test_gas_swapExactOut_2Hops() public { diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 89dd07db..a6977e41 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -16,7 +16,7 @@ contract V4RouterTest is RoutingTestHelpers { plan = ActionsRouterPlanner.init(); } - function test_gas_swapExactInputSingle_zeroForOne() public { + function test_swapExactInputSingle_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -62,7 +62,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(newBalance0 - prevBalance0, expectedAmountOut); } - function test_gas_swapExactIn_1Hop_zeroForOne() public { + function test_swapExactIn_1Hop_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -109,7 +109,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(newBalance0 - prevBalance0, expectedAmountOut); } - function test_gas_swapExactIn_2Hops() public { + function test_swapExactIn_2Hops() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 984211133872795298; @@ -140,7 +140,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(currency2.balanceOf(address(router)), 0); } - function test_gas_swapExactIn_3Hops() public { + function test_swapExactIn_3Hops() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 976467664490096191; @@ -170,7 +170,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(currency3.balanceOf(address(router)), 0); } - function test_gas_swapExactOutputSingle_zeroForOne() public { + function test_swapExactOutputSingle_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -216,7 +216,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(newBalance0 - prevBalance0, amountOut); } - function test_gas_swapExactOut_1Hop_zeroForOne() public { + function test_swapExactOut_1Hop_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -240,7 +240,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(newBalance1 - prevBalance1, amountOut); } - function test_gas_swapExactOut_1Hop_oneForZero() public { + function test_swapExactOut_1Hop_oneForZero() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -264,7 +264,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(newBalance0 - prevBalance0, amountOut); } - function test_gas_swapExactOut_2Hops() public { + function test_swapExactOut_2Hops() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1016204441757464409; @@ -295,7 +295,7 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(currency2.balanceOf(address(router)), 0); } - function test_gas_swapExactOut_3Hops() public { + function test_swapExactOut_3Hops() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1024467570922834110; From b51f0ea7e79591ab65884f17241f16046f2e9bb4 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Fri, 26 Jul 2024 15:40:14 +0100 Subject: [PATCH 46/50] another gas test --- .../V4Router_ExactIn1Hop_oneForZero.snap | 1 + ...nap => V4Router_ExactIn1Hop_zeroForOne.snap} | 0 test/router/V4Router.gas.t.sol | 17 ++++++++++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 .forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap rename .forge-snapshots/{V4Router_ExactIn1Hop.snap => V4Router_ExactIn1Hop_zeroForOne.snap} (100%) diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap new file mode 100644 index 00000000..7adadd9b --- /dev/null +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -0,0 +1 @@ +129132 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap similarity index 100% rename from .forge-snapshots/V4Router_ExactIn1Hop.snap rename to .forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 50131fbd..5e038a23 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -48,7 +48,22 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { bytes memory data = plan.encode(); router.executeActions(data); - snapLastCall("V4Router_ExactIn1Hop"); + snapLastCall("V4Router_ExactIn1Hop_zeroForOne"); + } + + function test_swapExactIn_1Hop_oneForZero() public { + uint256 amountIn = 1 ether; + + tokenPath.push(currency1); + tokenPath.push(currency0); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + _finalizePlan(currency1, currency0, address(this)); + bytes memory data = plan.encode(); + + router.executeActions(data); + snapLastCall("V4Router_ExactIn1Hop_oneForZero"); } function test_gas_swapExactIn_2Hops() public { From 7094a75c63db6580f82abfa5b77a028803307fa4 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 29 Jul 2024 12:43:20 +0200 Subject: [PATCH 47/50] PR comments --- src/V4Router.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/V4Router.sol b/src/V4Router.sol index 39612077..78025195 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -7,7 +7,6 @@ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PathKey, PathKeyLib} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; @@ -25,7 +24,7 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using CalldataDecoder for bytes; using TransientStateLibrary for IPoolManager; - constructor(IPoolManager poolManager) BaseActionsRouter(poolManager) {} + constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {} // TODO native support !! function _handleAction(uint256 action, bytes calldata params) internal override { From 9ee5768f4ee626b50ec38804a1789399bb5f4028 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 29 Jul 2024 12:45:45 +0200 Subject: [PATCH 48/50] remove unused function --- src/V4Router.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/V4Router.sol b/src/V4Router.sol index 78025195..268671f2 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -162,6 +162,4 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); } } - - function _mapAmount(uint256 amount) internal returns (uint256) {} } From 7545fa3762b6136c5b6e4129282d89e7acd46d00 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 29 Jul 2024 22:20:04 +0100 Subject: [PATCH 49/50] PR cmments --- test/router/V4Router.gas.t.sol | 30 ++++++++--------------- test/router/V4Router.t.sol | 36 ++++++++++------------------ test/shared/ActionsRouterPlanner.sol | 14 +++++++++++ test/shared/RoutingTestHelpers.sol | 7 +----- 4 files changed, 37 insertions(+), 50 deletions(-) diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 5e038a23..bfbd76fd 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -29,8 +29,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency0, key0.currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactInputSingle"); @@ -44,8 +43,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_zeroForOne"); @@ -59,8 +57,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency1, currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactIn1Hop_oneForZero"); @@ -75,8 +72,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency2, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactIn2Hops"); @@ -92,8 +88,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency3, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactIn3Hops"); @@ -106,8 +101,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency0, key0.currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactOutputSingle"); @@ -121,8 +115,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_zeroForOne"); @@ -136,8 +129,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency1, currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactOut1Hop_oneForZero"); @@ -152,8 +144,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency2, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactOut2Hops"); @@ -169,8 +160,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency3, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); router.executeActions(data); snapLastCall("V4Router_ExactOut3Hops"); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index a6977e41..194da80b 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -27,8 +27,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency0, key0.currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); router.executeActions(data); @@ -50,8 +49,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency1, key0.currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency1, key0.currency0, address(this)); router.executeActions(data); @@ -74,8 +72,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = currency1.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); router.executeActions(data); @@ -97,8 +94,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = currency1.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency1, currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); router.executeActions(data); @@ -123,8 +119,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance2 = currency2.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency2, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); router.executeActions(data); @@ -154,8 +149,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance3 = currency3.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); - _finalizePlan(currency0, currency3, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); router.executeActions(data); @@ -181,8 +175,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency0, key0.currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); router.executeActions(data); @@ -204,8 +197,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = key0.currency1.balanceOf(address(this)); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); - _finalizePlan(key0.currency1, key0.currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(key0.currency1, key0.currency0, address(this)); router.executeActions(data); @@ -228,8 +220,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = currency1.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency1, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency1, address(this)); router.executeActions(data); @@ -252,8 +243,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance1 = currency1.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency1, currency0, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency1, currency0, address(this)); router.executeActions(data); @@ -278,8 +268,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance2 = currency2.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency2, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency2, address(this)); router.executeActions(data); @@ -309,8 +298,7 @@ contract V4RouterTest is RoutingTestHelpers { uint256 prevBalance3 = currency3.balanceOfSelf(); plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); - _finalizePlan(currency0, currency3, address(this)); - bytes memory data = plan.encode(); + bytes memory data = plan.finalizeSwap(currency0, currency3, address(this)); router.executeActions(data); diff --git a/test/shared/ActionsRouterPlanner.sol b/test/shared/ActionsRouterPlanner.sol index 8ea06662..1623ea01 100644 --- a/test/shared/ActionsRouterPlanner.sol +++ b/test/shared/ActionsRouterPlanner.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {Actions} from "../../src/libraries/Actions.sol"; + struct Plan { uint256[] actions; bytes[] params; } library ActionsRouterPlanner { + using ActionsRouterPlanner for Plan; + function init() internal pure returns (Plan memory plan) { return Plan({actions: new uint256[](0), params: new bytes[](0)}); } @@ -33,4 +38,13 @@ library ActionsRouterPlanner { function encode(Plan memory plan) internal pure returns (bytes memory) { return abi.encode(plan.actions, plan.params); } + + function finalizeSwap(Plan memory plan, Currency inputCurrency, Currency outputCurrency, address recipient) + internal + returns (bytes memory) + { + plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); + plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); + return plan.encode(); + } } diff --git a/test/shared/RoutingTestHelpers.sol b/test/shared/RoutingTestHelpers.sol index c53fdef7..b1af43cf 100644 --- a/test/shared/RoutingTestHelpers.sol +++ b/test/shared/RoutingTestHelpers.sol @@ -19,7 +19,7 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; import {LiquidityOperations} from "./LiquidityOperations.sol"; import {IV4Router} from "../../src/interfaces/IV4Router.sol"; -/// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic liquidity operations on posm. +/// @notice A shared test contract that wraps the v4-core deployers contract and exposes basic helpers for swapping with the router. contract RoutingTestHelpers is Test, Deployers { using ActionsRouterPlanner for Plan; @@ -107,9 +107,4 @@ contract RoutingTestHelpers is Test, Deployers { params.amountOut = uint128(amountOut); params.amountInMaximum = type(uint128).max; } - - function _finalizePlan(Currency inputCurrency, Currency outputCurrency, address recipient) internal { - plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency)); - plan = plan.add(Actions.TAKE_ALL, abi.encode(outputCurrency, recipient)); - } } From a92da591b6313d4fda1813ebb63778da22035d26 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Mon, 29 Jul 2024 22:39:13 +0100 Subject: [PATCH 50/50] handle hook edgecase --- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactIn1Hop_oneForZero.snap | 2 +- .../V4Router_ExactIn1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactIn2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactIn3Hops.snap | 2 +- .../V4Router_ExactOut1Hop_oneForZero.snap | 2 +- .../V4Router_ExactOut1Hop_zeroForOne.snap | 2 +- .forge-snapshots/V4Router_ExactOut2Hops.snap | 2 +- .forge-snapshots/V4Router_ExactOut3Hops.snap | 2 +- src/V4Router.sol | 8 ++++++-- src/libraries/SafeCast.sol | 20 +++++++++++++++++++ test/shared/ActionsRouterPlanner.sol | 1 + 12 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 src/libraries/SafeCast.sol diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index f3a22372..a99d2d98 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6129 \ No newline at end of file +6214 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap index 7adadd9b..791b2fa3 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_oneForZero.snap @@ -1 +1 @@ -129132 \ No newline at end of file +129205 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap index 432cfe4e..225336b0 100644 --- a/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactIn1Hop_zeroForOne.snap @@ -1 +1 @@ -135962 \ No newline at end of file +136035 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn2Hops.snap b/.forge-snapshots/V4Router_ExactIn2Hops.snap index 412a7a96..d2f4fba9 100644 --- a/.forge-snapshots/V4Router_ExactIn2Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn2Hops.snap @@ -1 +1 @@ -187241 \ No newline at end of file +187387 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactIn3Hops.snap b/.forge-snapshots/V4Router_ExactIn3Hops.snap index 35f13a5b..b253014f 100644 --- a/.forge-snapshots/V4Router_ExactIn3Hops.snap +++ b/.forge-snapshots/V4Router_ExactIn3Hops.snap @@ -1 +1 @@ -238549 \ No newline at end of file +238768 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap index ae44165a..6b466d46 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_oneForZero.snap @@ -1 +1 @@ -129979 \ No newline at end of file +130046 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap index f62773b2..ae545a40 100644 --- a/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap +++ b/.forge-snapshots/V4Router_ExactOut1Hop_zeroForOne.snap @@ -1 +1 @@ -134780 \ No newline at end of file +134847 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut2Hops.snap b/.forge-snapshots/V4Router_ExactOut2Hops.snap index 70c1d010..c9d747ed 100644 --- a/.forge-snapshots/V4Router_ExactOut2Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut2Hops.snap @@ -1 +1 @@ -186674 \ No newline at end of file +186808 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOut3Hops.snap b/.forge-snapshots/V4Router_ExactOut3Hops.snap index a8031d46..0a254676 100644 --- a/.forge-snapshots/V4Router_ExactOut3Hops.snap +++ b/.forge-snapshots/V4Router_ExactOut3Hops.snap @@ -1 +1 @@ -238612 \ No newline at end of file +238813 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 268671f2..5e7e81ab 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -14,12 +14,14 @@ import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; +import {SafeCast} from "./libraries/SafeCast.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools /// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` /// An inheriting contract should call _executeActions at the point that they wish actions to be executed abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { + using SafeCast for *; using PathKeyLib for PathKey; using CalldataDecoder for bytes; using TransientStateLibrary for IPoolManager; @@ -98,7 +100,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { for (uint256 i = 0; i < pathLength; i++) { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); - amountOut = uint128(_swap(poolKey, zeroForOne, -int256(uint256(amountIn)), 0, pathKey.hookData)); + // The output delta will always be positive, except for when interacting with certain hook pools + amountOut = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), 0, pathKey.hookData).toUint128(); amountIn = amountOut; currencyIn = pathKey.intermediateCurrency; @@ -130,7 +133,8 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); - amountIn = uint128(-_swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData)); + // The output delta will always be negative, except for when interacting with certain hook pools + amountIn = (-_swap(poolKey, !oneForZero, int256(uint256(amountOut)), 0, pathKey.hookData)).toUint128(); amountOut = amountIn; currencyOut = pathKey.intermediateCurrency; diff --git a/src/libraries/SafeCast.sol b/src/libraries/SafeCast.sol new file mode 100644 index 00000000..75fddf53 --- /dev/null +++ b/src/libraries/SafeCast.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; + +/// @title Safe casting methods +/// @notice Contains methods for safely casting between types +library SafeCast { + using CustomRevert for bytes4; + + error SafeCastOverflow(); + + /// @notice Cast a int128 to a uint128, revert on overflow or underflow + /// @param x The int128 to be casted + /// @return y The casted integer, now type uint128 + function toUint128(int128 x) internal pure returns (uint128 y) { + if (x < 0) SafeCastOverflow.selector.revertWith(); + y = uint128(x); + } +} diff --git a/test/shared/ActionsRouterPlanner.sol b/test/shared/ActionsRouterPlanner.sol index 1623ea01..09803eeb 100644 --- a/test/shared/ActionsRouterPlanner.sol +++ b/test/shared/ActionsRouterPlanner.sol @@ -41,6 +41,7 @@ library ActionsRouterPlanner { function finalizeSwap(Plan memory plan, Currency inputCurrency, Currency outputCurrency, address recipient) internal + pure returns (bytes memory) { plan = plan.add(Actions.SETTLE_ALL, abi.encode(inputCurrency));