From 902b23d5e10fb72854058beca6d767081f5705c7 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 31 Jul 2024 19:38:28 +0100 Subject: [PATCH 1/2] slippage checks on single trades --- .forge-snapshots/V4Router_Bytecode.snap | 2 +- .../V4Router_ExactInputSingle.snap | 2 +- .../V4Router_ExactInputSingle_nativeIn.snap | 2 +- .../V4Router_ExactInputSingle_nativeOut.snap | 2 +- .../V4Router_ExactOutputSingle.snap | 2 +- ...r_ExactOutputSingle_nativeIn_sweepETH.snap | 2 +- .../V4Router_ExactOutputSingle_nativeOut.snap | 2 +- src/V4Router.sol | 22 +++++++++++-------- test/router/V4Router.gas.t.sol | 6 ++--- test/router/V4Router.t.sol | 20 ++++++++++------- 10 files changed, 35 insertions(+), 27 deletions(-) diff --git a/.forge-snapshots/V4Router_Bytecode.snap b/.forge-snapshots/V4Router_Bytecode.snap index 42bd3cdc..79a18dfd 100644 --- a/.forge-snapshots/V4Router_Bytecode.snap +++ b/.forge-snapshots/V4Router_Bytecode.snap @@ -1 +1 @@ -6628 \ No newline at end of file +6845 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle.snap b/.forge-snapshots/V4Router_ExactInputSingle.snap index 2e3423fd..2e8df555 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle.snap @@ -1 +1 @@ -134717 \ No newline at end of file +134844 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap index b7011dc2..3b00e8e8 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeIn.snap @@ -1 +1 @@ -119848 \ No newline at end of file +119975 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap index cdff580e..0bb776a3 100644 --- a/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactInputSingle_nativeOut.snap @@ -1 +1 @@ -118993 \ No newline at end of file +119120 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle.snap b/.forge-snapshots/V4Router_ExactOutputSingle.snap index ef03db55..e04bda5d 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle.snap @@ -1 +1 @@ -133251 \ No newline at end of file +133648 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap index d3168270..15a7c019 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeIn_sweepETH.snap @@ -1 +1 @@ -125373 \ No newline at end of file +125770 \ No newline at end of file diff --git a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap index 78729eea..6fb6e08d 100644 --- a/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap +++ b/.forge-snapshots/V4Router_ExactOutputSingle_nativeOut.snap @@ -1 +1 @@ -119636 \ No newline at end of file +120033 \ No newline at end of file diff --git a/src/V4Router.sol b/src/V4Router.sol index 5f679193..7056e4a0 100644 --- a/src/V4Router.sol +++ b/src/V4Router.sol @@ -63,13 +63,14 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params) private { - _swap( + uint128 amountOut = _swap( params.poolKey, params.zeroForOne, int256(-int128(params.amountIn)), params.sqrtPriceLimitX96, params.hookData - ); + ).toUint128(); + if (amountOut < params.amountOutMinimum) revert TooLittleReceived(); } function _swapExactInput(IV4Router.ExactInputParams memory params) private { @@ -96,13 +97,16 @@ abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { } function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params) private { - _swap( - params.poolKey, - params.zeroForOne, - int256(int128(params.amountOut)), - params.sqrtPriceLimitX96, - params.hookData - ); + uint128 amountIn = ( + -_swap( + params.poolKey, + params.zeroForOne, + int256(int128(params.amountOut)), + params.sqrtPriceLimitX96, + params.hookData + ) + ).toUint128(); + if (amountIn > params.amountInMaximum) revert TooMuchRequested(); } function _swapExactOutput(IV4Router.ExactOutputParams memory params) private { diff --git a/test/router/V4Router.gas.t.sol b/test/router/V4Router.gas.t.sol index 766a90f6..2c48b2c6 100644 --- a/test/router/V4Router.gas.t.sol +++ b/test/router/V4Router.gas.t.sol @@ -195,7 +195,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); @@ -271,7 +271,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency0, nativeKey.currency1, address(this)); @@ -284,7 +284,7 @@ contract V4RouterTest is RoutingTestHelpers, GasSnapshot { uint256 amountOut = 1 ether; IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), type(uint128).max, 0, bytes("")); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); bytes memory data = plan.finalizeSwap(nativeKey.currency1, nativeKey.currency0, address(this)); diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index a590e0f3..2493175c 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -291,8 +291,9 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, true, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -310,8 +311,9 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(key0, false, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -422,8 +424,9 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, true, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); @@ -441,8 +444,9 @@ contract V4RouterTest is RoutingTestHelpers { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; - IV4Router.ExactOutputSingleParams memory params = - IV4Router.ExactOutputSingleParams(nativeKey, false, uint128(amountOut), 0, 0, bytes("")); + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("") + ); plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); From 9162838c78e67476ebfae457f97c9cc29dd927e6 Mon Sep 17 00:00:00 2001 From: Alice Henshaw Date: Wed, 31 Jul 2024 20:08:40 +0100 Subject: [PATCH 2/2] SLippage check tests --- test/router/V4Router.t.sol | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/router/V4Router.t.sol b/test/router/V4Router.t.sol index 2493175c..bc34f0a5 100644 --- a/test/router/V4Router.t.sol +++ b/test/router/V4Router.t.sol @@ -20,6 +20,22 @@ contract V4RouterTest is RoutingTestHelpers { ERC20 -> ERC20 EXACT INPUT //////////////////////////////////////////////////////////////*/ + function test_swapExactInputSingle_revertsForAmountOut() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + // min amount out of 1 higher than the actual amount out + IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams( + key0, true, uint128(amountIn), uint128(expectedAmountOut + 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + + vm.expectRevert(IV4Router.TooLittleReceived.selector); + router.executeActions(data); + } + function test_swapExactInputSingle_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -56,6 +72,22 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut); } + function test_swapExactInput_revertsForAmountOut() public { + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 992054607780215625; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn); + params.amountOutMinimum = uint128(expectedAmountOut + 1); + + plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + + vm.expectRevert(IV4Router.TooLittleReceived.selector); + router.executeActions(data); + } + function test_swapExactIn_1Hop_zeroForOne() public { uint256 amountIn = 1 ether; uint256 expectedAmountOut = 992054607780215625; @@ -287,6 +319,21 @@ contract V4RouterTest is RoutingTestHelpers { ERC20 -> ERC20 EXACT OUTPUT //////////////////////////////////////////////////////////////*/ + function test_swapExactOutputSingle_revertsForAmountIn() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams( + key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), 0, bytes("") + ); + + plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + + vm.expectRevert(IV4Router.TooMuchRequested.selector); + router.executeActions(data); + } + function test_swapExactOutputSingle_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163; @@ -327,6 +374,22 @@ contract V4RouterTest is RoutingTestHelpers { assertEq(outputBalanceAfter - outputBalanceBefore, amountOut); } + function test_swapExactOut_revertsForAmountIn() public { + uint256 amountOut = 1 ether; + uint256 expectedAmountIn = 1008049273448486163; + + tokenPath.push(currency0); + tokenPath.push(currency1); + IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut); + params.amountInMaximum = uint128(expectedAmountIn - 1); + + plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params)); + bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, address(this)); + + vm.expectRevert(IV4Router.TooMuchRequested.selector); + router.executeActions(data); + } + function test_swapExactOut_1Hop_zeroForOne() public { uint256 amountOut = 1 ether; uint256 expectedAmountIn = 1008049273448486163;