From 147ba68392bb776e8a48d7818edb567e3d66b5de Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 11 Mar 2025 17:57:40 -0400 Subject: [PATCH 01/12] feat: ExecutorTransferMethods helper contract - Also sketch its use in USV2 (missing proper decoding) --- .../src/executors/ExecutorTransferMethods.sol | 52 +++++++++++++++++++ foundry/src/executors/UniswapV2Executor.sol | 19 ++++--- foundry/test/TychoRouterTestSetup.sol | 2 +- .../test/executors/UniswapV2Executor.t.sol | 35 +++++++++---- 4 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 foundry/src/executors/ExecutorTransferMethods.sol diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol new file mode 100644 index 0000000..45fc1d2 --- /dev/null +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "@interfaces/IExecutor.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; + +error ExecutorTransferMethods__InvalidPermit2(); + +contract ExecutorTransferMethods { + using SafeERC20 for IERC20; + + IAllowanceTransfer public immutable permit2; + + enum TransferMethod { + TRANSFER, + TRANSFERFROM, + TRANSFERPERMIT2, + NONE + } + + constructor(address _permit2) { + if (_permit2 == address(0)) { + revert ExecutorTransferMethods__InvalidPermit2(); + } + permit2 = IAllowanceTransfer(_permit2); + } + + function _transfer( + IERC20 tokenIn, + address receiver, + uint256 amount, + TransferMethod method + ) internal { + if (method == TransferMethod.TRANSFER) { + tokenIn.safeTransfer(receiver, amount); + } else if (method == TransferMethod.TRANSFERFROM) { + tokenIn.safeTransferFrom(msg.sender, receiver, amount); + } else if (method == TransferMethod.TRANSFERPERMIT2) { + // Permit2.permit is called from the TychoRouter + permit2.transferFrom( + msg.sender, + receiver, // Does this work if receiver is not address(this)? + uint160(amount), + address(tokenIn) + ); + } else { + // Funds are likely already in pool. Do nothing. + } + } +} diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 50fb056..29544ab 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,20 +4,23 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; +import "./ExecutorTransferMethods.sol"; error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); -contract UniswapV2Executor is IExecutor { +contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { using SafeERC20 for IERC20; address public immutable factory; bytes32 public immutable initCode; address private immutable self; - constructor(address _factory, bytes32 _initCode) { + constructor(address _factory, bytes32 _initCode, address _permit2) + ExecutorTransferMethods(_permit2) + { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); } @@ -35,17 +38,18 @@ contract UniswapV2Executor is IExecutor { payable returns (uint256 calculatedAmount) { + IERC20 tokenIn; address target; address receiver; bool zeroForOne; - IERC20 tokenIn; + TransferMethod method; - (tokenIn, target, receiver, zeroForOne) = _decodeData(data); + (tokenIn, target, receiver, zeroForOne, method) = _decodeData(data); _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - tokenIn.safeTransfer(target, givenAmount); + _transfer(tokenIn, target, givenAmount, method); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { @@ -62,7 +66,8 @@ contract UniswapV2Executor is IExecutor { IERC20 inToken, address target, address receiver, - bool zeroForOne + bool zeroForOne, + TransferMethod method ) { if (data.length != 61) { @@ -72,6 +77,8 @@ contract UniswapV2Executor is IExecutor { target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); zeroForOne = uint8(data[60]) > 0; + // TODO properly decode, assume encoded using just 1 byte. + method = TransferMethod.TRANSFER; } function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 1690d3c..89ea394 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -97,7 +97,7 @@ contract TychoRouterTestSetup is Constants { address ekuboCore = 0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444; IPoolManager poolManager = IPoolManager(poolManagerAddress); - usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2); + usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3); usv4Executor = new UniswapV4Executor(poolManager); pancakev3Executor = diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index a26a9b7..3f303fd 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -2,12 +2,13 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV2Executor.sol"; +import "@src/executors/ExecutorTransferMethods.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { - constructor(address _factory, bytes32 _initCode) - UniswapV2Executor(_factory, _initCode) + constructor(address _factory, bytes32 _initCode, address _permit2) + UniswapV2Executor(_factory, _initCode, _permit2) {} function decodeParams(bytes calldata data) @@ -17,7 +18,8 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor { IERC20 inToken, address target, address receiver, - bool zeroForOne + bool zeroForOne, + TransferMethod method ) { return _decodeData(data); @@ -59,13 +61,13 @@ contract UniswapV2ExecutorTest is Test, Constants { uint256 forkBlock = 17323404; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV2Exposed = new UniswapV2ExecutorExposed( - USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH + USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); sushiswapV2Exposed = new UniswapV2ExecutorExposed( - SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH + SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( - PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH + PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); } @@ -73,13 +75,19 @@ contract UniswapV2ExecutorTest is Test, Constants { bytes memory params = abi.encodePacked(WETH_ADDR, address(2), address(3), false); - (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = - uniswapV2Exposed.decodeParams(params); + ( + IERC20 tokenIn, + address target, + address receiver, + bool zeroForOne, + ExecutorTransferMethods.TransferMethod method + ) = uniswapV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, address(2)); assertEq(receiver, address(3)); assertEq(zeroForOne, false); + assertEq(0, uint8(method)); } function testDecodeParamsInvalidDataLength() public { @@ -145,13 +153,20 @@ contract UniswapV2ExecutorTest is Test, Constants { bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; - (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = - uniswapV2Exposed.decodeParams(protocolData); + ( + IERC20 tokenIn, + address target, + address receiver, + bool zeroForOne, + ExecutorTransferMethods.TransferMethod method + ) = uniswapV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); + // TRANSFER = 0 + assertEq(0, uint8(method)); } function testSwapIntegration() public { From 389009901ed0eb8c380c8d1a2f0a2a0668295cf5 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 13 Mar 2025 11:59:13 -0400 Subject: [PATCH 02/12] feat: ExecutorTransferMethods in UniswapV3Executor --- foundry/src/executors/UniswapV3Executor.sol | 40 +++++++++++++------ foundry/test/TychoRouterTestSetup.sol | 2 +- .../test/executors/UniswapV2Executor.t.sol | 5 ++- .../test/executors/UniswapV3Executor.t.sol | 21 ++++++---- 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 7b6383f..e243dc9 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -5,13 +5,14 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@interfaces/ICallback.sol"; +import {ExecutorTransferMethods} from "./ExecutorTransferMethods.sol"; error UniswapV3Executor__InvalidDataLength(); error UniswapV3Executor__InvalidFactory(); error UniswapV3Executor__InvalidTarget(); error UniswapV3Executor__InvalidInitCode(); -contract UniswapV3Executor is IExecutor, ICallback { +contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { using SafeERC20 for IERC20; uint160 private constant MIN_SQRT_RATIO = 4295128739; @@ -22,7 +23,9 @@ contract UniswapV3Executor is IExecutor, ICallback { bytes32 public immutable initCode; address private immutable self; - constructor(address _factory, bytes32 _initCode) { + constructor(address _factory, bytes32 _initCode, address _permit2) + ExecutorTransferMethods(_permit2) + { if (_factory == address(0)) { revert UniswapV3Executor__InvalidFactory(); } @@ -46,7 +49,8 @@ contract UniswapV3Executor is IExecutor, ICallback { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferMethod method ) = _decodeData(data); _verifyPairAddress(tokenIn, tokenOut, fee, target); @@ -55,7 +59,8 @@ contract UniswapV3Executor is IExecutor, ICallback { int256 amount1; IUniswapV3Pool pool = IUniswapV3Pool(target); - bytes memory callbackData = _makeV3CallbackData(tokenIn, tokenOut, fee); + bytes memory callbackData = + _makeV3CallbackData(tokenIn, tokenOut, fee, method); { (amount0, amount1) = pool.swap( @@ -92,12 +97,20 @@ contract UniswapV3Executor is IExecutor, ICallback { address tokenIn = address(bytes20(msgData[132:152])); + require( + uint8(msgData[171]) <= uint8(TransferMethod.NONE), + "InvalidTransferMethod" + ); + TransferMethod method = TransferMethod(uint8(msgData[171])); + verifyCallback(msgData[132:]); uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - IERC20(tokenIn).safeTransfer(msg.sender, amountOwed); + // TODO This must never be a safeTransfer. Figure out how to ensure this. + _transfer(IERC20(tokenIn), msg.sender, amountOwed, method); + return abi.encode(amountOwed, tokenIn); } @@ -132,7 +145,8 @@ contract UniswapV3Executor is IExecutor, ICallback { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferMethod method ) { if (data.length != 84) { @@ -144,14 +158,16 @@ contract UniswapV3Executor is IExecutor, ICallback { receiver = address(bytes20(data[43:63])); target = address(bytes20(data[63:83])); zeroForOne = uint8(data[83]) > 0; + method = TransferMethod.TRANSFER; } - function _makeV3CallbackData(address tokenIn, address tokenOut, uint24 fee) - internal - pure - returns (bytes memory) - { - return abi.encodePacked(tokenIn, tokenOut, fee); + function _makeV3CallbackData( + address tokenIn, + address tokenOut, + uint24 fee, + TransferMethod method + ) internal pure returns (bytes memory) { + return abi.encodePacked(tokenIn, tokenOut, fee, uint8(method), self); } function _verifyPairAddress( diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 89ea394..acf5a7a 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -98,7 +98,7 @@ contract TychoRouterTestSetup is Constants { IPoolManager poolManager = IPoolManager(poolManagerAddress); usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); - usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3); + usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); usv4Executor = new UniswapV4Executor(poolManager); pancakev3Executor = new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 3f303fd..4a341f8 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -87,7 +87,10 @@ contract UniswapV2ExecutorTest is Test, Constants { assertEq(target, address(2)); assertEq(receiver, address(3)); assertEq(zeroForOne, false); - assertEq(0, uint8(method)); + assertEq( + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER), + uint8(method) + ); } function testDecodeParamsInvalidDataLength() public { diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 59b03ba..2033edf 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -6,8 +6,8 @@ import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; contract UniswapV3ExecutorExposed is UniswapV3Executor { - constructor(address _factory, bytes32 _initCode) - UniswapV3Executor(_factory, _initCode) + constructor(address _factory, bytes32 _initCode, address _permit2) + UniswapV3Executor(_factory, _initCode, _permit2) {} function decodeData(bytes calldata data) @@ -19,7 +19,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + TransferMethod method ) { return _decodeData(data); @@ -48,10 +49,10 @@ contract UniswapV3ExecutorTest is Test, Constants { vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); uniswapV3Exposed = new UniswapV3ExecutorExposed( - USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH + USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); pancakeV3Exposed = new UniswapV3ExecutorExposed( - PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH + PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); } @@ -67,7 +68,8 @@ contract UniswapV3ExecutorTest is Test, Constants { uint24 fee, address receiver, address target, - bool zeroForOne + bool zeroForOne, + ExecutorTransferMethods.TransferMethod method ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -76,6 +78,10 @@ contract UniswapV3ExecutorTest is Test, Constants { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); + assertEq( + uint8(method), + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + ); } function testDecodeParamsInvalidDataLength() public { @@ -116,7 +122,8 @@ contract UniswapV3ExecutorTest is Test, Constants { int256(0), // amount1Delta dataOffset, dataLength, - protocolData + protocolData, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) ); uniswapV3Exposed.handleCallback(callbackData); vm.stopPrank(); From 8969186654ec5db96bdcaab0099b883f1ed0ba63 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 13 Mar 2025 12:19:55 -0400 Subject: [PATCH 03/12] feat: allow to pass msg.sender to USV3 callback - So that we can possibly do a transferFrom - This should still be safe since the user can't control what is passed here --- foundry/src/executors/ExecutorTransferMethods.sol | 3 ++- foundry/src/executors/UniswapV2Executor.sol | 2 +- foundry/src/executors/UniswapV3Executor.sol | 8 +++++--- foundry/test/executors/UniswapV3Executor.t.sol | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index 45fc1d2..9736994 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -29,6 +29,7 @@ contract ExecutorTransferMethods { function _transfer( IERC20 tokenIn, + address sender, address receiver, uint256 amount, TransferMethod method @@ -40,7 +41,7 @@ contract ExecutorTransferMethods { } else if (method == TransferMethod.TRANSFERPERMIT2) { // Permit2.permit is called from the TychoRouter permit2.transferFrom( - msg.sender, + sender, receiver, // Does this work if receiver is not address(this)? uint160(amount), address(tokenIn) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 29544ab..b54ecf7 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -49,7 +49,7 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - _transfer(tokenIn, target, givenAmount, method); + _transfer(tokenIn, msg.sender, target, givenAmount, method); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index e243dc9..09521a9 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -102,14 +102,14 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { "InvalidTransferMethod" ); TransferMethod method = TransferMethod(uint8(msgData[171])); + address sender = address(bytes20(msgData[172:192])); verifyCallback(msgData[132:]); uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - // TODO This must never be a safeTransfer. Figure out how to ensure this. - _transfer(IERC20(tokenIn), msg.sender, amountOwed, method); + _transfer(IERC20(tokenIn), sender, msg.sender, amountOwed, method); return abi.encode(amountOwed, tokenIn); } @@ -167,7 +167,9 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { uint24 fee, TransferMethod method ) internal pure returns (bytes memory) { - return abi.encodePacked(tokenIn, tokenOut, fee, uint8(method), self); + return abi.encodePacked( + tokenIn, tokenOut, fee, uint8(method), msg.sender, self + ); } function _verifyPairAddress( diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 2033edf..0b07237 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -123,7 +123,8 @@ contract UniswapV3ExecutorTest is Test, Constants { dataOffset, dataLength, protocolData, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER), + address(uniswapV3Exposed) // transferFrom sender (irrelevant in this case) ); uniswapV3Exposed.handleCallback(callbackData); vm.stopPrank(); From ca1d474f0874cc24fc713191470d23a72b9d3e04 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 7 Apr 2025 20:42:54 -0400 Subject: [PATCH 04/12] feat: Proper USV2Executor transfer decoding + tests - Properly decode, update tests with proper decoding - Added test case for each transfer method - Also fully tested permit2 transferFrom and it works perfectly. TODO: - Fix integration tests once encoding is implemented. --- .../src/executors/ExecutorTransferMethods.sol | 8 +- foundry/src/executors/UniswapV2Executor.sol | 5 +- foundry/test/TychoRouterTestSetup.sol | 19 +- .../test/executors/UniswapV2Executor.t.sol | 204 ++++++++++++++++-- .../test/executors/UniswapV3Executor.t.sol | 4 +- 5 files changed, 216 insertions(+), 24 deletions(-) diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index 9736994..d14d4d3 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -14,9 +14,13 @@ contract ExecutorTransferMethods { IAllowanceTransfer public immutable permit2; enum TransferMethod { + // Assume funds are in the TychoRouter - transfer into the pool TRANSFER, + // Assume funds are in msg.sender's wallet - transferFrom into the pool TRANSFERFROM, + // Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool TRANSFERPERMIT2, + // Assume funds have already been transferred into the pool. Do nothing. NONE } @@ -39,10 +43,10 @@ contract ExecutorTransferMethods { } else if (method == TransferMethod.TRANSFERFROM) { tokenIn.safeTransferFrom(msg.sender, receiver, amount); } else if (method == TransferMethod.TRANSFERPERMIT2) { - // Permit2.permit is called from the TychoRouter + // Permit2.permit is already called from the TychoRouter permit2.transferFrom( sender, - receiver, // Does this work if receiver is not address(this)? + receiver, uint160(amount), address(tokenIn) ); diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index b54ecf7..e5d5a3b 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -70,15 +70,14 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { TransferMethod method ) { - if (data.length != 61) { + if (data.length != 62) { revert UniswapV2Executor__InvalidDataLength(); } inToken = IERC20(address(bytes20(data[0:20]))); target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); zeroForOne = uint8(data[60]) > 0; - // TODO properly decode, assume encoded using just 1 byte. - method = TransferMethod.TRANSFER; + method = TransferMethod(uint8(data[61])); } function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index acf5a7a..a53614c 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -97,11 +97,14 @@ contract TychoRouterTestSetup is Constants { address ekuboCore = 0xe0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444; IPoolManager poolManager = IPoolManager(poolManagerAddress); - usv2Executor = new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); - usv3Executor = new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); + usv2Executor = + new UniswapV2Executor(factoryV2, initCodeV2, PERMIT2_ADDRESS); + usv3Executor = + new UniswapV3Executor(factoryV3, initCodeV3, PERMIT2_ADDRESS); usv4Executor = new UniswapV4Executor(poolManager); - pancakev3Executor = - new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); + pancakev3Executor = new UniswapV3Executor( + factoryPancakeV3, initCodePancakeV3, PERMIT2_ADDRESS + ); balancerv2Executor = new BalancerV2Executor(); ekuboExecutor = new EkuboExecutor(ekuboCore); curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE); @@ -254,7 +257,13 @@ contract TychoRouterTestSetup is Constants { address receiver, bool zero2one ) internal pure returns (bytes memory) { - return abi.encodePacked(tokenIn, target, receiver, zero2one); + return abi.encodePacked( + tokenIn, + target, + receiver, + zero2one, + ExecutorTransferMethods.TransferMethod.TRANSFER + ); } function encodeUniswapV3Swap( diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 4a341f8..365d3e7 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -56,6 +56,7 @@ contract UniswapV2ExecutorTest is Test, Constants { UniswapV2ExecutorExposed pancakeswapV2Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); + IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; @@ -64,16 +65,26 @@ contract UniswapV2ExecutorTest is Test, Constants { USV2_FACTORY_ETHEREUM, USV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); sushiswapV2Exposed = new UniswapV2ExecutorExposed( - SUSHISWAPV2_FACTORY_ETHEREUM, SUSHIV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + SUSHISWAPV2_FACTORY_ETHEREUM, + SUSHIV2_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); pancakeswapV2Exposed = new UniswapV2ExecutorExposed( - PANCAKESWAPV2_FACTORY_ETHEREUM, PANCAKEV2_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + PANCAKESWAPV2_FACTORY_ETHEREUM, + PANCAKEV2_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); + permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { - bytes memory params = - abi.encodePacked(WETH_ADDR, address(2), address(3), false); + bytes memory params = abi.encodePacked( + WETH_ADDR, + address(2), + address(3), + false, + ExecutorTransferMethods.TransferMethod.TRANSFER + ); ( IERC20 tokenIn, @@ -137,12 +148,17 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(amountOut, 0); } - function testSwap() public { + function testSwapWithTransfer() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; bool zeroForOne = false; - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); uniswapV2Exposed.swap(amountIn, protocolData); @@ -151,10 +167,162 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(finalBalance, amountOut); } + function testSwapWithTransferFrom() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFERFROM) + ); + + deal(WETH_ADDR, address(this), amountIn); + IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn); + + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } + + // TODO generalize these next two methods - don't reuse from TychoRouterTestSetup + /** + * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract + * to spend `amount_in` of `tokenIn` on her behalf. + * + * This function approves the Permit2 contract to transfer the specified token amount + * and constructs a `PermitSingle` struct for the approval. It also generates a valid + * EIP-712 signature for the approval using Alice's private key. + * + * @param tokenIn The address of the token being approved. + * @param amount_in The amount of tokens to approve for transfer. + * @return permitSingle The `PermitSingle` struct containing the approval details. + * @return signature The EIP-712 signature for the approval. + */ + function handlePermit2Approval(address tokenIn, uint256 amount_in) + internal + returns (IAllowanceTransfer.PermitSingle memory, bytes memory) + { + IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); + IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer + .PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: tokenIn, + amount: uint160(amount_in), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: address(uniswapV2Exposed), + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + /** + * @dev Signs a Permit2 `PermitSingle` struct with the given private key. + * @param permit The `PermitSingle` struct to sign. + * @param privateKey The private key of the signer. + * @return The signature as a `bytes` array. + */ + function signPermit2( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey + ) internal view returns (bytes memory) { + bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 domainSeparator = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ), + keccak256("Permit2"), + block.chainid, + PERMIT2_ADDRESS + ) + ); + bytes32 detailsHash = + keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + bytes32 permitHash = keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + detailsHash, + permit.spender, + permit.sigDeadline + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodePacked(r, s, v); + } + + + function testSwapWithPermit2TransferFrom() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + ALICE, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2) + ); + + + deal(WETH_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amountIn); + + // Assume the permit2.approve method will be called from the TychoRouter + // Replicate this secnario in this test. + permit2.permit(ALICE, permitSingle, signature); + + uniswapV2Exposed.swap(amountIn, protocolData); + vm.stopPrank(); + + uint256 finalBalance = DAI.balanceOf(ALICE); + assertGe(finalBalance, amountOut); + } + + function testSwapNoTransfer() public { + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + bool zeroForOne = false; + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + WETH_DAI_POOL, + BOB, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.NONE) + ); + + deal(WETH_ADDR, address(this), amountIn); + IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn); + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } + function testDecodeIntegration() public view { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; ( IERC20 tokenIn, @@ -175,7 +343,7 @@ contract UniswapV2ExecutorTest is Test, Constants { function testSwapIntegration() public { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -189,8 +357,13 @@ contract UniswapV2ExecutorTest is Test, Constants { uint256 amountIn = 10 ** 18; bool zeroForOne = false; address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR)); - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + fakePool, + BOB, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); @@ -204,8 +377,13 @@ contract UniswapV2ExecutorTest is Test, Constants { vm.rollFork(26857267); uint256 amountIn = 10 * 10 ** 6; bool zeroForOne = true; - bytes memory protocolData = - abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne); + bytes memory protocolData = abi.encodePacked( + BASE_USDC, + USDC_MAG7_POOL, + BOB, + zeroForOne, + uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + ); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 0b07237..4628754 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -52,7 +52,9 @@ contract UniswapV3ExecutorTest is Test, Constants { USV3_FACTORY_ETHEREUM, USV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); pancakeV3Exposed = new UniswapV3ExecutorExposed( - PANCAKESWAPV3_DEPLOYER_ETHEREUM, PANCAKEV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS + PANCAKESWAPV3_DEPLOYER_ETHEREUM, + PANCAKEV3_POOL_CODE_INIT_HASH, + PERMIT2_ADDRESS ); } From 30557e7e54a30ecb351c5fac38c7dee02e3d9089 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 8 Apr 2025 11:31:35 -0400 Subject: [PATCH 05/12] refactor: create Permit2TestHelper - To avoid duplicating permit2 setup code for TychoRouter and executor tests. --- .../src/executors/ExecutorTransferMethods.sol | 6 +- foundry/test/Permit2TestHelper.sol | 87 ++++++++++++++++++ foundry/test/TychoRouterSequentialSwap.t.sol | 6 +- foundry/test/TychoRouterSingleSwap.t.sol | 4 +- foundry/test/TychoRouterSplitSwap.t.sol | 12 +-- foundry/test/TychoRouterTestSetup.sol | 81 +---------------- .../test/executors/UniswapV2Executor.t.sol | 88 ++----------------- 7 files changed, 106 insertions(+), 178 deletions(-) create mode 100644 foundry/test/Permit2TestHelper.sol diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index d14d4d3..0e9772e 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; -import "@permit2/src/interfaces/IAllowanceTransfer.sol"; error ExecutorTransferMethods__InvalidPermit2(); @@ -45,10 +44,7 @@ contract ExecutorTransferMethods { } else if (method == TransferMethod.TRANSFERPERMIT2) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom( - sender, - receiver, - uint160(amount), - address(tokenIn) + sender, receiver, uint160(amount), address(tokenIn) ); } else { // Funds are likely already in pool. Do nothing. diff --git a/foundry/test/Permit2TestHelper.sol b/foundry/test/Permit2TestHelper.sol new file mode 100644 index 0000000..912b007 --- /dev/null +++ b/foundry/test/Permit2TestHelper.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "./Constants.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract Permit2TestHelper is Constants { + /** + * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract + * to spend `amount_in` of `tokenIn` on her behalf. + * + * This function approves the Permit2 contract to transfer the specified token amount + * and constructs a `PermitSingle` struct for the approval. It also generates a valid + * EIP-712 signature for the approval using Alice's private key. + * + * @param tokenIn The address of the token being approved. + * @param amount_in The amount of tokens to approve for transfer. + * @return permitSingle The `PermitSingle` struct containing the approval details. + * @return signature The EIP-712 signature for the approval. + */ + function handlePermit2Approval( + address tokenIn, + address spender, + uint256 amount_in + ) internal returns (IAllowanceTransfer.PermitSingle memory, bytes memory) { + IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); + IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer + .PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: tokenIn, + amount: uint160(amount_in), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: spender, + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + /** + * @dev Signs a Permit2 `PermitSingle` struct with the given private key. + * @param permit The `PermitSingle` struct to sign. + * @param privateKey The private key of the signer. + * @return The signature as a `bytes` array. + */ + function signPermit2( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey + ) internal view returns (bytes memory) { + bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 domainSeparator = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ), + keccak256("Permit2"), + block.chainid, + PERMIT2_ADDRESS + ) + ); + bytes32 detailsHash = + keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + bytes32 permitHash = keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + detailsHash, + permit.spender, + permit.sigDeadline + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodePacked(r, s, v); + } +} diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index c5db19f..46a9936 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -51,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwapPermit2( @@ -150,7 +150,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); @@ -229,7 +229,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDC_ADDR, amountIn); + ) = handlePermit2Approval(USDC_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index ea33401..648d9e9 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -19,7 +19,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -230,7 +230,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 0887500..a9c593a 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -81,7 +81,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -191,7 +191,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -282,7 +282,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); @@ -325,7 +325,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI bool zeroForOne = false; @@ -377,7 +377,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -426,7 +426,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDE_ADDR, amountIn); + ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn); UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](1); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index a53614c..e9bc79e 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -13,6 +13,7 @@ import "@src/TychoRouter.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; +import {Permit2TestHelper} from "./Permit2TestHelper.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} @@ -41,7 +42,7 @@ contract TychoRouterExposed is TychoRouter { } } -contract TychoRouterTestSetup is Constants { +contract TychoRouterTestSetup is Constants, Permit2TestHelper { TychoRouterExposed tychoRouter; address tychoRouterAddr; UniswapV2Executor public usv2Executor; @@ -132,84 +133,6 @@ contract TychoRouterTestSetup is Constants { } } - /** - * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract - * to spend `amount_in` of `tokenIn` on her behalf. - * - * This function approves the Permit2 contract to transfer the specified token amount - * and constructs a `PermitSingle` struct for the approval. It also generates a valid - * EIP-712 signature for the approval using Alice's private key. - * - * @param tokenIn The address of the token being approved. - * @param amount_in The amount of tokens to approve for transfer. - * @return permitSingle The `PermitSingle` struct containing the approval details. - * @return signature The EIP-712 signature for the approval. - */ - function handlePermit2Approval(address tokenIn, uint256 amount_in) - internal - returns (IAllowanceTransfer.PermitSingle memory, bytes memory) - { - IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer - .PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: tokenIn, - amount: uint160(amount_in), - expiration: uint48(block.timestamp + 1 days), - nonce: 0 - }), - spender: tychoRouterAddr, - sigDeadline: block.timestamp + 1 days - }); - - bytes memory signature = signPermit2(permitSingle, ALICE_PK); - return (permitSingle, signature); - } - - /** - * @dev Signs a Permit2 `PermitSingle` struct with the given private key. - * @param permit The `PermitSingle` struct to sign. - * @param privateKey The private key of the signer. - * @return The signature as a `bytes` array. - */ - function signPermit2( - IAllowanceTransfer.PermitSingle memory permit, - uint256 privateKey - ) internal view returns (bytes memory) { - bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( - "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,uint256 chainId,address verifyingContract)" - ), - keccak256("Permit2"), - block.chainid, - PERMIT2_ADDRESS - ) - ); - bytes32 detailsHash = - keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); - bytes32 permitHash = keccak256( - abi.encode( - _PERMIT_SINGLE_TYPEHASH, - detailsHash, - permit.spender, - permit.sigDeadline - ) - ); - - bytes32 digest = - keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - return abi.encodePacked(r, s, v); - } - function pleEncode(bytes[] memory data) public pure diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 365d3e7..42abec1 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -5,6 +5,7 @@ import "@src/executors/UniswapV2Executor.sol"; import "@src/executors/ExecutorTransferMethods.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { constructor(address _factory, bytes32 _initCode, address _permit2) @@ -48,7 +49,7 @@ contract FakeUniswapV2Pool { } } -contract UniswapV2ExecutorTest is Test, Constants { +contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { using SafeERC20 for IERC20; UniswapV2ExecutorExposed uniswapV2Exposed; @@ -188,86 +189,6 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(finalBalance, amountOut); } - // TODO generalize these next two methods - don't reuse from TychoRouterTestSetup - /** - * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract - * to spend `amount_in` of `tokenIn` on her behalf. - * - * This function approves the Permit2 contract to transfer the specified token amount - * and constructs a `PermitSingle` struct for the approval. It also generates a valid - * EIP-712 signature for the approval using Alice's private key. - * - * @param tokenIn The address of the token being approved. - * @param amount_in The amount of tokens to approve for transfer. - * @return permitSingle The `PermitSingle` struct containing the approval details. - * @return signature The EIP-712 signature for the approval. - */ - function handlePermit2Approval(address tokenIn, uint256 amount_in) - internal - returns (IAllowanceTransfer.PermitSingle memory, bytes memory) - { - IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer - .PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: tokenIn, - amount: uint160(amount_in), - expiration: uint48(block.timestamp + 1 days), - nonce: 0 - }), - spender: address(uniswapV2Exposed), - sigDeadline: block.timestamp + 1 days - }); - - bytes memory signature = signPermit2(permitSingle, ALICE_PK); - return (permitSingle, signature); - } - - /** - * @dev Signs a Permit2 `PermitSingle` struct with the given private key. - * @param permit The `PermitSingle` struct to sign. - * @param privateKey The private key of the signer. - * @return The signature as a `bytes` array. - */ - function signPermit2( - IAllowanceTransfer.PermitSingle memory permit, - uint256 privateKey - ) internal view returns (bytes memory) { - bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( - "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - bytes32 domainSeparator = keccak256( - abi.encode( - keccak256( - "EIP712Domain(string name,uint256 chainId,address verifyingContract)" - ), - keccak256("Permit2"), - block.chainid, - PERMIT2_ADDRESS - ) - ); - bytes32 detailsHash = - keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); - bytes32 permitHash = keccak256( - abi.encode( - _PERMIT_SINGLE_TYPEHASH, - detailsHash, - permit.spender, - permit.sigDeadline - ) - ); - - bytes32 digest = - keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - - return abi.encodePacked(r, s, v); - } - - function testSwapWithPermit2TransferFrom() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; @@ -280,13 +201,14 @@ contract UniswapV2ExecutorTest is Test, Constants { uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2) ); - deal(WETH_ADDR, ALICE, amountIn); vm.startPrank(ALICE); ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval( + WETH_ADDR, address(uniswapV2Exposed), amountIn + ); // Assume the permit2.approve method will be called from the TychoRouter // Replicate this secnario in this test. From e3ac394d27b8adaeb0aa16ffffb286fc31486ef1 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 8 Apr 2025 16:40:01 -0400 Subject: [PATCH 06/12] feat: Proper USV3Executor transfer decoding + tests - Properly decode, update tests with proper decoding - Added test case for each transfer method - Also fully tested permit2 transferFrom and it works perfectly. NOTE: UniswapV3 doesn't support NONE as a transfer method. TODO: - Fix integration tests once encoding is implemented. --- .../src/executors/ExecutorTransferMethods.sol | 2 +- foundry/src/executors/UniswapV3Executor.sol | 4 +- foundry/test/TychoRouterTestSetup.sol | 8 +- .../test/executors/UniswapV3Executor.t.sol | 113 ++++++++++++++++-- 4 files changed, 115 insertions(+), 12 deletions(-) diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index 0e9772e..6eb73a8 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -40,7 +40,7 @@ contract ExecutorTransferMethods { if (method == TransferMethod.TRANSFER) { tokenIn.safeTransfer(receiver, amount); } else if (method == TransferMethod.TRANSFERFROM) { - tokenIn.safeTransferFrom(msg.sender, receiver, amount); + tokenIn.safeTransferFrom(sender, receiver, amount); } else if (method == TransferMethod.TRANSFERPERMIT2) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom( diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 09521a9..49b95d0 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -149,7 +149,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { TransferMethod method ) { - if (data.length != 84) { + if (data.length != 85) { revert UniswapV3Executor__InvalidDataLength(); } tokenIn = address(bytes20(data[0:20])); @@ -158,7 +158,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { receiver = address(bytes20(data[43:63])); target = address(bytes20(data[63:83])); zeroForOne = uint8(data[83]) > 0; - method = TransferMethod.TRANSFER; + method = TransferMethod(uint8(data[84])); } function _makeV3CallbackData( diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index e9bc79e..df7cc69 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -198,7 +198,13 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( - tokenIn, tokenOut, pool.fee(), receiver, target, zero2one + tokenIn, + tokenOut, + pool.fee(), + receiver, + target, + zero2one, + ExecutorTransferMethods.TransferMethod.TRANSFER ); } } diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 4628754..8d35c0c 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV3Executor.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV3ExecutorExposed is UniswapV3Executor { constructor(address _factory, bytes32 _initCode, address _permit2) @@ -36,13 +38,14 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { } } -contract UniswapV3ExecutorTest is Test, Constants { +contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { using SafeERC20 for IERC20; UniswapV3ExecutorExposed uniswapV3Exposed; UniswapV3ExecutorExposed pancakeV3Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); + IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; @@ -56,12 +59,19 @@ contract UniswapV3ExecutorTest is Test, Constants { PANCAKEV3_POOL_CODE_INIT_HASH, PERMIT2_ADDRESS ); + permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { uint24 expectedPoolFee = 500; bytes memory data = abi.encodePacked( - WETH_ADDR, DAI_ADDR, expectedPoolFee, address(2), address(3), false + WETH_ADDR, + DAI_ADDR, + expectedPoolFee, + address(2), + address(3), + false, + ExecutorTransferMethods.TransferMethod.TRANSFER ); ( @@ -113,8 +123,12 @@ contract UniswapV3ExecutorTest is Test, Constants { uint256 initialPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3); vm.startPrank(DAI_WETH_USV3); - bytes memory protocolData = - abi.encodePacked(WETH_ADDR, DAI_ADDR, poolFee); + bytes memory protocolData = abi.encodePacked( + WETH_ADDR, + DAI_ADDR, + poolFee, + ExecutorTransferMethods.TransferMethod.TRANSFER + ); uint256 dataOffset = 3; // some offset uint256 dataLength = protocolData.length; @@ -125,7 +139,6 @@ contract UniswapV3ExecutorTest is Test, Constants { dataOffset, dataLength, protocolData, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER), address(uniswapV3Exposed) // transferFrom sender (irrelevant in this case) ); uniswapV3Exposed.handleCallback(callbackData); @@ -135,6 +148,88 @@ contract UniswapV3ExecutorTest is Test, Constants { assertEq(finalPoolReserve - initialPoolReserve, amountOwed); } + function testSwapWithTransfer() public { + uint256 amountIn = 10 ** 18; + deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + + bytes memory data = encodeUniswapV3Swap( + WETH_ADDR, + DAI_ADDR, + address(this), + DAI_WETH_USV3, + zeroForOne, + ExecutorTransferMethods.TransferMethod.TRANSFER + ); + + uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); + + assertGe(amountOut, expAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); + assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); + } + + function testSwapWithTransferFrom() public { + uint256 amountIn = 10 ** 18; + deal(WETH_ADDR, address(this), amountIn); + IERC20(WETH_ADDR).approve(address(uniswapV3Exposed), amountIn); + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + + bytes memory data = encodeUniswapV3Swap( + WETH_ADDR, + DAI_ADDR, + address(this), + DAI_WETH_USV3, + zeroForOne, + ExecutorTransferMethods.TransferMethod.TRANSFERFROM + ); + + uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); + + assertGe(amountOut, expAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); + assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); + } + + function testSwapWithPermit2TransferFrom() public { + uint256 amountIn = 10 ** 18; + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + + bytes memory data = encodeUniswapV3Swap( + WETH_ADDR, + DAI_ADDR, + address(this), + DAI_WETH_USV3, + zeroForOne, + ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2 + ); + + deal(WETH_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval( + WETH_ADDR, address(uniswapV3Exposed), amountIn + ); + + // Assume the permit2.approve method will be called from the TychoRouter + // Replicate this secnario in this test. + permit2.permit(ALICE, permitSingle, signature); + uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); + vm.stopPrank(); + + assertGe(amountOut, expAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); + assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); + } + function testSwapFailureInvalidTarget() public { uint256 amountIn = 10 ** 18; deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); @@ -147,7 +242,8 @@ contract UniswapV3ExecutorTest is Test, Constants { uint24(3000), address(this), fakePool, - zeroForOne + zeroForOne, + ExecutorTransferMethods.TransferMethod.TRANSFER ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); @@ -159,11 +255,12 @@ contract UniswapV3ExecutorTest is Test, Constants { address tokenOut, address receiver, address target, - bool zero2one + bool zero2one, + ExecutorTransferMethods.TransferMethod method ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( - tokenIn, tokenOut, pool.fee(), receiver, target, zero2one + tokenIn, tokenOut, pool.fee(), receiver, target, zero2one, method ); } } From d3ff9fd0e26081ae80de05623fad188fe66c4959 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 9 Apr 2025 14:37:41 -0400 Subject: [PATCH 07/12] fix: Fix integration tests with transfer in method support - For now, hardcode them to TRANSFER on the rust encoder side. This will be fixed in an upcoming PR. - Remove the split swap simple route integration test - seemed overkill since simple routes are tested in many other integration tests, the original rust test named doesn't exist anymore, and simple routes should anyway be passing through the single endpoint. --- foundry/test/TychoRouterIntegration.t.sol | 45 ++++---------- foundry/test/TychoRouterSingleSwap.t.sol | 4 +- .../evm/strategy_encoder/strategy_encoders.rs | 61 +++++++++++-------- .../evm/swap_encoder/swap_encoders.rs | 15 +++++ 4 files changed, 65 insertions(+), 60 deletions(-) diff --git a/foundry/test/TychoRouterIntegration.t.sol b/foundry/test/TychoRouterIntegration.t.sol index 9042a89..29f2cb9 100644 --- a/foundry/test/TychoRouterIntegration.t.sol +++ b/foundry/test/TychoRouterIntegration.t.sol @@ -4,36 +4,15 @@ pragma solidity ^0.8.26; import "./TychoRouterTestSetup.sol"; contract TychoRouterTestIntegration is TychoRouterTestSetup { - function testSplitSwapSingleIntegration() public { - // Tests swapping WETH -> DAI on a USV2 pool - deal(WETH_ADDR, ALICE, 1 ether); - uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - - // Approve permit2 - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple` - (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681362ea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdcf2000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041a82e9bdde90314de4b1bf918cc2e8b27da98adcab46e8e99d4e77472a572d6381837e9453095f4cc5e9b25691b678288174e547e040a67d12b36ddfdd1e672d21b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" - ); - - vm.stopPrank(); - - uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balancerAfter - balancerBefore, 2659881924818443699787); - } - function testSplitSwapSingleWithoutPermit2Integration() public { // Tests swapping WETH -> DAI on a USV2 pool without permit2 deal(WETH_ADDR, ALICE, 1 ether); vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), 1 ether); uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_no_permit2` + // Encoded solution generated using `test_split_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"79b9b93b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); vm.stopPrank(); @@ -128,9 +107,9 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Approve permit2 vm.startPrank(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_wrap` + // Encoded solution generated using `test_split_swap_strategy_encoder_wrap` (bool success,) = tychoRouterAddr.call{value: 1 ether}( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006813638900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdd91000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041f6ff7411a7ec76cb7dcafecf5e7f11121b1aa88af505635dc7faae6057e4f44e2859712f58331a14a1624f1e5edf2af80ddd2d90b5453d74df1b1fea10b9a2f91c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e435000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd580000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415563c90eb0f8a79e234e300520b50879242b42622cabd5d01c19e67aba4854a723e0bd8333774062ae03a22b8c5de4a0dfd70bffd9197f56b2063f390610e5891b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700020000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); vm.stopPrank(); @@ -150,9 +129,9 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { // Approve permit2 vm.startPrank(ALICE); IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); - // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_unwrap` + // Encoded solution generated using `test_split_swap_strategy_encoder_unwrap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006813615200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebdb5a000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041a7da748b04674485a5da185055affefc85b6d8fe412accce55b6f67842116f0f7f7130de5d74c68c20e1cedcdf93b8741b9171de2e6a3f2567887382a0712e3f1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be0000000000000000000000000000000000000000000000000000000000000681e436b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6bd730000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000411150ac5d21c61fcd35b486034a433e44d045950a52e991a7ad4035f3ad52beee5d9ecbc5ed9c370730cd2631a050fbe1219dd606e39c8aca40d588aa3eab03411c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000" ); vm.stopPrank(); @@ -204,7 +183,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_swap_strategy_encoder_complex` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681363a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebddab0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415b7ff43991de10c4c3a0372653891d27eb305ce04228bfe46a7d84a0978063fc4cb05183f19b83511bcb689b002d4f8e170f1d3cd77cf18c638229ccb67e0cac1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000160005600028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d0139500005602030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d0139501005601030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d0139501" + hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e2d8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a793000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041e9868ce97a4c09488710ce748362ca38418bda44148bb5faf7820445d47efaff66f8f3667a0b3091c9d67d240db4cc2c95db27bdf019e4d5ff76b7b6620514691b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d53ede3eca2a72b3aecc820e955b36f38437d013950100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -231,7 +210,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_sequential_swap_strategy_encoder_complex_route` (bool success,) = tychoRouterAddr.call( - hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068168aea00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ef04f200000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004154956683effd126a9182e2d82ebd3d778e5283b93d571b13cdbc9dfbf3d9f655057a2332ed566f79bed7514a22ef1c52969132bc71a5a2ef125d78e39ec264511c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a600515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000515615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000" + hex"51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681e493d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c34500000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004189757b528c9874627ca7ecd64bd662db5fd7855837ec98cca5cff2cbd599f9af15baff1d389b5de4ca0dc1106b9d3795a1695934cd5fa1158fffcc8d6495dfaa1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -255,7 +234,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_sequential_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a600515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000515615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d01395010000000000000000000000000000000000000000000000000000" + hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d01395000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a4163ede3eca2a72b3aecc820e955b36f38437d013950100000000000000000000000000000000000000000000000000" ); vm.stopPrank(); @@ -275,7 +254,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_cyclic_sequential_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681363d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ebddda0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000418d58a54a3b8afc5d2e228ce6c5a1ab6b342cb5bfd9a00d57b869a4703ca2bb084d10d21f6842be9652a9ff2392673fbdcb961439ccc962de09f6bc64e5e665fe1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de006d00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564001006d01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f4308e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e4a0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6c40a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416a11f27f0546e9cc9d27e4383a3f860d9822f0d7d0117e73abbf03007b3e235b36c2288ff083a04f51285092ff23422b3e00a5a292d5627172dfd031308fc3a11b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99889294); @@ -291,7 +270,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_input_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006816408300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067eeba8b0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416de253b927fdcf110d157372e620e70c7220d3c01f04e01cdffb076edbb8b42052d281dd6c55a2349502742a0a8de58d2d1dbdc452f6c9d695b1c732c023d0561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000136006d00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564001006d00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d801005601000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d1600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a71e00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004129478d2bffac3e378054d024ca3461f61e55552e2e8b0c7e0869f38ee834462f157f761e1a67c713f5749a6f6210dc4ac998a0521dda8dcb780b35d774250a141c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d01395000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99574171); @@ -307,7 +286,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup { IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_split_output_cyclic_swap` (bool success,) = tychoRouterAddr.call( - hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006816418400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067eebb8c0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412c44c7de8f7eaaea61e49dbdefdc5606925db6f93db0789e632899ac88d3c7677cc8b69719603ab1b5ecef07d659b7254881d0667a49ebccbf43949b760b041a1b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000136005600010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d0139501006d01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f564000006d01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80000000000000000000000" + hex"7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005eea514000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000681e2d3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f6a738000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041196745237299bfab79f038c0c12decec9c46b264621e1f00bbef32b15a22f15543defe95694dd1616aa8785bbb6f91a1557eebc97c0f5ca968c99f250da1b2ce1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000" ); assertEq(IERC20(USDC_ADDR).balanceOf(ALICE), 99525908); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 648d9e9..246a95c 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -267,7 +267,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder_no_permit2` (bool success,) = tychoRouterAddr.call( - hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" ); vm.stopPrank(); @@ -286,7 +286,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max); // Encoded solution generated using `test_single_swap_strategy_encoder` (bool success,) = tychoRouterAddr.call( - hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006817833200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067effd3a00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417efea09004d5d40d8d072e1ce0a425507717ea485c765eb90c170859197d362b502fb039b4f5cdce57318ecfe3ab276d1ac87771eb5d017b253a8f4107e6a20b1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000515615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" + hex"30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000903146e5f6c59c064b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006817833200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067effd3a00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417efea09004d5d40d8d072e1ce0a425507717ea485c765eb90c170859197d362b502fb039b4f5cdce57318ecfe3ab276d1ac87771eb5d017b253a8f4107e6a20b1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000" ); vm.stopPrank(); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index d1c683b..cbb82be 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -709,9 +709,9 @@ mod tests { let expected_swaps = String::from(concat!( // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000058", + "0000000000000000000000000000000000000000000000000000000000000059", // ple encoded swaps - "0056", + "0057", // Swap header "00", // token in index "01", // token out index @@ -722,7 +722,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // exact out + "00", // transfer method "00000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -817,8 +817,8 @@ mod tests { // it's hard to assert let expected_swap = String::from(concat!( - // length of ple encoded swaps without padding - "0000000000000000000000000000000000000000000000000000000000000051", + // length of encoded swap without padding + "0000000000000000000000000000000000000000000000000000000000000052", // Swap data "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in @@ -826,7 +826,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "0000000000000000000000000000", // padding + "00", // transfer method + "00000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -1475,7 +1476,7 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes - "0000000000000000000000000000000000000000000000000000000000000051", // length of swap bytes without padding + "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding // Swap data "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address @@ -1484,7 +1485,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "0000000000000000000000000000", // padding + "00", // transfer method + "00000000000000000000000000", // padding ] .join(""); @@ -1554,8 +1556,8 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000002", // tokens length "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "0000000000000000000000000000000000000000000000000000000000000120", // offset of ple encoded swaps - "0000000000000000000000000000000000000000000000000000000000000058", // length of ple encoded swaps without padding - "0056", // ple encoded swaps + "0000000000000000000000000000000000000000000000000000000000000059", // length of ple encoded swaps without padding + "0057", // ple encoded swaps // Swap header "00", // token in index "01", // token out index @@ -1567,7 +1569,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "00000000000000", // padding + "00", // transfer method + "000000000000", // padding ] .join(""); @@ -1806,8 +1809,8 @@ mod tests { .join(""); let expected_swaps = [ - "00000000000000000000000000000000000000000000000000000000000000de", // length of ple encoded swaps without padding - "006d", // ple encoded swaps + "00000000000000000000000000000000000000000000000000000000000000e0", // length of ple encoded swaps without padding + "006e", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -1818,7 +1821,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer method + "006e", // ple encoded swaps "01", // token in index "00000000", // split "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address @@ -1827,7 +1831,8 @@ mod tests { "000bb8", // pool fee "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id - "000000", // zero2one + "00", // zero2one + "00", // transfer method ] .join(""); @@ -1958,8 +1963,8 @@ mod tests { ] .join(""); let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000136", // length of ple encoded swaps without padding - "006d", // ple encoded swaps + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "006e", // ple encoded swaps "00", // token in index "01", // token out index "999999", // split @@ -1970,7 +1975,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer method + "006e", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -1981,7 +1987,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "01", // zero2one - "0056", // ple encoded swaps + "00", // transfer method + "0057", // ple encoded swaps "01", // token in index "00", // token out index "000000", // split @@ -1990,7 +1997,8 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "00", // zero2one - "00000000000000000000" // padding + "00", // transfer method + "00000000000000" // padding ] .join(""); assert_eq!(hex_calldata[..520], expected_input); @@ -2117,8 +2125,8 @@ mod tests { .join(""); let expected_swaps = [ - "0000000000000000000000000000000000000000000000000000000000000136", // length of ple encoded swaps without padding - "0056", // ple encoded swaps + "0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding + "0057", // ple encoded swaps "00", // token in index "01", // token out index "000000", // split @@ -2127,7 +2135,8 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "01", // zero2one - "006d", // ple encoded swaps + "00", // transfer method + "006e", // ple encoded swaps "01", // token in index "00", // token out index "999999", // split @@ -2138,7 +2147,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "00", // zero2one - "006d", // ple encoded swaps + "00", // transfer method + "006e", // ple encoded swaps "01", // token in index "00", // token out index "000000", // split @@ -2149,7 +2159,8 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00000000000000000000" // padding + "00", // transfer method + "00000000000000" // padding ] .join(""); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index a4414b1..bdc170e 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -22,6 +22,15 @@ use crate::encoding::{ swap_encoder::SwapEncoder, }; +#[allow(dead_code)] +#[repr(u8)] +pub enum TransferType { + Transfer = 0, + TransferFrom = 1, + Permit2Transfer = 2, + None = 3, +} + /// Encodes a swap on a Uniswap V2 pool through the given executor address. /// /// # Fields @@ -66,6 +75,7 @@ impl SwapEncoder for UniswapV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, zero_to_one, + (TransferType::Transfer as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -128,6 +138,7 @@ impl SwapEncoder for UniswapV3SwapEncoder { bytes_to_address(&encoding_context.receiver)?, component_id, zero_to_one, + (TransferType::Transfer as u8).to_be_bytes(), ); Ok(args.abi_encode_packed()) @@ -606,6 +617,8 @@ mod tests { "0000000000000000000000000000000000000001", // zero for one "00", + // transfer type (transfer) + "00", )) ); } @@ -661,6 +674,8 @@ mod tests { "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // zero for one "00", + // transfer type (transfer) + "00", )) ); } From 61c0163bee363bcd14644f8d0a8616e4970b4fdc Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 9 Apr 2025 14:56:57 -0400 Subject: [PATCH 08/12] fix: test fix after rebase --- src/encoding/evm/tycho_encoders.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 2c7f474..3a0c96f 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -916,6 +916,8 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", + // transfer method + "00", )) ); } From 4a20fa621557d754cb677af41aa72de5cd7a6ffb Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 9 Apr 2025 15:10:02 -0400 Subject: [PATCH 09/12] fix: Conscious slither silencing - This arbitrary sender should be same, since the sender is always set to be msg.sender from within the contract (no calldata can be sent specifying otherwise). This is just used to circumvent msg.sender being the pool/protocol during callbacks. --- foundry/src/executors/ExecutorTransferMethods.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index 6eb73a8..ed4a141 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -40,6 +40,7 @@ contract ExecutorTransferMethods { if (method == TransferMethod.TRANSFER) { tokenIn.safeTransfer(receiver, amount); } else if (method == TransferMethod.TRANSFERFROM) { + // slither-disable-next-line arbitrary-send-erc20 tokenIn.safeTransferFrom(sender, receiver, amount); } else if (method == TransferMethod.TRANSFERPERMIT2) { // Permit2.permit is already called from the TychoRouter From 4bc29f283b37dbf11958a954e277be5be3adf8b0 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 10 Apr 2025 11:02:04 -0400 Subject: [PATCH 10/12] chore: Renamings + comment fixes - Renamed ExecutorTransferMethods to TokenTransfer to avoid leaking information about how the transfer happens into the executor. The executor shouldn't care if there are multiple methods or one single method that takes care of everything. - Also renamed TransferMethod to TransferType to match the rust encoding --- ...rTransferMethods.sol => TokenTransfer.sol} | 18 +++++----- foundry/src/executors/UniswapV2Executor.sol | 17 ++++----- foundry/src/executors/UniswapV3Executor.sol | 24 ++++++------- foundry/test/TychoRouterTestSetup.sol | 4 +-- .../test/executors/UniswapV2Executor.t.sol | 29 ++++++++------- .../test/executors/UniswapV3Executor.t.sol | 36 +++++++++---------- 6 files changed, 63 insertions(+), 65 deletions(-) rename foundry/src/executors/{ExecutorTransferMethods.sol => TokenTransfer.sol} (75%) diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/TokenTransfer.sol similarity index 75% rename from foundry/src/executors/ExecutorTransferMethods.sol rename to foundry/src/executors/TokenTransfer.sol index ed4a141..0e69f13 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -5,14 +5,14 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; -error ExecutorTransferMethods__InvalidPermit2(); +error TokenTransfer__InvalidPermit2(); -contract ExecutorTransferMethods { +contract TokenTransfer { using SafeERC20 for IERC20; IAllowanceTransfer public immutable permit2; - enum TransferMethod { + enum TransferType { // Assume funds are in the TychoRouter - transfer into the pool TRANSFER, // Assume funds are in msg.sender's wallet - transferFrom into the pool @@ -25,7 +25,7 @@ contract ExecutorTransferMethods { constructor(address _permit2) { if (_permit2 == address(0)) { - revert ExecutorTransferMethods__InvalidPermit2(); + revert TokenTransfer__InvalidPermit2(); } permit2 = IAllowanceTransfer(_permit2); } @@ -35,20 +35,18 @@ contract ExecutorTransferMethods { address sender, address receiver, uint256 amount, - TransferMethod method + TransferType transferType ) internal { - if (method == TransferMethod.TRANSFER) { + if (transferType == TransferType.TRANSFER) { tokenIn.safeTransfer(receiver, amount); - } else if (method == TransferMethod.TRANSFERFROM) { + } else if (transferType == TransferType.TRANSFERFROM) { // slither-disable-next-line arbitrary-send-erc20 tokenIn.safeTransferFrom(sender, receiver, amount); - } else if (method == TransferMethod.TRANSFERPERMIT2) { + } else if (transferType == TransferType.TRANSFERPERMIT2) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom( sender, receiver, uint160(amount), address(tokenIn) ); - } else { - // Funds are likely already in pool. Do nothing. } } } diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index e5d5a3b..2ddcd97 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; -import "./ExecutorTransferMethods.sol"; +import "./TokenTransfer.sol"; error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); -contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { +contract UniswapV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address public immutable factory; @@ -19,7 +19,7 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { address private immutable self; constructor(address _factory, bytes32 _initCode, address _permit2) - ExecutorTransferMethods(_permit2) + TokenTransfer(_permit2) { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); @@ -42,14 +42,15 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { address target; address receiver; bool zeroForOne; - TransferMethod method; + TransferType transferType; - (tokenIn, target, receiver, zeroForOne, method) = _decodeData(data); + (tokenIn, target, receiver, zeroForOne, transferType) = + _decodeData(data); _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - _transfer(tokenIn, msg.sender, target, givenAmount, method); + _transfer(tokenIn, msg.sender, target, givenAmount, transferType); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { @@ -67,7 +68,7 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { address target, address receiver, bool zeroForOne, - TransferMethod method + TransferType transferType ) { if (data.length != 62) { @@ -77,7 +78,7 @@ contract UniswapV2Executor is IExecutor, ExecutorTransferMethods { target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); zeroForOne = uint8(data[60]) > 0; - method = TransferMethod(uint8(data[61])); + transferType = TransferType(uint8(data[61])); } function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 49b95d0..043f3a0 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -5,14 +5,14 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@interfaces/ICallback.sol"; -import {ExecutorTransferMethods} from "./ExecutorTransferMethods.sol"; +import {TokenTransfer} from "./TokenTransfer.sol"; error UniswapV3Executor__InvalidDataLength(); error UniswapV3Executor__InvalidFactory(); error UniswapV3Executor__InvalidTarget(); error UniswapV3Executor__InvalidInitCode(); -contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { +contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { using SafeERC20 for IERC20; uint160 private constant MIN_SQRT_RATIO = 4295128739; @@ -24,7 +24,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { address private immutable self; constructor(address _factory, bytes32 _initCode, address _permit2) - ExecutorTransferMethods(_permit2) + TokenTransfer(_permit2) { if (_factory == address(0)) { revert UniswapV3Executor__InvalidFactory(); @@ -50,7 +50,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { address receiver, address target, bool zeroForOne, - TransferMethod method + TransferType transferType ) = _decodeData(data); _verifyPairAddress(tokenIn, tokenOut, fee, target); @@ -60,7 +60,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { IUniswapV3Pool pool = IUniswapV3Pool(target); bytes memory callbackData = - _makeV3CallbackData(tokenIn, tokenOut, fee, method); + _makeV3CallbackData(tokenIn, tokenOut, fee, transferType); { (amount0, amount1) = pool.swap( @@ -98,10 +98,10 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { address tokenIn = address(bytes20(msgData[132:152])); require( - uint8(msgData[171]) <= uint8(TransferMethod.NONE), + uint8(msgData[171]) <= uint8(TransferType.NONE), "InvalidTransferMethod" ); - TransferMethod method = TransferMethod(uint8(msgData[171])); + TransferType transferType = TransferType(uint8(msgData[171])); address sender = address(bytes20(msgData[172:192])); verifyCallback(msgData[132:]); @@ -109,7 +109,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - _transfer(IERC20(tokenIn), sender, msg.sender, amountOwed, method); + _transfer(IERC20(tokenIn), sender, msg.sender, amountOwed, transferType); return abi.encode(amountOwed, tokenIn); } @@ -146,7 +146,7 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { address receiver, address target, bool zeroForOne, - TransferMethod method + TransferType transferType ) { if (data.length != 85) { @@ -158,17 +158,17 @@ contract UniswapV3Executor is IExecutor, ICallback, ExecutorTransferMethods { receiver = address(bytes20(data[43:63])); target = address(bytes20(data[63:83])); zeroForOne = uint8(data[83]) > 0; - method = TransferMethod(uint8(data[84])); + transferType = TransferType(uint8(data[84])); } function _makeV3CallbackData( address tokenIn, address tokenOut, uint24 fee, - TransferMethod method + TransferType transferType ) internal pure returns (bytes memory) { return abi.encodePacked( - tokenIn, tokenOut, fee, uint8(method), msg.sender, self + tokenIn, tokenOut, fee, uint8(transferType), msg.sender, self ); } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index df7cc69..961f0f8 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -185,7 +185,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { target, receiver, zero2one, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); } @@ -204,7 +204,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper { receiver, target, zero2one, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); } } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 42abec1..c993ff9 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV2Executor.sol"; -import "@src/executors/ExecutorTransferMethods.sol"; +import "@src/executors/TokenTransfer.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; @@ -20,7 +20,7 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor { address target, address receiver, bool zeroForOne, - TransferMethod method + TransferType transferType ) { return _decodeData(data); @@ -84,7 +84,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); ( @@ -92,7 +92,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address target, address receiver, bool zeroForOne, - ExecutorTransferMethods.TransferMethod method + TokenTransfer.TransferType transferType ) = uniswapV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -100,8 +100,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(3)); assertEq(zeroForOne, false); assertEq( - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER), - uint8(method) + uint8(TokenTransfer.TransferType.TRANSFER), uint8(transferType) ); } @@ -158,7 +157,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER) ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -177,7 +176,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFERFROM) + uint8(TokenTransfer.TransferType.TRANSFERFROM) ); deal(WETH_ADDR, address(this), amountIn); @@ -198,7 +197,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, ALICE, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2) + uint8(TokenTransfer.TransferType.TRANSFERPERMIT2) ); deal(WETH_ADDR, ALICE, amountIn); @@ -211,7 +210,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { ); // Assume the permit2.approve method will be called from the TychoRouter - // Replicate this secnario in this test. + // Replicate this scenario in this test. permit2.permit(ALICE, permitSingle, signature); uniswapV2Exposed.swap(amountIn, protocolData); @@ -230,7 +229,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.NONE) + uint8(TokenTransfer.TransferType.NONE) ); deal(WETH_ADDR, address(this), amountIn); @@ -251,7 +250,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address target, address receiver, bool zeroForOne, - ExecutorTransferMethods.TransferMethod method + TokenTransfer.TransferType transferType ) = uniswapV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); @@ -259,7 +258,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); // TRANSFER = 0 - assertEq(0, uint8(method)); + assertEq(0, uint8(transferType)); } function testSwapIntegration() public { @@ -284,7 +283,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { fakePool, BOB, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER) ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -304,7 +303,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { USDC_MAG7_POOL, BOB, zeroForOne, - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) + uint8(TokenTransfer.TransferType.TRANSFER) ); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index 8d35c0c..a81a6e5 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -22,7 +22,7 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { address receiver, address target, bool zeroForOne, - TransferMethod method + TransferType method ) { return _decodeData(data); @@ -71,7 +71,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); ( @@ -81,7 +81,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zeroForOne, - ExecutorTransferMethods.TransferMethod method + TokenTransfer.TransferType method ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -90,10 +90,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); - assertEq( - uint8(method), - uint8(ExecutorTransferMethods.TransferMethod.TRANSFER) - ); + assertEq(uint8(method), uint8(TokenTransfer.TransferType.TRANSFER)); } function testDecodeParamsInvalidDataLength() public { @@ -124,10 +121,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { vm.startPrank(DAI_WETH_USV3); bytes memory protocolData = abi.encodePacked( - WETH_ADDR, - DAI_ADDR, - poolFee, - ExecutorTransferMethods.TransferMethod.TRANSFER + WETH_ADDR, DAI_ADDR, poolFee, TokenTransfer.TransferType.TRANSFER ); uint256 dataOffset = 3; // some offset uint256 dataLength = protocolData.length; @@ -161,7 +155,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), DAI_WETH_USV3, zeroForOne, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); @@ -185,7 +179,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), DAI_WETH_USV3, zeroForOne, - ExecutorTransferMethods.TransferMethod.TRANSFERFROM + TokenTransfer.TransferType.TRANSFERFROM ); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); @@ -207,7 +201,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), DAI_WETH_USV3, zeroForOne, - ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2 + TokenTransfer.TransferType.TRANSFERPERMIT2 ); deal(WETH_ADDR, ALICE, amountIn); @@ -220,7 +214,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { ); // Assume the permit2.approve method will be called from the TychoRouter - // Replicate this secnario in this test. + // Replicate this scenario in this test. permit2.permit(ALICE, permitSingle, signature); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); vm.stopPrank(); @@ -243,7 +237,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), fakePool, zeroForOne, - ExecutorTransferMethods.TransferMethod.TRANSFER + TokenTransfer.TransferType.TRANSFER ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); @@ -256,11 +250,17 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zero2one, - ExecutorTransferMethods.TransferMethod method + TokenTransfer.TransferType transferType ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( - tokenIn, tokenOut, pool.fee(), receiver, target, zero2one, method + tokenIn, + tokenOut, + pool.fee(), + receiver, + target, + zero2one, + transferType ); } } From 7e98145ad7d96b22c087b306eed38142396d062a Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 10:20:52 -0400 Subject: [PATCH 11/12] fix: bad merge fn should be view, not pure --- foundry/src/executors/UniswapV3Executor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 043f3a0..20df282 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -166,7 +166,7 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { address tokenOut, uint24 fee, TransferType transferType - ) internal pure returns (bytes memory) { + ) internal view returns (bytes memory) { return abi.encodePacked( tokenIn, tokenOut, fee, uint8(transferType), msg.sender, self ); From f3c4128eda18dce0d573f9d8e0cb75100d922d11 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Mon, 14 Apr 2025 11:23:08 -0400 Subject: [PATCH 12/12] fix: USV3 encoding/decoding after rebase - Also do some renamings and comment improvements: transfer method -> type - Remove integration tests since we no longer support direct calls to USV3 executor, even for testing. --- foundry/src/executors/TokenTransfer.sol | 4 +- foundry/src/executors/UniswapV3Executor.sol | 16 ++-- .../test/executors/UniswapV3Executor.t.sol | 90 ++----------------- .../evm/strategy_encoder/strategy_encoders.rs | 24 ++--- src/encoding/evm/tycho_encoders.rs | 2 +- 5 files changed, 29 insertions(+), 107 deletions(-) diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index 0e69f13..f529b69 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -5,7 +5,7 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; -error TokenTransfer__InvalidPermit2(); +error TokenTransfer__AddressZero(); contract TokenTransfer { using SafeERC20 for IERC20; @@ -25,7 +25,7 @@ contract TokenTransfer { constructor(address _permit2) { if (_permit2 == address(0)) { - revert TokenTransfer__InvalidPermit2(); + revert TokenTransfer__AddressZero(); } permit2 = IAllowanceTransfer(_permit2); } diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 20df282..dbc794e 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -11,6 +11,7 @@ error UniswapV3Executor__InvalidDataLength(); error UniswapV3Executor__InvalidFactory(); error UniswapV3Executor__InvalidTarget(); error UniswapV3Executor__InvalidInitCode(); +error UniswapV3Executor__InvalidTransferType(uint8 transferType); contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { using SafeERC20 for IERC20; @@ -97,12 +98,13 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { address tokenIn = address(bytes20(msgData[132:152])); - require( - uint8(msgData[171]) <= uint8(TransferType.NONE), - "InvalidTransferMethod" - ); - TransferType transferType = TransferType(uint8(msgData[171])); - address sender = address(bytes20(msgData[172:192])); + // Transfer type does not exist + if (uint8(msgData[175]) > uint8(TransferType.NONE)) { + revert UniswapV3Executor__InvalidTransferType(uint8(msgData[175])); + } + + TransferType transferType = TransferType(uint8(msgData[175])); + address sender = address(bytes20(msgData[176:196])); verifyCallback(msgData[132:]); @@ -168,7 +170,7 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { TransferType transferType ) internal view returns (bytes memory) { return abi.encodePacked( - tokenIn, tokenOut, fee, uint8(transferType), msg.sender, self + tokenIn, tokenOut, fee, uint8(transferType), msg.sender ); } diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index a81a6e5..24bfd22 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -22,7 +22,7 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { address receiver, address target, bool zeroForOne, - TransferType method + TransferType transferType ) { return _decodeData(data); @@ -81,7 +81,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zeroForOne, - TokenTransfer.TransferType method + TokenTransfer.TransferType transferType ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -90,7 +90,9 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(2)); assertEq(target, address(3)); assertEq(zeroForOne, false); - assertEq(uint8(method), uint8(TokenTransfer.TransferType.TRANSFER)); + assertEq( + uint8(transferType), uint8(TokenTransfer.TransferType.TRANSFER) + ); } function testDecodeParamsInvalidDataLength() public { @@ -142,88 +144,6 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(finalPoolReserve - initialPoolReserve, amountOwed); } - function testSwapWithTransfer() public { - uint256 amountIn = 10 ** 18; - deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); - - uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI - bool zeroForOne = false; - - bytes memory data = encodeUniswapV3Swap( - WETH_ADDR, - DAI_ADDR, - address(this), - DAI_WETH_USV3, - zeroForOne, - TokenTransfer.TransferType.TRANSFER - ); - - uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); - - assertGe(amountOut, expAmountOut); - assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); - assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); - } - - function testSwapWithTransferFrom() public { - uint256 amountIn = 10 ** 18; - deal(WETH_ADDR, address(this), amountIn); - IERC20(WETH_ADDR).approve(address(uniswapV3Exposed), amountIn); - - uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI - bool zeroForOne = false; - - bytes memory data = encodeUniswapV3Swap( - WETH_ADDR, - DAI_ADDR, - address(this), - DAI_WETH_USV3, - zeroForOne, - TokenTransfer.TransferType.TRANSFERFROM - ); - - uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); - - assertGe(amountOut, expAmountOut); - assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); - assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); - } - - function testSwapWithPermit2TransferFrom() public { - uint256 amountIn = 10 ** 18; - - uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI - bool zeroForOne = false; - - bytes memory data = encodeUniswapV3Swap( - WETH_ADDR, - DAI_ADDR, - address(this), - DAI_WETH_USV3, - zeroForOne, - TokenTransfer.TransferType.TRANSFERPERMIT2 - ); - - deal(WETH_ADDR, ALICE, amountIn); - vm.startPrank(ALICE); - ( - IAllowanceTransfer.PermitSingle memory permitSingle, - bytes memory signature - ) = handlePermit2Approval( - WETH_ADDR, address(uniswapV3Exposed), amountIn - ); - - // Assume the permit2.approve method will be called from the TychoRouter - // Replicate this scenario in this test. - permit2.permit(ALICE, permitSingle, signature); - uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); - vm.stopPrank(); - - assertGe(amountOut, expAmountOut); - assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0); - assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut); - } - function testSwapFailureInvalidTarget() public { uint256 amountIn = 10 ** 18; deal(WETH_ADDR, address(uniswapV3Exposed), amountIn); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index cbb82be..3151034 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -722,7 +722,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one - "00", // transfer method + "00", // transfer type "00000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -826,7 +826,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "00", // transfer method + "00", // transfer type "00000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -1485,7 +1485,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "00", // transfer method + "00", // transfer type "00000000000000000000000000", // padding ] .join(""); @@ -1569,7 +1569,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out - "00", // transfer method + "00", // transfer type "000000000000", // padding ] .join(""); @@ -1821,7 +1821,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "00", // transfer method + "00", // transfer type "006e", // ple encoded swaps "01", // token in index "00000000", // split @@ -1832,7 +1832,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00", // transfer method + "00", // transfer type ] .join(""); @@ -1975,7 +1975,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "00", // transfer method + "00", // transfer type "006e", // ple encoded swaps "00", // token in index "01", // token out index @@ -1987,7 +1987,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "01", // zero2one - "00", // transfer method + "00", // transfer type "0057", // ple encoded swaps "01", // token in index "00", // token out index @@ -1997,7 +1997,7 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "00", // zero2one - "00", // transfer method + "00", // transfer type "00000000000000" // padding ] .join(""); @@ -2135,7 +2135,7 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "01", // zero2one - "00", // transfer method + "00", // transfer type "006e", // ple encoded swaps "01", // token in index "00", // token out index @@ -2147,7 +2147,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "00", // zero2one - "00", // transfer method + "00", // transfer type "006e", // ple encoded swaps "01", // token in index "00", // token out index @@ -2159,7 +2159,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // router address "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00", // transfer method + "00", // transfer type "00000000000000" // padding ] .join(""); diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 3a0c96f..92706fc 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -916,7 +916,7 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", - // transfer method + // transfer type "00", )) );