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;