From d8b44f623b8175f4759f8a8cbd42c46e5abad3b4 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 29 Jan 2025 16:56:45 -0500 Subject: [PATCH 01/21] fix: Remove exactOut from USV3 encoding - We don't support exactOut atm --- src/encoding/evm/swap_encoder/encoders.rs | 3 --- src/encoding/models.rs | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index de13cfc..f4fbf24 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -113,7 +113,6 @@ impl SwapEncoder for UniswapV3SwapEncoder { bytes_to_address(&encoding_context.receiver)?, component_id, zero_to_one, - encoding_context.exact_out, ); Ok(args.abi_encode_packed()) @@ -259,8 +258,6 @@ mod tests { "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // zero for one "00", - // exact out - "00", )) ); } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 4e2cbbd..5e37500 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -65,6 +65,7 @@ pub struct Transaction { #[allow(dead_code)] pub struct EncodingContext { pub receiver: Bytes, + // TODO should we keep this? pub exact_out: bool, pub router_address: Bytes, } From ca32446a9ee28118d8857c02abefd24389485b7e Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 29 Jan 2025 19:51:17 -0500 Subject: [PATCH 02/21] feat: UniswapV3Executor and integration tests - Note: I think we can get the fee straight from the pool... why did we always encode this and send it from the solver? Is this bound to change sometimes? --- foundry/src/TychoRouter.sol | 6 +- foundry/src/executors/UniswapV3Executor.sol | 85 +++++++++++++++++++ foundry/test/Constants.sol | 3 + foundry/test/TychoRouter.t.sol | 56 +++++++++++- foundry/test/TychoRouterTestSetup.sol | 22 ++++- .../test/executors/UniswapV3Executor.t.sol | 68 +++++++++++++++ 6 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 foundry/src/executors/UniswapV3Executor.sol create mode 100644 foundry/test/executors/UniswapV3Executor.t.sol diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 52b00e1..2b98667 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -369,8 +369,8 @@ contract TychoRouter is int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) internal view returns (uint256 amountOwed, address tokenOwed) { - address tokenIn = address(bytes20(data[0:20])); + ) internal view returns (uint256 amountOwed, address tokenIn) { + tokenIn = address(bytes20(data[0:20])); address tokenOut = address(bytes20(data[20:40])); uint24 poolFee = uint24(bytes3(data[40:43])); @@ -382,6 +382,6 @@ contract TychoRouter is amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - return (amountOwed, tokenOwed); + return (amountOwed, tokenIn); } } diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol new file mode 100644 index 0000000..84c0044 --- /dev/null +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@interfaces/IExecutor.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; + +error UniswapV3Executor__InvalidDataLength(); + +contract UniswapV3Executor is IExecutor { + uint160 private constant MIN_SQRT_RATIO = 4295128739; + uint160 private constant MAX_SQRT_RATIO = + 1461446703485210103287273052203988822378723970342; + address private constant factoryV3 = + 0x1F98431c8aD98523631AE4a59f267346ea31F984; + + // slither-disable-next-line locked-ether + function swap(uint256 amountIn, bytes calldata data) + external + payable + returns (uint256 amountOut) + { + ( + address tokenIn, + address tokenOut, + uint24 fee, + address receiver, + address target, + bool zeroForOne + ) = _decodeData(data); + int256 amount0; + int256 amount1; + IUniswapV3Pool pool = IUniswapV3Pool(target); + + bytes memory callbackData = _makeV3CallbackData(tokenIn, tokenOut, fee); + + { + (amount0, amount1) = pool.swap( + receiver, + zeroForOne, + // positive means exactIn + int256(amountIn), + zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + callbackData + ); + } + + if (zeroForOne) { + amountOut = amount1 > 0 ? uint256(amount1) : uint256(-amount1); + } else { + amountOut = amount0 > 0 ? uint256(amount0) : uint256(-amount0); + } + } + + function _decodeData(bytes calldata data) + internal + pure + returns ( + address tokenIn, + address tokenOut, + uint24 fee, + address receiver, + address target, + bool zeroForOne + ) + { + if (data.length != 84) { + revert UniswapV3Executor__InvalidDataLength(); + } + tokenIn = address(bytes20(data[0:20])); + tokenOut = address(bytes20(data[20:40])); + fee = uint24(bytes3(data[40:43])); + receiver = address(bytes20(data[43:63])); + target = address(bytes20(data[63:83])); + zeroForOne = uint8(data[83]) > 0; + } + + function _makeV3CallbackData(address tokenIn, address tokenOut, uint24 fee) + internal + pure + returns (bytes memory) + { + return abi.encodePacked(tokenIn, tokenOut, fee); + } +} diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 8776c7d..a82b9e1 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -32,6 +32,9 @@ contract Constants is Test { address WETH_WBTC_POOL = 0xBb2b8038a1640196FbE3e38816F3e67Cba72D940; address USDC_WBTC_POOL = 0x004375Dff511095CC5A197A54140a24eFEF3A416; + // uniswap v3 + address DAI_WETH_USV3 = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8; + /** * @dev Deploys a dummy contract with non-empty bytecode */ diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index d736494..15feb1d 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -215,7 +215,7 @@ contract TychoRouterTest is TychoRouterTestSetup { function testSwapSimple() public { // Trade 1 WETH for DAI with 1 swap on Uniswap V2 // 1 WETH -> DAI - // (univ2) + // (USV2) uint256 amountIn = 1 ether; deal(WETH_ADDR, tychoRouterAddr, amountIn); @@ -234,7 +234,7 @@ contract TychoRouterTest is TychoRouterTestSetup { bytes[] memory swaps = new bytes[](1); swaps[0] = swap; - tychoRouter.ExposedSwap(amountIn, 2, pleEncode(swaps)); + tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps)); uint256 daiBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr); assertEq(daiBalance, 2630432278145144658455); @@ -271,7 +271,7 @@ contract TychoRouterTest is TychoRouterTestSetup { encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true) ); - tychoRouter.ExposedSwap(amountIn, 3, pleEncode(swaps)); + tychoRouter.exposedSwap(amountIn, 3, pleEncode(swaps)); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr); assertEq(usdcBalance, 2610580090); @@ -332,7 +332,7 @@ contract TychoRouterTest is TychoRouterTestSetup { encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, tychoRouterAddr, true) ); - tychoRouter.ExposedSwap(amountIn, 4, pleEncode(swaps)); + tychoRouter.exposedSwap(amountIn, 4, pleEncode(swaps)); uint256 usdcBalance = IERC20(USDC_ADDR).balanceOf(tychoRouterAddr); assertEq(usdcBalance, 2581503157); @@ -606,4 +606,52 @@ contract TychoRouterTest is TychoRouterTestSetup { vm.stopPrank(); } + + function testUSV3Callback() public { + uint24 poolFee = 3000; + uint256 amountOwed = 1000000000000000000; + deal(WETH_ADDR, tychoRouterAddr, amountOwed); + uint256 initialPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3); + + vm.startPrank(DAI_WETH_USV3); + tychoRouter.uniswapV3SwapCallback( + -2631245338449998525223, + int256(amountOwed), + abi.encodePacked(WETH_ADDR, DAI_ADDR, poolFee) + ); + vm.stopPrank(); + + uint256 finalPoolReserve = IERC20(WETH_ADDR).balanceOf(DAI_WETH_USV3); + assertEq(finalPoolReserve - initialPoolReserve, amountOwed); + } + + function testSwapSingleUSV3() public { + // Trade 1 WETH for DAI with 1 swap on Uniswap V3 + // 1 WETH -> DAI + // (USV3) + uint256 amountIn = 10 ** 18; + deal(WETH_ADDR, tychoRouterAddr, amountIn); + + uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI + bool zeroForOne = false; + bytes memory protocolData = encodeUniswapV3Swap( + WETH_ADDR, DAI_ADDR, tychoRouterAddr, DAI_WETH_USV3, zeroForOne + ); + bytes memory swap = encodeSwap( + uint8(0), + uint8(1), + uint24(0), + address(usv3Executor), + bytes4(0), + protocolData + ); + + bytes[] memory swaps = new bytes[](1); + swaps[0] = swap; + + tychoRouter.exposedSwap(amountIn, 2, pleEncode(swaps)); + + uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr); + assertGe(finalBalance, expAmountOut); + } } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index a4404c0..22ae546 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -6,6 +6,7 @@ import "./Constants.sol"; import "./mock/MockERC20.sol"; import "@src/TychoRouter.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; +import "../src/executors/UniswapV3Executor.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth, address usv3Factory) @@ -20,7 +21,7 @@ contract TychoRouterExposed is TychoRouter { return _unwrapETH(amount); } - function ExposedSwap( + function exposedSwap( uint256 amountIn, uint256 nTokens, bytes calldata swaps @@ -34,6 +35,7 @@ contract TychoRouterTestSetup is Test, Constants { address tychoRouterAddr; address permit2Address = address(0x000000000022D473030F116dDEE9F6B43aC78BA3); UniswapV2Executor public usv2Executor; + UniswapV3Executor public usv3Executor; MockERC20[] tokens; function setUp() public { @@ -41,8 +43,9 @@ contract TychoRouterTestSetup is Test, Constants { vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.startPrank(ADMIN); + address factoryV3 = address(0x1F98431c8aD98523631AE4a59f267346ea31F984); tychoRouter = - new TychoRouterExposed(permit2Address, WETH_ADDR, address(1)); + new TychoRouterExposed(permit2Address, WETH_ADDR, factoryV3); tychoRouterAddr = address(tychoRouter); tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER); tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER); @@ -55,8 +58,10 @@ contract TychoRouterTestSetup is Test, Constants { vm.stopPrank(); usv2Executor = new UniswapV2Executor(); + usv3Executor = new UniswapV3Executor(); vm.startPrank(EXECUTOR_SETTER); tychoRouter.setExecutor(address(usv2Executor)); + tychoRouter.setExecutor(address(usv3Executor)); vm.stopPrank(); vm.startPrank(BOB); @@ -190,4 +195,17 @@ contract TychoRouterTestSetup is Test, Constants { ) internal pure returns (bytes memory) { return abi.encodePacked(tokenIn, target, receiver, zero2one); } + + function encodeUniswapV3Swap( + address tokenIn, + address tokenOut, + address receiver, + address target, + bool zero2one + ) internal view returns (bytes memory) { + IUniswapV3Pool pool = IUniswapV3Pool(target); + return abi.encodePacked( + tokenIn, tokenOut, pool.fee(), receiver, target, zero2one + ); + } } diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol new file mode 100644 index 0000000..b73d580 --- /dev/null +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import "@src/executors/UniswapV3Executor.sol"; +import {Test} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "../Constants.sol"; + +contract UniswapV3ExecutorExposed is UniswapV3Executor { + function decodeData(bytes calldata data) + external + pure + returns ( + address inToken, + address outToken, + uint24 fee, + address receiver, + address target, + bool zeroForOne + ) + { + return _decodeData(data); + } +} + +contract UniswapV3ExecutorTest is UniswapV3ExecutorExposed, Test, Constants { + using SafeERC20 for IERC20; + + UniswapV3ExecutorExposed uniswapV3Exposed; + IERC20 WETH = IERC20(WETH_ADDR); + IERC20 DAI = IERC20(DAI_ADDR); + + function setUp() public { + uint256 forkBlock = 17323404; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + uniswapV3Exposed = new UniswapV3ExecutorExposed(); + } + + function testDecodeParams() public view { + uint24 expectedPoolFee = 500; + bytes memory data = abi.encodePacked( + WETH_ADDR, DAI_ADDR, expectedPoolFee, address(2), address(3), false + ); + + ( + address tokenIn, + address tokenOut, + uint24 fee, + address receiver, + address target, + bool zeroForOne + ) = uniswapV3Exposed.decodeData(data); + + assertEq(tokenIn, WETH_ADDR); + assertEq(tokenOut, DAI_ADDR); + assertEq(fee, expectedPoolFee); + assertEq(receiver, address(2)); + assertEq(target, address(3)); + assertEq(zeroForOne, false); + } + + function testDecodeParamsInvalidDataLength() public { + bytes memory invalidParams = + abi.encodePacked(WETH_ADDR, address(2), address(3)); + + vm.expectRevert(UniswapV3Executor__InvalidDataLength.selector); + uniswapV3Exposed.decodeData(invalidParams); + } +} From 92b4ee00d308c4ecb385ad7838c957708c9b5977 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 29 Jan 2025 19:53:22 -0500 Subject: [PATCH 03/21] chore: remove unused factory variable --- foundry/src/executors/UniswapV3Executor.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index 84c0044..64ae64d 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -11,8 +11,6 @@ contract UniswapV3Executor is IExecutor { uint160 private constant MIN_SQRT_RATIO = 4295128739; uint160 private constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - address private constant factoryV3 = - 0x1F98431c8aD98523631AE4a59f267346ea31F984; // slither-disable-next-line locked-ether function swap(uint256 amountIn, bytes calldata data) From 83bbabddd102a476ec641f49e1ac434b060601b3 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 30 Jan 2025 10:33:09 -0500 Subject: [PATCH 04/21] chore: rename amountOwed -> amountIn ...for consistency. Also remove comment. We will keep exact_out to avoid future interface changes. --- foundry/src/TychoRouter.sol | 6 +++--- src/encoding/models.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 2b98667..dbdf089 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -369,7 +369,7 @@ contract TychoRouter is int256 amount0Delta, int256 amount1Delta, bytes calldata data - ) internal view returns (uint256 amountOwed, address tokenIn) { + ) internal view returns (uint256 amountIn, address tokenIn) { tokenIn = address(bytes20(data[0:20])); address tokenOut = address(bytes20(data[20:40])); uint24 poolFee = uint24(bytes3(data[40:43])); @@ -379,9 +379,9 @@ contract TychoRouter is _usv3Factory, tokenIn, tokenOut, poolFee ); - amountOwed = + amountIn = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - return (amountOwed, tokenIn); + return (amountIn, tokenIn); } } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 5e37500..4e2cbbd 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -65,7 +65,6 @@ pub struct Transaction { #[allow(dead_code)] pub struct EncodingContext { pub receiver: Bytes, - // TODO should we keep this? pub exact_out: bool, pub router_address: Bytes, } From ee0aafbc4d45aef967ee411c242a980171d3b518 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 30 Jan 2025 15:40:49 +0000 Subject: [PATCH 05/21] chore(release): 0.22.0 [skip ci] ## [0.22.0](https://github.com/propeller-heads/tycho-execution/compare/0.21.0...0.22.0) (2025-01-30) ### Features * fixed USV3 Verification ([96af542](https://github.com/propeller-heads/tycho-execution/commit/96af5429232a851a7e7144b8a30843a3e6dc980e)) * Implement generic callback ([fafeba9](https://github.com/propeller-heads/tycho-execution/commit/fafeba924848f107e1a00a00cfe94347fde3d919)) * UniswapV3Executor and integration tests ([ca32446](https://github.com/propeller-heads/tycho-execution/commit/ca32446a9ee28118d8857c02abefd24389485b7e)) * USV3 verification ([7822c4f](https://github.com/propeller-heads/tycho-execution/commit/7822c4f9132b6d64a1281f6e54a8515cb0d242d3)) ### Bug Fixes * Remove amountReceived and dataOffset from the callback verification ([63b94b5](https://github.com/propeller-heads/tycho-execution/commit/63b94b55849f2087dab78ec951c459d3811409eb)) * Remove amountReceived, dataOffset from ICallbackVerifier interface ([33ada0c](https://github.com/propeller-heads/tycho-execution/commit/33ada0cf26209cd626c75e26fc6d56943988e0b1)) * Remove exactOut from USV3 encoding ([d8b44f6](https://github.com/propeller-heads/tycho-execution/commit/d8b44f623b8175f4759f8a8cbd42c46e5abad3b4)) --- CHANGELOG.md | 17 +++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d5bbdf..06766dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## [0.22.0](https://github.com/propeller-heads/tycho-execution/compare/0.21.0...0.22.0) (2025-01-30) + + +### Features + +* fixed USV3 Verification ([96af542](https://github.com/propeller-heads/tycho-execution/commit/96af5429232a851a7e7144b8a30843a3e6dc980e)) +* Implement generic callback ([fafeba9](https://github.com/propeller-heads/tycho-execution/commit/fafeba924848f107e1a00a00cfe94347fde3d919)) +* UniswapV3Executor and integration tests ([ca32446](https://github.com/propeller-heads/tycho-execution/commit/ca32446a9ee28118d8857c02abefd24389485b7e)) +* USV3 verification ([7822c4f](https://github.com/propeller-heads/tycho-execution/commit/7822c4f9132b6d64a1281f6e54a8515cb0d242d3)) + + +### Bug Fixes + +* Remove amountReceived and dataOffset from the callback verification ([63b94b5](https://github.com/propeller-heads/tycho-execution/commit/63b94b55849f2087dab78ec951c459d3811409eb)) +* Remove amountReceived, dataOffset from ICallbackVerifier interface ([33ada0c](https://github.com/propeller-heads/tycho-execution/commit/33ada0cf26209cd626c75e26fc6d56943988e0b1)) +* Remove exactOut from USV3 encoding ([d8b44f6](https://github.com/propeller-heads/tycho-execution/commit/d8b44f623b8175f4759f8a8cbd42c46e5abad3b4)) + ## [0.21.0](https://github.com/propeller-heads/tycho-execution/compare/0.20.0...0.21.0) (2025-01-28) diff --git a/Cargo.lock b/Cargo.lock index 82f4bcb..2315882 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4163,7 +4163,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.21.0" +version = "0.22.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index a1172fe..3632570 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.21.0" +version = "0.22.0" edition = "2021" [dependencies] From 5c396512cf695dab3b0d0fec16f71b916661d54d Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 21:20:22 +0530 Subject: [PATCH 06/21] feat: update ExecutorEncoder interface and relevant types --- src/encoding/evm/router_encoder.rs | 11 ++++++++--- src/encoding/evm/strategy_encoder/encoder.rs | 19 +++++++++++-------- src/encoding/evm/strategy_encoder/selector.rs | 4 ++-- src/encoding/models.rs | 3 +++ src/encoding/strategy_encoder.rs | 4 +++- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/encoding/evm/router_encoder.rs b/src/encoding/evm/router_encoder.rs index 53ab437..a564e3e 100644 --- a/src/encoding/evm/router_encoder.rs +++ b/src/encoding/evm/router_encoder.rs @@ -30,7 +30,7 @@ impl RouterEncoder for EVMRo &self, solutions: Vec, ) -> Result, EncodingError> { - let _approvals_calldata = self.handle_approvals(&solutions)?; // TODO: where should we append this? + let _approvals_calldata = self.handle_approvals(&solutions)?; let mut transactions: Vec = Vec::new(); for solution in solutions.iter() { let exact_out = solution.exact_out; @@ -39,7 +39,8 @@ impl RouterEncoder for EVMRo let strategy = self .strategy_selector .select_strategy(solution); - let method_calldata = strategy.encode_strategy((*solution).clone())?; + let (method_calldata, target_address) = + strategy.encode_strategy((*solution).clone())?; let contract_interaction = if straight_to_pool { method_calldata @@ -52,7 +53,11 @@ impl RouterEncoder for EVMRo } else { BigUint::ZERO }; - transactions.push(Transaction { value, data: contract_interaction }); + transactions.push(Transaction { + value, + data: contract_interaction, + to: target_address, + }); } Ok(transactions) } diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 7068608..35c1b19 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use alloy_primitives::Address; use alloy_sol_types::SolValue; @@ -27,7 +29,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder { pub struct SplitSwapStrategyEncoder {} impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} impl StrategyEncoder for SplitSwapStrategyEncoder { - fn encode_strategy(&self, _solution: Solution) -> Result, EncodingError> { + fn encode_strategy(&self, _solution: Solution) -> Result<(Vec, Address), EncodingError> { todo!() } fn selector(&self, _exact_out: bool) -> &str { @@ -37,10 +39,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { /// This strategy encoder is used for solutions that are sent directly to the pool. /// Only 1 solution with 1 swap is supported. -pub struct StraightToPoolStrategyEncoder {} -impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {} -impl StrategyEncoder for StraightToPoolStrategyEncoder { - fn encode_strategy(&self, solution: Solution) -> Result, EncodingError> { +pub struct ExecutorEncoder {} +impl EVMStrategyEncoder for ExecutorEncoder {} +impl StrategyEncoder for ExecutorEncoder { + fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Address), EncodingError> { if solution.router_address.is_none() { return Err(EncodingError::InvalidInput( "Router address is required for straight to pool solutions".to_string(), @@ -68,10 +70,11 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder { router_address, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; - // TODO: here we need to pass also the address of the executor to be used - Ok(protocol_data) + let executor_address = Address::from_str(swap_encoder.executor_address()) + .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; + Ok((protocol_data, executor_address)) } fn selector(&self, _exact_out: bool) -> &str { - unimplemented!(); + "swap(uint256, bytes)" } } diff --git a/src/encoding/evm/strategy_encoder/selector.rs b/src/encoding/evm/strategy_encoder/selector.rs index 37e397c..5ede40a 100644 --- a/src/encoding/evm/strategy_encoder/selector.rs +++ b/src/encoding/evm/strategy_encoder/selector.rs @@ -1,5 +1,5 @@ use crate::encoding::{ - evm::strategy_encoder::encoder::{SplitSwapStrategyEncoder, StraightToPoolStrategyEncoder}, + evm::strategy_encoder::encoder::{ExecutorEncoder, SplitSwapStrategyEncoder}, models::Solution, strategy_encoder::{StrategyEncoder, StrategySelector}, }; @@ -9,7 +9,7 @@ pub struct EVMStrategySelector; impl StrategySelector for EVMStrategySelector { fn select_strategy(&self, solution: &Solution) -> Box { if solution.straight_to_pool { - Box::new(StraightToPoolStrategyEncoder {}) + Box::new(ExecutorEncoder {}) } else { Box::new(SplitSwapStrategyEncoder {}) } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 4e2cbbd..431cdc5 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -1,3 +1,4 @@ +use alloy_primitives::Address; use num_bigint::BigUint; use tycho_core::{dto::ProtocolComponent, Bytes}; @@ -60,6 +61,8 @@ pub struct Transaction { pub data: Vec, // ETH value to be sent with the transaction. pub value: BigUint, + // Address of the contract to call with the calldata + pub to: Address, } #[allow(dead_code)] diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 2b4e348..fb3aa41 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,8 +1,10 @@ +use alloy_primitives::Address; + use crate::encoding::{errors::EncodingError, models::Solution}; #[allow(dead_code)] pub trait StrategyEncoder { - fn encode_strategy(&self, to_encode: Solution) -> Result, EncodingError>; + fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec, Address), EncodingError>; fn selector(&self, exact_out: bool) -> &str; } From ad70a0d5a87f2a89d78de7f4ae783f6c80097407 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 22:26:24 +0530 Subject: [PATCH 07/21] feat: add executor encoder test --- Cargo.lock | 4 +- src/encoding/evm/strategy_encoder/encoder.rs | 55 ++++++++++++++++++++ src/encoding/models.rs | 2 +- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2315882..378aa43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2941,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.96", @@ -3011,7 +3011,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 35c1b19..fb71261 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -78,3 +78,58 @@ impl StrategyEncoder for ExecutorEncoder { "swap(uint256, bytes)" } } + +#[cfg(test)] +mod tests { + use num_bigint::BigUint; + use tycho_core::{dto::ProtocolComponent, Bytes}; + + use super::*; + use crate::encoding::models::Swap; + + #[test] + fn test_executor_encoder() { + let encoder = ExecutorEncoder {}; + + let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_out = Bytes::from("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"); + + let swap = Swap { + component: ProtocolComponent { + id: "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" + .to_string(), + protocol_system: "vm:balancer_v2".to_string(), + ..Default::default() + }, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0.5, + }; + + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from(1000000000000000000u64), + expected_amount: BigUint::from(1000000000000000000u64), + checked_token: token_out, + check_amount: None, + sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), + receiver: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), + swaps: vec![swap], + straight_to_pool: true, + router_address: Some( + Bytes::from_str("0x0000000000000000000000000000000000000002").unwrap(), + ), + slippage: None, + native_action: None, + }; + + let (_, executor_address) = encoder + .encode_strategy(solution) + .unwrap(); + assert_eq!( + executor_address, + Address::from_str("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4").unwrap() + ); + } +} diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 431cdc5..7a93105 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -12,7 +12,7 @@ pub struct Solution { /// Amount of the given token. pub given_amount: BigUint, /// The token being bought (exact in) or sold (exact out). - checked_token: Bytes, + pub checked_token: Bytes, /// Expected amount of the bought token (exact in) or sold token (exact out). pub expected_amount: BigUint, /// Minimum amount to be checked for the solution to be valid. From d2289c3765ee320f3116f60aa5a9f367be398e3c Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 22:41:11 +0530 Subject: [PATCH 08/21] chore: update test --- src/encoding/evm/strategy_encoder/encoder.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index fb71261..1bb8c6f 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -92,18 +92,17 @@ mod tests { let encoder = ExecutorEncoder {}; let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); - let token_out = Bytes::from("0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"); + let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); let swap = Swap { component: ProtocolComponent { - id: "0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014" - .to_string(), - protocol_system: "vm:balancer_v2".to_string(), + id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), + protocol_system: "uniswap_v2".to_string(), ..Default::default() }, token_in: token_in.clone(), token_out: token_out.clone(), - split: 0.5, + split: 0f64, }; let solution = Solution { @@ -114,12 +113,10 @@ mod tests { checked_token: token_out, check_amount: None, sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), - receiver: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), + receiver: Bytes::from_str("0x0000000000000000000000000000000000000001").unwrap(), swaps: vec![swap], straight_to_pool: true, - router_address: Some( - Bytes::from_str("0x0000000000000000000000000000000000000002").unwrap(), - ), + router_address: Some(Bytes::zero(20)), slippage: None, native_action: None, }; @@ -129,7 +126,7 @@ mod tests { .unwrap(); assert_eq!( executor_address, - Address::from_str("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4").unwrap() + Address::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() ); } } From 0007c4924ccb7a3f50f127ce25c3f1c7c27c8caf Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 22:50:10 +0530 Subject: [PATCH 09/21] chore: update executor encoder tests --- src/encoding/evm/strategy_encoder/encoder.rs | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 1bb8c6f..644bd67 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -81,6 +81,7 @@ impl StrategyEncoder for ExecutorEncoder { #[cfg(test)] mod tests { + use alloy::hex::encode; use num_bigint::BigUint; use tycho_core::{dto::ProtocolComponent, Bytes}; @@ -121,12 +122,34 @@ mod tests { native_action: None, }; - let (_, executor_address) = encoder + let (protocol_data, executor_address) = encoder .encode_strategy(solution) .unwrap(); + let hex_protocol_data = encode(&protocol_data); assert_eq!( executor_address, Address::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() ); + assert_eq!( + hex_protocol_data, + String::from(concat!( + // in token + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // component id + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + // receiver + "0000000000000000000000000000000000000001", + // zero for one + "00", + // exact out + "00", + )) + ); + } + + #[test] + fn test_selector() { + let encoder = ExecutorEncoder {}; + assert_eq!(encoder.selector(false), "swap(uint256, bytes)"); } } From c482e21a5f7b254a19cca53c5a86b97830a90932 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 23:10:23 +0530 Subject: [PATCH 10/21] feat: add univ2 executor test with hex --- foundry/src/executors/UniswapV2Executor.sol | 2 +- foundry/test/executors/UniswapV2Executor.t.sol | 16 +++++++++++++++- src/encoding/evm/strategy_encoder/encoder.rs | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 9f447f1..16bef82 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -43,7 +43,7 @@ contract UniswapV2Executor is IExecutor { bool zeroForOne ) { - if (data.length != 61) { + if (data.length < 61) { revert UniswapV2Executor__InvalidDataLength(); } inToken = IERC20(address(bytes20(data[0:20]))); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index cdc0c89..300293c 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.28; import "@src/executors/UniswapV2Executor.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; - +import {console} from "forge-std/console.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { function decodeParams(bytes calldata data) external @@ -93,4 +93,18 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { uint256 finalBalance = DAI.balanceOf(BOB); assertGe(finalBalance, amountOut); } + + function testSwapExecutorEncoderData() public { + bytes memory protocolData = + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; + console.log(protocolData.length); + + (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = + uniswapV2Exposed.decodeParams(protocolData); + + assertEq(address(tokenIn), WETH_ADDR); + assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); + assertEq(receiver, 0x0000000000000000000000000000000000000001); + assertEq(zeroForOne, false); + } } diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 644bd67..87b8122 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -126,6 +126,7 @@ mod tests { .encode_strategy(solution) .unwrap(); let hex_protocol_data = encode(&protocol_data); + println!("{}", hex_protocol_data); assert_eq!( executor_address, Address::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() From 0196767eff1d18481e3154defd92514bd45d74b9 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 23:20:55 +0530 Subject: [PATCH 11/21] feat: add swap test with hex for univ2 executor --- foundry/test/executors/UniswapV2Executor.t.sol | 12 ++++++++++++ src/encoding/evm/strategy_encoder/encoder.rs | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 300293c..070bc21 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -107,4 +107,16 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); } + + function testSwapExecutorSwap() public { + bytes memory protocolData = + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } } diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 87b8122..b12a50e 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -97,7 +97,7 @@ mod tests { let swap = Swap { component: ProtocolComponent { - id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), protocol_system: "uniswap_v2".to_string(), ..Default::default() }, @@ -114,7 +114,7 @@ mod tests { checked_token: token_out, check_amount: None, sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), - receiver: Bytes::from_str("0x0000000000000000000000000000000000000001").unwrap(), + receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap], straight_to_pool: true, router_address: Some(Bytes::zero(20)), @@ -137,9 +137,9 @@ mod tests { // in token "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // component id - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // receiver - "0000000000000000000000000000000000000001", + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", // exact out From cfce36486a812f784d6865506dd501a116592596 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 23:21:57 +0530 Subject: [PATCH 12/21] chore: add comment --- foundry/test/executors/UniswapV2Executor.t.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 070bc21..f157ee1 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -95,6 +95,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorEncoderData() public { + // Generated by the strategy encoder bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; console.log(protocolData.length); @@ -109,6 +110,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorSwap() public { + // Generated by the strategy encoder bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; uint256 amountIn = 10 ** 18; From a50c31203b1e44a796469b03eee7c771d661396f Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 29 Jan 2025 23:26:36 +0530 Subject: [PATCH 13/21] chore: foundry fmt --- foundry/test/executors/UniswapV2Executor.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index f157ee1..99a6c80 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.28; import "@src/executors/UniswapV2Executor.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; -import {console} from "forge-std/console.sol"; + contract UniswapV2ExecutorExposed is UniswapV2Executor { function decodeParams(bytes calldata data) external @@ -98,7 +98,6 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { // Generated by the strategy encoder bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; - console.log(protocolData.length); (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = uniswapV2Exposed.decodeParams(protocolData); From 1b8bf56c754254dc74233e28f3ae3a3992bbf0d3 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 18:07:07 +0530 Subject: [PATCH 14/21] feat: resolve pr comments --- .../test/executors/UniswapV2Executor.t.sol | 4 ++-- src/encoding/evm/strategy_encoder/encoder.rs | 21 ++++++++++--------- src/encoding/evm/strategy_encoder/selector.rs | 4 ++-- src/encoding/models.rs | 3 +-- src/encoding/strategy_encoder.rs | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 99a6c80..4f82d24 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -95,7 +95,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorEncoderData() public { - // Generated by the strategy encoder + // Generated by the ExecutorStrategyEncoder - test_executor_encoder bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; @@ -109,7 +109,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorSwap() public { - // Generated by the strategy encoder + // Generated by the ExecutorStrategyEncoder - test_executor_encoder bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; uint256 amountIn = 10 ** 18; diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index b12a50e..12cac05 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use alloy_primitives::Address; use alloy_sol_types::SolValue; +use tycho_core::Bytes; use crate::encoding::{ errors::EncodingError, @@ -29,7 +30,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder { pub struct SplitSwapStrategyEncoder {} impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} impl StrategyEncoder for SplitSwapStrategyEncoder { - fn encode_strategy(&self, _solution: Solution) -> Result<(Vec, Address), EncodingError> { + fn encode_strategy(&self, _solution: Solution) -> Result<(Vec, Bytes), EncodingError> { todo!() } fn selector(&self, _exact_out: bool) -> &str { @@ -39,10 +40,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { /// This strategy encoder is used for solutions that are sent directly to the pool. /// Only 1 solution with 1 swap is supported. -pub struct ExecutorEncoder {} -impl EVMStrategyEncoder for ExecutorEncoder {} -impl StrategyEncoder for ExecutorEncoder { - fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Address), EncodingError> { +pub struct ExecutorStrategyEncoder {} +impl EVMStrategyEncoder for ExecutorStrategyEncoder {} +impl StrategyEncoder for ExecutorStrategyEncoder { + fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { if solution.router_address.is_none() { return Err(EncodingError::InvalidInput( "Router address is required for straight to pool solutions".to_string(), @@ -72,7 +73,7 @@ impl StrategyEncoder for ExecutorEncoder { let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; let executor_address = Address::from_str(swap_encoder.executor_address()) .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; - Ok((protocol_data, executor_address)) + Ok((protocol_data, Bytes::from(executor_address.as_slice()))) } fn selector(&self, _exact_out: bool) -> &str { "swap(uint256, bytes)" @@ -90,7 +91,7 @@ mod tests { #[test] fn test_executor_encoder() { - let encoder = ExecutorEncoder {}; + let encoder = ExecutorStrategyEncoder {}; let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); @@ -114,6 +115,7 @@ mod tests { checked_token: token_out, check_amount: None, sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), + // The receiver was generated with `makeAddr("bob") using forge` receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap], straight_to_pool: true, @@ -126,10 +128,9 @@ mod tests { .encode_strategy(solution) .unwrap(); let hex_protocol_data = encode(&protocol_data); - println!("{}", hex_protocol_data); assert_eq!( executor_address, - Address::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() + Bytes::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() ); assert_eq!( hex_protocol_data, @@ -150,7 +151,7 @@ mod tests { #[test] fn test_selector() { - let encoder = ExecutorEncoder {}; + let encoder = ExecutorStrategyEncoder {}; assert_eq!(encoder.selector(false), "swap(uint256, bytes)"); } } diff --git a/src/encoding/evm/strategy_encoder/selector.rs b/src/encoding/evm/strategy_encoder/selector.rs index 5ede40a..74cbabd 100644 --- a/src/encoding/evm/strategy_encoder/selector.rs +++ b/src/encoding/evm/strategy_encoder/selector.rs @@ -1,5 +1,5 @@ use crate::encoding::{ - evm::strategy_encoder::encoder::{ExecutorEncoder, SplitSwapStrategyEncoder}, + evm::strategy_encoder::encoder::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder}, models::Solution, strategy_encoder::{StrategyEncoder, StrategySelector}, }; @@ -9,7 +9,7 @@ pub struct EVMStrategySelector; impl StrategySelector for EVMStrategySelector { fn select_strategy(&self, solution: &Solution) -> Box { if solution.straight_to_pool { - Box::new(ExecutorEncoder {}) + Box::new(ExecutorStrategyEncoder {}) } else { Box::new(SplitSwapStrategyEncoder {}) } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 7a93105..a31ecaa 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -1,4 +1,3 @@ -use alloy_primitives::Address; use num_bigint::BigUint; use tycho_core::{dto::ProtocolComponent, Bytes}; @@ -62,7 +61,7 @@ pub struct Transaction { // ETH value to be sent with the transaction. pub value: BigUint, // Address of the contract to call with the calldata - pub to: Address, + pub to: Bytes, } #[allow(dead_code)] diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index fb3aa41..fd2e757 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,10 +1,10 @@ -use alloy_primitives::Address; +use tycho_core::Bytes; use crate::encoding::{errors::EncodingError, models::Solution}; #[allow(dead_code)] pub trait StrategyEncoder { - fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec, Address), EncodingError>; + fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec, Bytes), EncodingError>; fn selector(&self, exact_out: bool) -> &str; } From 4ef9b0b82fe8a531b11186d492856a7717fbc4cf Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 18:13:47 +0530 Subject: [PATCH 15/21] chore: use Bytes --- src/encoding/evm/strategy_encoder/encoder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 12cac05..f7fe0f4 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -71,9 +71,9 @@ impl StrategyEncoder for ExecutorStrategyEncoder { router_address, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; - let executor_address = Address::from_str(swap_encoder.executor_address()) + let executor_address = Bytes::from_str(swap_encoder.executor_address()) .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; - Ok((protocol_data, Bytes::from(executor_address.as_slice()))) + Ok((protocol_data, executor_address)) } fn selector(&self, _exact_out: bool) -> &str { "swap(uint256, bytes)" From 746733056cb1ed08a28454098c7a4353e3291320 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 19:14:01 +0530 Subject: [PATCH 16/21] chore: encoder test naming --- foundry/test/executors/UniswapV2Executor.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 4f82d24..ed4631f 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -95,7 +95,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorEncoderData() public { - // Generated by the ExecutorStrategyEncoder - test_executor_encoder + // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; @@ -109,7 +109,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { } function testSwapExecutorSwap() public { - // Generated by the ExecutorStrategyEncoder - test_executor_encoder + // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; uint256 amountIn = 10 ** 18; From 95d45975adf9f4b35c3746d4281cb08cdbe0f04d Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 19:14:22 +0530 Subject: [PATCH 17/21] chore: encoder test naming --- src/encoding/evm/strategy_encoder/encoder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index f7fe0f4..e9c50f9 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -90,7 +90,7 @@ mod tests { use crate::encoding::models::Swap; #[test] - fn test_executor_encoder() { + fn test_executor_strategy_encode() { let encoder = ExecutorStrategyEncoder {}; let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); From 8cb95f0950e3b57ae0c6ecc3f4c0950005ae75e7 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 20:48:35 +0530 Subject: [PATCH 18/21] feat: remove exact_out from USV2 --- foundry/src/executors/UniswapV2Executor.sol | 3 ++- foundry/test/executors/UniswapV2Executor.t.sol | 8 +++++--- src/encoding/evm/strategy_encoder/encoder.rs | 2 -- src/encoding/evm/swap_encoder/encoders.rs | 3 --- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index 16bef82..e62a99b 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.28; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; +import "forge-std/console.sol"; error UniswapV2Executor__InvalidDataLength(); @@ -43,7 +44,7 @@ contract UniswapV2Executor is IExecutor { bool zeroForOne ) { - if (data.length < 61) { + if (data.length != 61) { revert UniswapV2Executor__InvalidDataLength(); } inToken = IERC20(address(bytes20(data[0:20]))); diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index ed4631f..9372a5d 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -80,7 +80,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { assertGe(amountOut, 0); } - function testSwap() public { + function testSwapUniswapV2() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; bool zeroForOne = false; @@ -97,7 +97,8 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { function testSwapExecutorEncoderData() public { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; + console.log("protocolData1", protocolData.length); (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = uniswapV2Exposed.decodeParams(protocolData); @@ -111,7 +112,8 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { function testSwapExecutorSwap() public { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00"; + console.log("protocolData2", protocolData.length); uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index e9c50f9..a04cddb 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -143,8 +143,6 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", - // exact out - "00", )) ); } diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index f4fbf24..841089d 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -46,7 +46,6 @@ impl SwapEncoder for UniswapV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, zero_to_one, - encoding_context.exact_out, ); Ok(args.abi_encode_packed()) @@ -211,8 +210,6 @@ mod tests { "0000000000000000000000000000000000000001", // zero for one "00", - // exact out - "00", )) ); } From 9c35da07608afc864934409140200c847110e1a7 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 21:06:30 +0530 Subject: [PATCH 19/21] chore: rename straight_to_pool to direct_execution --- src/encoding/evm/router_encoder.rs | 2 +- src/encoding/evm/strategy_encoder/encoder.rs | 2 +- src/encoding/evm/strategy_encoder/selector.rs | 2 +- src/encoding/models.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/encoding/evm/router_encoder.rs b/src/encoding/evm/router_encoder.rs index a564e3e..14075c1 100644 --- a/src/encoding/evm/router_encoder.rs +++ b/src/encoding/evm/router_encoder.rs @@ -34,7 +34,7 @@ impl RouterEncoder for EVMRo let mut transactions: Vec = Vec::new(); for solution in solutions.iter() { let exact_out = solution.exact_out; - let straight_to_pool = solution.straight_to_pool; + let straight_to_pool = solution.direct_execution; let strategy = self .strategy_selector diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index a04cddb..09525a2 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -118,7 +118,7 @@ mod tests { // The receiver was generated with `makeAddr("bob") using forge` receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap], - straight_to_pool: true, + direct_execution: true, router_address: Some(Bytes::zero(20)), slippage: None, native_action: None, diff --git a/src/encoding/evm/strategy_encoder/selector.rs b/src/encoding/evm/strategy_encoder/selector.rs index 74cbabd..3ac619d 100644 --- a/src/encoding/evm/strategy_encoder/selector.rs +++ b/src/encoding/evm/strategy_encoder/selector.rs @@ -8,7 +8,7 @@ pub struct EVMStrategySelector; impl StrategySelector for EVMStrategySelector { fn select_strategy(&self, solution: &Solution) -> Box { - if solution.straight_to_pool { + if solution.direct_execution { Box::new(ExecutorStrategyEncoder {}) } else { Box::new(SplitSwapStrategyEncoder {}) diff --git a/src/encoding/models.rs b/src/encoding/models.rs index a31ecaa..a0daeb0 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -23,10 +23,10 @@ pub struct Solution { pub receiver: Bytes, /// List of swaps to fulfill the solution. pub swaps: Vec, - /// If set to true, the solution will be encoded to be sent directly to the SwapExecutor and + /// If set to true, the solution will be encoded to be sent directly to the Executor and /// skip the router. The user is responsible for managing necessary approvals and token /// transfers. - pub straight_to_pool: bool, + pub direct_execution: bool, // if not set, then the Propeller Router will be used pub router_address: Option, // if set, it will be applied to check_amount From da7b02407a104ae485e7f9f47c269e978bcde2dd Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 21:11:24 +0530 Subject: [PATCH 20/21] chore: remove console --- foundry/src/executors/UniswapV2Executor.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index e62a99b..9f447f1 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.28; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; -import "forge-std/console.sol"; error UniswapV2Executor__InvalidDataLength(); From 6c8226340d068fa498205f07e426aee7b3a7e3ce Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 30 Jan 2025 21:13:15 +0530 Subject: [PATCH 21/21] chore: remove console --- foundry/test/executors/UniswapV2Executor.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 9372a5d..9c7066f 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -98,7 +98,6 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; - console.log("protocolData1", protocolData.length); (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = uniswapV2Exposed.decodeParams(protocolData); @@ -113,7 +112,6 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode bytes memory protocolData = hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00"; - console.log("protocolData2", protocolData.length); uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);