From 93db953c620f4d52e8852ff8148f2dfdbc580029 Mon Sep 17 00:00:00 2001 From: adrian Date: Fri, 15 Aug 2025 10:28:12 +0200 Subject: [PATCH 01/14] feat: implement `SwapEncoder` for `Hashflow` --- Cargo.toml | 17 +- config/executor_addresses.json | 3 +- config/protocol_specific_addresses.json | 5 +- config/test_executor_addresses.json | 3 +- foundry/src/executors/HashflowExecutor.sol | 22 +- foundry/test/Constants.sol | 3 + foundry/test/TychoRouterSequentialSwap.t.sol | 33 ++ foundry/test/TychoRouterSingleSwap.t.sol | 33 ++ foundry/test/TychoRouterTestSetup.sol | 32 +- foundry/test/assets/calldata.txt | 2 + foundry/test/protocols/Hashflow.t.sol | 10 +- src/encoding/evm/approvals/permit2.rs | 4 - .../approvals/protocol_approvals_manager.rs | 4 - src/encoding/evm/swap_encoder/builder.rs | 9 +- .../evm/swap_encoder/swap_encoders.rs | 296 +++++++++++++++++- src/encoding/evm/testing_utils.rs | 6 +- src/encoding/evm/utils.rs | 3 + tests/common/mod.rs | 4 + .../optimized_transfers_integration_tests.rs | 234 +++++++++++++- 19 files changed, 667 insertions(+), 56 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4783f4d..986110d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ license = "MIT" categories = ["finance", "cryptography::cryptocurrencies"] readme = "README.md" exclude = [ - "foundry/*", - "foundry", - "tests/*", - "tests/common", - ".github/*", - ".gitmodules", + "foundry/*", + "foundry", + "tests/*", + "tests/common", + ".github/*", + ".gitmodules", ] [[bin]] @@ -37,7 +37,8 @@ tokio = { version = "1.38.0", features = ["full"] } chrono = "0.4.39" clap = { version = "4.5.3", features = ["derive"] } once_cell = "1.20.2" -tycho-common = ">0.81.5" +tycho-common = ">=0.81.6" + alloy = { version = "1.0.6", features = ["providers", "rpc-types-eth", "eip712", "signer-local", "node-bindings"], optional = true } async-trait = { version = "0.1.88", optional = true } @@ -52,4 +53,4 @@ fork-tests = [] test-utils = ["async-trait"] [profile.bench] -debug = true \ No newline at end of file +debug = true diff --git a/config/executor_addresses.json b/config/executor_addresses.json index f1459d6..ebfa862 100644 --- a/config/executor_addresses.json +++ b/config/executor_addresses.json @@ -11,7 +11,8 @@ "vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165", "vm:maverick_v2": "0xF35e3F5F205769B41508A18787b62A21bC80200B", "vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec", - "rfq:bebop": "0xFE42BFb115eD9671011cA52BDD23A52A2e077a7c" + "rfq:bebop": "0xFE42BFb115eD9671011cA52BDD23A52A2e077a7c", + "rfq:hashflow": "0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f" }, "base": { "uniswap_v2": "0xF744EBfaA580cF3fFc25aD046E92BD8B770a0700", diff --git a/config/protocol_specific_addresses.json b/config/protocol_specific_addresses.json index c2f2fd2..f4cbe21 100644 --- a/config/protocol_specific_addresses.json +++ b/config/protocol_specific_addresses.json @@ -9,6 +9,9 @@ "rfq:bebop": { "bebop_settlement_address": "0xbbbbbBB520d69a9775E85b458C58c648259FAD5F", "native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" + }, + "rfq:hashflow": { + "hashflow_router_address": "0x55084eE0fEf03f14a305cd24286359A35D735151" } }, "base": { @@ -18,4 +21,4 @@ } }, "unichain": {} -} \ No newline at end of file +} diff --git a/config/test_executor_addresses.json b/config/test_executor_addresses.json index 9a2f628..f0b5d8e 100644 --- a/config/test_executor_addresses.json +++ b/config/test_executor_addresses.json @@ -11,6 +11,7 @@ "vm:curve": "0x1d1499e622D69689cdf9004d05Ec547d650Ff211", "vm:maverick_v2": "0xA4AD4f68d0b91CFD19687c881e50f3A00242828c", "vm:balancer_v3": "0x03A6a84cD762D9707A21605b548aaaB891562aAb", - "rfq:bebop": "0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF" + "rfq:bebop": "0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF", + "rfq:hashflow": "0x15cF58144EF33af1e14b5208015d11F9143E27b9" } } diff --git a/foundry/src/executors/HashflowExecutor.sol b/foundry/src/executors/HashflowExecutor.sol index 0e3b93b..1ee9847 100644 --- a/foundry/src/executors/HashflowExecutor.sol +++ b/foundry/src/executors/HashflowExecutor.sol @@ -32,12 +32,20 @@ interface IHashflowRouter { contract HashflowExecutor is IExecutor, RestrictTransferFrom { using SafeERC20 for IERC20; - address public constant HASHFLOW_ROUTER = - 0x55084eE0fEf03f14a305cd24286359A35D735151; address public constant NATIVE_TOKEN = 0x0000000000000000000000000000000000000000; - constructor(address _permit2) RestrictTransferFrom(_permit2) {} + /// @notice The Hashflow router address + address public immutable hashflowRouter; + + constructor(address _hashflowRouter, address _permit2) + RestrictTransferFrom(_permit2) + { + if (_hashflowRouter == address(0)) { + revert HashflowExecutor__InvalidHashflowRouter(); + } + hashflowRouter = _hashflowRouter; + } function swap(uint256 givenAmount, bytes calldata data) external @@ -60,7 +68,7 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { if (approvalNeeded && quote.baseToken != NATIVE_TOKEN) { // slither-disable-next-line unused-return IERC20(quote.baseToken).forceApprove( - HASHFLOW_ROUTER, type(uint256).max + hashflowRouter, type(uint256).max ); } @@ -72,7 +80,7 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { address(this), transferType, address(quote.baseToken), givenAmount ); uint256 balanceBefore = _balanceOf(quote.quoteToken); - IHashflowRouter(HASHFLOW_ROUTER).tradeRFQT{value: ethValue}(quote); + IHashflowRouter(hashflowRouter).tradeRFQT{value: ethValue}(quote); uint256 balanceAfter = _balanceOf(quote.quoteToken); calculatedAmount = balanceAfter - balanceBefore; } @@ -90,8 +98,8 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { revert HashflowExecutor__InvalidDataLength(); } - approvalNeeded = data[0] != 0; - transferType = TransferType(uint8(data[1])); + transferType = TransferType(uint8(data[0])); + approvalNeeded = data[1] != 0; quote.pool = address(bytes20(data[2:22])); quote.externalAccount = address(bytes20(data[22:42])); diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 5d3ea09..f078f36 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -128,6 +128,9 @@ contract Constants is Test, BaseConstants { // Bebop Settlement address BEBOP_SETTLEMENT = 0xbbbbbBB520d69a9775E85b458C58c648259FAD5F; + // Hashflow Router + address HASHFLOW_ROUTER = 0x55084eE0fEf03f14a305cd24286359A35D735151; + // Pool Code Init Hashes bytes32 USV2_POOL_CODE_INIT_HASH = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f; diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index f22158e..3614494 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -547,3 +547,36 @@ contract TychoRouterSequentialSwapTestForBebop is TychoRouterTestSetup { assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); } } + +contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { + function getForkBlock() public pure override returns (uint256) { + return 23167288; + } + + function testUSV3HashflowIntegration() public { + // Performs a sequential swap from WETH to WBTC through USDC using USV3 and Hashflow RFQ + // + // WETH ──(USV3)──> USDC ───(Hashflow RFQ)──> WBTC + + // The Hashflow order expects: + // - 4308094737 USDC input -> 3738288 WBTC output + + uint256 amountIn = 1 ether; + uint256 expectedAmountOut = 3738288; + deal(WETH_ADDR, ALICE, amountIn); + uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); + + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max); + bytes memory callData = loadCallDataFromFile("test_uniswap_v3_hashflow"); + (bool success,) = tychoRouterAddr.call(callData); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, expectedAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } +} diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index fdc1410..8e3bb0e 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -426,3 +426,36 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { assertEq(balanceAfter - balanceBefore, 2018817438608734439722); } } + +contract TychoRouterSingleSwapTestForHashflow is TychoRouterTestSetup { + function getForkBlock() public pure override returns (uint256) { + return 23175437; + } + + function testUSV3HashflowIntegration() public { + // Performs a swap from USDC to WBTC using Hashflow RFQ + // + // USDC ───(Hashflow RFQ)──> WBTC + + // The Hashflow order expects: + // - 4308094737 USDC input -> 3714751 WBTC output + + uint256 amountIn = 4308094737; + uint256 expectedAmountOut = 3714751; + deal(USDC_ADDR, ALICE, amountIn); + uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); + + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); + bytes memory callData = loadCallDataFromFile("test_hashflow"); + (bool success,) = tychoRouterAddr.call(callData); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, expectedAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } +} diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index ec1c0b1..006ef7a 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -2,27 +2,28 @@ pragma solidity ^0.8.26; // Executors +import "../src/executors/HashflowExecutor.sol"; +import "./Constants.sol"; +import "./TestUtils.sol"; +import "@src/TychoRouter.sol"; +import { +UniswapV3Executor, +IUniswapV3Pool +} from "../src/executors/UniswapV3Executor.sol"; import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol"; import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol"; import {BebopExecutor} from "../src/executors/BebopExecutor.sol"; import {CurveExecutor} from "../src/executors/CurveExecutor.sol"; -import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol"; -import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol"; -import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol"; -import { - UniswapV3Executor, - IUniswapV3Pool -} from "../src/executors/UniswapV3Executor.sol"; -import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol"; // Test utilities and mocks -import "./Constants.sol"; -import "./TestUtils.sol"; -import {Permit2TestHelper} from "./Permit2TestHelper.sol"; +import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol"; // Core contracts and interfaces -import "@src/TychoRouter.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Permit2TestHelper} from "./Permit2TestHelper.sol"; +import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol"; +import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} @@ -75,6 +76,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { MaverickV2Executor public maverickv2Executor; BalancerV3Executor public balancerV3Executor; BebopExecutor public bebopExecutor; + HashflowExecutor public hashflowExecutor; function getForkBlock() public view virtual returns (uint256) { return 22082754; @@ -135,8 +137,9 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS); balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS); bebopExecutor = new BebopExecutor(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); + hashflowExecutor = new HashflowExecutor(HASHFLOW_ROUTER, PERMIT2_ADDRESS); - address[] memory executors = new address[](10); + address[] memory executors = new address[](11); executors[0] = address(usv2Executor); executors[1] = address(usv3Executor); executors[2] = address(pancakev3Executor); @@ -147,6 +150,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { executors[7] = address(maverickv2Executor); executors[8] = address(balancerV3Executor); executors[9] = address(bebopExecutor); + executors[10] = address(hashflowExecutor); return executors; } diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index ea38f49..e83503f 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -38,3 +38,5 @@ test_single_encoding_strategy_bebop_aggregate:5c4b639c00000000000000000000000000 test_single_encoding_strategy_bebop:5c4b639c000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000a8aea46aa4ec5c0f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002d7d6bbde9174b1cdaa358d2cf4d57d1a9f7178fbffa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48faba6f8e4a5e8ab82f62fe7c39859fa577269be3000c000000000000000000000000000000000000000000000000000000000bebc20001d2068e04cf586f76eece7ba5beb779d7bb1474a14dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000000000000000000000 test_sequential_swap_strategy_encoder_unwrap:51bcc7b600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068c5498700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000689dc38f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041192fc75d79a3d76bcf3c3d193cf769446abc98ff76ce2a1de183e7e46d80073836cd6ceedc30f98085188eab1098ca2f0ef03c25ebaa69cd2758988263e563c41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48004375dff511095cc5a197a54140a24efef3a416bb2b8038a1640196fbe3e38816f3e67cba72d940000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950102000000000000000000000000000000000000000000000000 test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000 +test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000000000077359400000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000007735940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014400692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000d75615deb798bb3e4dfa0139dfa1b3d433cc23b72f0201031903307c517c11b71f8313d19afde0a4f41cb55615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000198bcad59fd125000064000640000001747937188ffffffffffffff00295e467232b36d0000fda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c0000000068a2fd62bb289bc97591f70d8216462df40ed713011b968acd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000 +test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016f15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000000000000000000000000000 diff --git a/foundry/test/protocols/Hashflow.t.sol b/foundry/test/protocols/Hashflow.t.sol index 73de369..2ed8eca 100644 --- a/foundry/test/protocols/Hashflow.t.sol +++ b/foundry/test/protocols/Hashflow.t.sol @@ -14,8 +14,8 @@ contract HashflowUtils is Test { RestrictTransferFrom.TransferType transferType ) internal pure returns (bytes memory) { return abi.encodePacked( - approvalNeeded, // needsApproval (1 byte) uint8(transferType), // transferType (1 byte) + approvalNeeded, // needsApproval (1 byte) quote.pool, // pool (20 bytes) quote.externalAccount, // externalAccount (20 bytes) quote.trader, // trader (20 bytes) @@ -53,7 +53,7 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils { function setUp() public { forkBlock = 23124977; // Using expiry date: 1755001853, ECR20 vm.createSelectFork("mainnet", forkBlock); - executor = new HashflowExecutorExposed(PERMIT2_ADDRESS); + executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS); } function testDecodeParams() public view { @@ -215,7 +215,7 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils { function setUp() public { forkBlock = 23125321; // Using expiry date: 1755006017, Native vm.createSelectFork("mainnet", forkBlock); - executor = new HashflowExecutorExposed(PERMIT2_ADDRESS); + executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS); } function testSwapNoSlippage() public { @@ -264,7 +264,9 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils { } contract HashflowExecutorExposed is HashflowExecutor { - constructor(address _permit2) HashflowExecutor(_permit2) {} + constructor(address _hashflowRouter, address _permit2) + HashflowExecutor(_hashflowRouter, _permit2) + {} function decodeData(bytes calldata data) external diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 83610ce..53306b0 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -31,10 +31,6 @@ pub struct Permit2 { address: Address, client: EVMProvider, runtime_handle: Handle, - // Store the runtime to prevent it from being dropped before use. - // This is required since tycho-execution does not have a pre-existing runtime. - // However, if the library is used in a context where a runtime already exists, it is not - // necessary to store it. #[allow(dead_code)] runtime: Option>, } diff --git a/src/encoding/evm/approvals/protocol_approvals_manager.rs b/src/encoding/evm/approvals/protocol_approvals_manager.rs index 292f192..861ded9 100644 --- a/src/encoding/evm/approvals/protocol_approvals_manager.rs +++ b/src/encoding/evm/approvals/protocol_approvals_manager.rs @@ -23,10 +23,6 @@ use crate::encoding::{ pub struct ProtocolApprovalsManager { client: EVMProvider, runtime_handle: Handle, - // Store the runtime to prevent it from being dropped before use. - // This is required since tycho-execution does not have a pre-existing runtime. - // However, if the library is used in a context where a runtime already exists, it is not - // necessary to store it. #[allow(dead_code)] runtime: Option>, } diff --git a/src/encoding/evm/swap_encoder/builder.rs b/src/encoding/evm/swap_encoder/builder.rs index bd517f7..7797651 100644 --- a/src/encoding/evm/swap_encoder/builder.rs +++ b/src/encoding/evm/swap_encoder/builder.rs @@ -6,8 +6,8 @@ use crate::encoding::{ errors::EncodingError, evm::swap_encoder::swap_encoders::{ BalancerV2SwapEncoder, BalancerV3SwapEncoder, BebopSwapEncoder, CurveSwapEncoder, - EkuboSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, - UniswapV4SwapEncoder, + EkuboSwapEncoder, HashflowSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder, + UniswapV3SwapEncoder, UniswapV4SwapEncoder, }, swap_encoder::SwapEncoder, }; @@ -91,6 +91,11 @@ impl SwapEncoderBuilder { "rfq:bebop" => { Ok(Box::new(BebopSwapEncoder::new(self.executor_address, self.chain, self.config)?)) } + "rfq:hashflow" => Ok(Box::new(HashflowSwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), _ => Err(EncodingError::FatalError(format!( "Unknown protocol system: {}", self.protocol_system diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 5ee08ff..fc292a0 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -664,10 +664,6 @@ pub struct BebopSwapEncoder { native_token_bebop_address: Bytes, native_token_address: Bytes, runtime_handle: Handle, - // Store the runtime to prevent it from being dropped before use. - // This is required since tycho-execution does not have a pre-existing runtime. - // However, if the library is used in a context where a runtime already exists, it is not - // necessary to store it. #[allow(dead_code)] runtime: Option>, } @@ -850,6 +846,148 @@ impl SwapEncoder for BebopSwapEncoder { } } +#[derive(Clone)] +pub struct HashflowSwapEncoder { + executor_address: String, + hashflow_router_address: String, + native_token_address: Bytes, + runtime_handle: Handle, + #[allow(dead_code)] + runtime: Option>, +} + +impl SwapEncoder for HashflowSwapEncoder { + fn new( + executor_address: String, + chain: Chain, + config: Option>, + ) -> Result { + let config = config.ok_or(EncodingError::FatalError( + "Missing hashflow specific addresses in config".to_string(), + ))?; + let hashflow_router_address = config + .get("hashflow_router_address") + .ok_or(EncodingError::FatalError( + "Missing bebop settlement address in config".to_string(), + ))? + .to_string(); + let native_token_address = chain.native_token().address; + let (runtime_handle, runtime) = get_runtime()?; + Ok(Self { + executor_address, + hashflow_router_address, + native_token_address, + runtime_handle, + runtime, + }) + } + + fn encode_swap( + &self, + swap: &Swap, + encoding_context: &EncodingContext, + ) -> Result, EncodingError> { + // Native tokens doesn't need approval, only ERC20 tokens do + let approval_needed: bool; + if let Some(router_address) = &encoding_context.router_address { + let tycho_router_address = bytes_to_address(router_address)?; + let hashflow_router_address = Address::from_str(&self.hashflow_router_address) + .map_err(|_| { + EncodingError::FatalError("Invalid hashflow router address address".to_string()) + })?; + + // Native ETH doesn't need approval, only ERC20 tokens do + if swap.token_in == self.native_token_address { + approval_needed = false; + } else { + approval_needed = ProtocolApprovalsManager::new()?.approval_needed( + bytes_to_address(&swap.token_in)?, + tycho_router_address, + hashflow_router_address, + )?; + } + } else { + approval_needed = true; + } + + // Get quote + let protocol_state = swap + .protocol_state + .as_ref() + .ok_or_else(|| { + EncodingError::FatalError("protocol_state is required for Hashflow".to_string()) + })?; + let amount_in = swap + .estimated_amount_in + .as_ref() + .ok_or(EncodingError::FatalError( + "Estimated amount in is mandatory for a Hashflow swap".to_string(), + ))? + .clone(); + let sender = encoding_context + .router_address + .clone() + .ok_or(EncodingError::FatalError( + "The router address is needed to perform a Hashflow swap".to_string(), + ))?; + let signed_quote = block_in_place(|| { + self.runtime_handle.block_on(async { + protocol_state + .as_indicatively_priced()? + .request_signed_quote(GetAmountOutParams { + amount_in, + token_in: swap.token_in.clone(), + token_out: swap.token_out.clone(), + sender, + receiver: encoding_context.receiver.clone(), + }) + .await + }) + })?; + + // Encode packed data for the executor + // Format: approval_needed | transfer_type | hashflow_calldata[..] + let hashflow_fields = [ + "pool", + "external_account", + "trader", + "effective_trader", + "base_token", + "quote_token", + "base_token_amount", + "quote_token_amount", + "quote_expiry", + "nonce", + "tx_id", + "signature", + ]; + let mut hashflow_calldata = vec![]; + for field in &hashflow_fields { + let value = signed_quote + .quote_attributes + .get(*field) + .ok_or(EncodingError::FatalError(format!( + "Hashflow quote must have a {field} attribute" + )))?; + hashflow_calldata.extend_from_slice(value); + } + let args = ( + (encoding_context.transfer_type as u8).to_be_bytes(), + (approval_needed as u8).to_be_bytes(), + &hashflow_calldata[..], + ); + Ok(args.abi_encode_packed()) + } + + fn executor_address(&self) -> &str { + &self.executor_address + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + #[cfg(test)] mod tests { use std::collections::HashMap; @@ -1985,7 +2123,7 @@ mod tests { }; let bebop_state = MockRFQState { quote_amount_out, - quote_calldata: bebop_calldata.clone(), + quote_data: vec![("calldata".to_string(), bebop_calldata.clone())], quote_partial_fill_offset: partial_fill_offset, }; @@ -2037,4 +2175,152 @@ mod tests { assert_eq!(hex_swap, expected_swap + &bebop_calldata.to_string()[2..]); } } + + mod hashflow { + use alloy::hex::encode; + use num_bigint::BigUint; + + use super::*; + use crate::encoding::{ + evm::testing_utils::MockRFQState, + models::{SwapBuilder, TransferType}, + }; + + fn hashflow_config() -> Option> { + Some(HashMap::from([( + "hashflow_router_address".to_string(), + "0x55084eE0fEf03f14a305cd24286359A35D735151".to_string(), + )])) + } + + #[test] + fn test_encode_hashflow_single_with_user_data() { + // Hashflow requires a swap with protocol data, otherwise will return an error + let hashflow_component = ProtocolComponent { + id: String::from("hashflow-rfq"), + protocol_system: String::from("rfq:hashflow"), + ..Default::default() + }; + + let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC + let token_out = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); // WETH + + let swap = SwapBuilder::new(hashflow_component, token_in.clone(), token_out.clone()) + .estimated_amount_in(BigUint::from_str("3000000000").unwrap()) + .build(); + + let encoding_context = EncodingContext { + receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, + }; + + let encoder = HashflowSwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + Chain::Ethereum, + hashflow_config(), + ) + .unwrap(); + encoder + .encode_swap(&swap, &encoding_context) + .expect_err("Should returned an error if the swap has no protocol state"); + } + + #[test] + fn test_encode_hashflow_single_with_protocol_state() { + // 3000 USDC -> 1 WETH using a mocked RFQ state to get a quote + let partial_fill_offset = 12u64; + let quote_amount_out = BigUint::from_str("1000000000000000000").unwrap(); + + let hashflow_component = ProtocolComponent { + id: String::from("hashflow-rfq"), + protocol_system: String::from("rfq:hashflow"), + ..Default::default() + }; + let hashflow_quote_data = vec![ + ( + "pool".to_string(), + Bytes::from_str("0x031903307c517c11b71f8313d19afde0a4f41cb5").unwrap(), + ), + ( + "trader".to_string(), + Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap(), + ), + ("nonce".to_string(), Bytes::from(1755512134141u64.to_be_bytes().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747937188ffffffffffffff00295e467232b36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0xfda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c").unwrap()), + ("quote_expiry".to_string(), Bytes::from(1755512162u64.to_be_bytes().to_vec())), + ( + "external_account".to_string(), + Bytes::from_str("0xbb289bc97591f70d8216462df40ed713011b968a").unwrap(), + ), + ( + "effective_trader".to_string(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), + ), + ]; + let hashflow_quote_data_values = + hashflow_quote_data + .iter() + .fold(vec![], |mut acc, (_key, value)| { + acc.extend_from_slice(value); + acc + }); + let hashflow_calldata = Bytes::from(hashflow_quote_data_values); + let hashflow_state = MockRFQState { + quote_amount_out, + quote_data: hashflow_quote_data, + quote_partial_fill_offset: partial_fill_offset, + }; + + let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC + let token_out = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); // WETH + + let swap = SwapBuilder::new(hashflow_component, token_in.clone(), token_out.clone()) + .estimated_amount_in(BigUint::from_str("3000000000").unwrap()) + .protocol_state(&hashflow_state) + .build(); + + let encoding_context = EncodingContext { + receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), + exact_out: false, + router_address: Some(Bytes::zero(20)), + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + transfer_type: TransferType::Transfer, + }; + + let encoder = HashflowSwapEncoder::new( + String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), + Chain::Ethereum, + hashflow_config(), + ) + .unwrap(); + + let encoded_swap = encoder + .encode_swap(&swap, &encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + + let expected_swap = String::from(concat!( + "01", // transfer type + "01", // approval needed + )); + assert_eq!(hex_swap, expected_swap + &hashflow_calldata.to_string()[2..]); + } + + #[test] + fn test_encode_hashflow_aggregate_with_protocol_state() { + todo!() + } + } } diff --git a/src/encoding/evm/testing_utils.rs b/src/encoding/evm/testing_utils.rs index f633728..e769ac8 100644 --- a/src/encoding/evm/testing_utils.rs +++ b/src/encoding/evm/testing_utils.rs @@ -17,7 +17,7 @@ use tycho_common::{ #[derive(Debug)] pub struct MockRFQState { pub quote_amount_out: BigUint, - pub quote_calldata: Bytes, + pub quote_data: Vec<(String, Bytes)>, pub quote_partial_fill_offset: u64, } impl ProtocolSim for MockRFQState { @@ -83,7 +83,9 @@ impl IndicativelyPriced for MockRFQState { params: GetAmountOutParams, ) -> Result { let mut quote_attributes: HashMap = HashMap::new(); - quote_attributes.insert("calldata".to_string(), self.quote_calldata.clone()); + for (attr, value) in &self.quote_data { + quote_attributes.insert(attr.clone(), value.clone()); + } quote_attributes.insert( "partial_fill_offset".to_string(), Bytes::from( diff --git a/src/encoding/evm/utils.rs b/src/encoding/evm/utils.rs index dc52b14..ee448e2 100644 --- a/src/encoding/evm/utils.rs +++ b/src/encoding/evm/utils.rs @@ -78,6 +78,9 @@ pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result .to_vec()) } +/// Returns the current Tokio runtime handle, or creates a new one if it doesn't exist. +/// It also returns the runtime to prevent it from being dropped before use. +/// This is required since tycho-execution does not have a pre-existing runtime. pub fn get_runtime() -> Result<(Handle, Option>), EncodingError> { match Handle::try_current() { Ok(h) => Ok((h, None)), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d5032d5..0949b7e 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -21,6 +21,10 @@ pub fn bob_address() -> Bytes { Bytes::from_str("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e").unwrap() } +pub fn alice_address() -> Bytes { + Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap() +} + pub fn eth_chain() -> Chain { Chain::Ethereum } diff --git a/tests/optimized_transfers_integration_tests.rs b/tests/optimized_transfers_integration_tests.rs index e59bb23..0aa52d1 100644 --- a/tests/optimized_transfers_integration_tests.rs +++ b/tests/optimized_transfers_integration_tests.rs @@ -4,12 +4,15 @@ use alloy::hex::encode; use num_bigint::{BigInt, BigUint}; use tycho_common::{models::protocol::ProtocolComponent, Bytes}; use tycho_execution::encoding::{ - evm::{testing_utils::MockRFQState, utils::write_calldata_to_file}, + evm::{ + testing_utils::MockRFQState, + utils::{biguint_to_u256, write_calldata_to_file}, + }, models::{Solution, Swap, SwapBuilder, UserTransferType}, }; use crate::common::{ - bob_address, encoding::encode_tycho_router_call, eth, eth_chain, get_signer, + alice_address, bob_address, encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, usdc, wbtc, weth, }; @@ -650,7 +653,7 @@ fn test_uniswap_v3_bebop() { let bebop_state = MockRFQState { quote_amount_out, - quote_calldata: bebop_calldata.clone(), + quote_data: vec![("calldata".to_string(), bebop_calldata)], quote_partial_fill_offset: partial_fill_offset, }; @@ -698,3 +701,228 @@ fn test_uniswap_v3_bebop() { let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_bebop", hex_calldata.as_str()); } + +#[test] +fn test_hashflow() { + // Note: This test does not assert anything. It is only used to obtain + // integration test data for our router solidity test. + // + // Performs a swap from USDC to WBTC using Hashflow RFQ + // + // USDC ───(Hashflow RFQ)──> WBTC + + let usdc = usdc(); + let wbtc = wbtc(); + + // USDC -> WBTC via Hashflow RFQ using real order data + let quote_amount_out = BigUint::from_str("3714751").unwrap(); + + let hashflow_state = MockRFQState { + quote_amount_out, + quote_data: vec![ + ( + "pool".to_string(), + Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), + ), + ( + "external_account".to_string(), + Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(), + ), + ( + "trader".to_string(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), + ), + ( + // Passing the tycho router address here has no effect + "effective_trader".to_string(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), + ), + ( + "base_token".to_string(), + Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), + ), + ( + "quote_token".to_string(), + Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(), + ), + ( + "base_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()), + ), + ( + "quote_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()), + ), + ("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())), + ("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), + ], + quote_partial_fill_offset: 0, + }; + + let hashflow_component = ProtocolComponent { + id: String::from("hashflow-rfq"), + protocol_system: String::from("rfq:hashflow"), + ..Default::default() + }; + + let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone()) + .estimated_amount_in(BigUint::from_str("4308094737").unwrap()) + .protocol_state(&hashflow_state) + .build(); + + let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom); + + let solution = Solution { + exact_out: false, + given_token: usdc, + given_amount: BigUint::from_str("4308094737").unwrap(), + checked_token: wbtc, + checked_amount: BigUint::from_str("3714751").unwrap(), + sender: alice_address(), + receiver: alice_address(), + swaps: vec![swap_usdc_wbtc], + ..Default::default() + }; + + let encoded_solution = encoder + .encode_solutions(vec![solution.clone()]) + .unwrap()[0] + .clone(); + + let calldata = encode_tycho_router_call( + eth_chain().id(), + encoded_solution, + &solution, + &UserTransferType::TransferFrom, + ð(), + None, + ) + .unwrap() + .data; + + let hex_calldata = encode(&calldata); + write_calldata_to_file("test_hashflow", hex_calldata.as_str()); +} + +#[test] +#[ignore] +fn test_uniswap_v3_hashflow() { + // Note: This test does not assert anything. It is only used to obtain + // integration test data for our router solidity test. + // + // Performs a sequential swap from WETH to WBTC through USDC using USV3 and + // Hashflow RFQ + // + // WETH ───(USV3)──> USDC ───(Hashflow RFQ)──> WBTC + + let weth = weth(); + let usdc = usdc(); + let wbtc = wbtc(); + + // First swap: WETH -> USDC via UniswapV3 + let swap_weth_usdc = SwapBuilder::new( + ProtocolComponent { + id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* WETH-USDC USV3 Pool + * 0.05% */ + protocol_system: "uniswap_v3".to_string(), + static_attributes: { + let mut attrs = HashMap::new(); + attrs + .insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be())); + attrs + }, + ..Default::default() + }, + weth.clone(), + usdc.clone(), + ) + .build(); + + // Second swap: USDC -> WBTC via Hashflow RFQ using real order data + let quote_amount_out = BigUint::from_str("1735332").unwrap(); + + let hashflow_state = MockRFQState { + quote_amount_out, + quote_data: vec![ + ( + "pool".to_string(), + Bytes::from_str("0x031903307c517c11b71f8313d19afde0a4f41cb5").unwrap(), + ), + ( + "trader".to_string(), + Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap(), + ), + ("nonce".to_string(), Bytes::from(1755512134141u64.to_be_bytes().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747937188ffffffffffffff00295e467232b36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0xfda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c").unwrap()), + ("quote_expiry".to_string(), Bytes::from(1755512162u64.to_be_bytes().to_vec())), + ( + "external_account".to_string(), + Bytes::from_str("0xbb289bc97591f70d8216462df40ed713011b968a").unwrap(), + ), + ( + "effective_trader".to_string(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), + ), + ], + quote_partial_fill_offset: 0, + }; + + let hashflow_component = ProtocolComponent { + id: String::from("hashflow-rfq"), + protocol_system: String::from("rfq:hashflow"), + ..Default::default() + }; + + let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone()) + .estimated_amount_in(BigUint::from_str("2000000000").unwrap()) + .protocol_state(&hashflow_state) + .build(); + + let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom); + + let solution = Solution { + exact_out: false, + given_token: weth, + given_amount: BigUint::from_str("2000000000").unwrap(), + checked_token: wbtc, + checked_amount: BigUint::from_str("2000000000").unwrap(), + sender: alice_address(), + receiver: alice_address(), + swaps: vec![swap_weth_usdc, swap_usdc_wbtc], + ..Default::default() + }; + + let encoded_solution = encoder + .encode_solutions(vec![solution.clone()]) + .unwrap()[0] + .clone(); + + let calldata = encode_tycho_router_call( + eth_chain().id(), + encoded_solution, + &solution, + &UserTransferType::TransferFrom, + ð(), + None, + ) + .unwrap() + .data; + + let hex_calldata = encode(&calldata); + write_calldata_to_file("test_uniswap_v3_hashflow", hex_calldata.as_str()); +} From a09d648f3c50f87b392c7c5eb14af07307c5ccea Mon Sep 17 00:00:00 2001 From: adrian Date: Tue, 19 Aug 2025 16:05:51 +0200 Subject: [PATCH 02/14] fix: in HashflowExecutor, _balanceOf must use `trader` address instead of the executor's to get the balance --- foundry/src/executors/HashflowExecutor.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/foundry/src/executors/HashflowExecutor.sol b/foundry/src/executors/HashflowExecutor.sol index 1ee9847..60d4f72 100644 --- a/foundry/src/executors/HashflowExecutor.sol +++ b/foundry/src/executors/HashflowExecutor.sol @@ -79,9 +79,9 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { _transfer( address(this), transferType, address(quote.baseToken), givenAmount ); - uint256 balanceBefore = _balanceOf(quote.quoteToken); + uint256 balanceBefore = _balanceOf(quote.trader, quote.quoteToken); IHashflowRouter(hashflowRouter).tradeRFQT{value: ethValue}(quote); - uint256 balanceAfter = _balanceOf(quote.quoteToken); + uint256 balanceAfter = _balanceOf(quote.trader, quote.quoteToken); calculatedAmount = balanceAfter - balanceBefore; } @@ -116,13 +116,13 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { quote.signature = data[282:347]; } - function _balanceOf(address token) + function _balanceOf(address trader, address token) internal view returns (uint256 balance) { balance = token == NATIVE_TOKEN - ? address(this).balance - : IERC20(token).balanceOf(address(this)); + ? trader.balance + : IERC20(token).balanceOf(trader); } } From 77de0d892a9b1c869f2893bed4e71ebb52b2c181 Mon Sep 17 00:00:00 2001 From: adrian Date: Tue, 19 Aug 2025 16:51:24 +0200 Subject: [PATCH 03/14] test: add sequential swap tests for hashflow --- foundry/test/TychoRouterSequentialSwap.t.sol | 9 +-- foundry/test/TychoRouterTestSetup.sol | 7 ++- foundry/test/assets/calldata.txt | 2 +- .../evm/swap_encoder/swap_encoders.rs | 54 +++++++++++------- .../optimized_transfers_integration_tests.rs | 57 ++++++++++++------- 5 files changed, 80 insertions(+), 49 deletions(-) diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 3614494..3d24a41 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -550,7 +550,7 @@ contract TychoRouterSequentialSwapTestForBebop is TychoRouterTestSetup { contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { function getForkBlock() public pure override returns (uint256) { - return 23167288; + return 23175437; } function testUSV3HashflowIntegration() public { @@ -559,10 +559,10 @@ contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { // WETH ──(USV3)──> USDC ───(Hashflow RFQ)──> WBTC // The Hashflow order expects: - // - 4308094737 USDC input -> 3738288 WBTC output + // - 4308094737 USDC input -> 3724533 WBTC output uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 3738288; + uint256 expectedAmountOut = 3724533; deal(WETH_ADDR, ALICE, amountIn); uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); @@ -576,7 +576,8 @@ contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, expectedAmountOut); + assertGt(balanceAfter - balanceBefore, 0); + assertLe(balanceAfter - balanceBefore, expectedAmountOut); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); } } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 006ef7a..1886275 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -7,8 +7,8 @@ import "./Constants.sol"; import "./TestUtils.sol"; import "@src/TychoRouter.sol"; import { -UniswapV3Executor, -IUniswapV3Pool + UniswapV3Executor, + IUniswapV3Pool } from "../src/executors/UniswapV3Executor.sol"; import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol"; import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol"; @@ -137,7 +137,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { new MaverickV2Executor(MAVERICK_V2_FACTORY, PERMIT2_ADDRESS); balancerV3Executor = new BalancerV3Executor(PERMIT2_ADDRESS); bebopExecutor = new BebopExecutor(BEBOP_SETTLEMENT, PERMIT2_ADDRESS); - hashflowExecutor = new HashflowExecutor(HASHFLOW_ROUTER, PERMIT2_ADDRESS); + hashflowExecutor = + new HashflowExecutor(HASHFLOW_ROUTER, PERMIT2_ADDRESS); address[] memory executors = new address[](11); executors[0] = address(usv2Executor); diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index e83503f..b771a8f 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -38,5 +38,5 @@ test_single_encoding_strategy_bebop_aggregate:5c4b639c00000000000000000000000000 test_single_encoding_strategy_bebop:5c4b639c000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000a8aea46aa4ec5c0f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002d7d6bbde9174b1cdaa358d2cf4d57d1a9f7178fbffa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48faba6f8e4a5e8ab82f62fe7c39859fa577269be3000c000000000000000000000000000000000000000000000000000000000bebc20001d2068e04cf586f76eece7ba5beb779d7bb1474a14dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000000000000000000000 test_sequential_swap_strategy_encoder_unwrap:51bcc7b600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068c5498700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000689dc38f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041192fc75d79a3d76bcf3c3d193cf769446abc98ff76ce2a1de183e7e46d80073836cd6ceedc30f98085188eab1098ca2f0ef03c25ebaa69cd2758988263e563c41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48004375dff511095cc5a197a54140a24efef3a416bb2b8038a1640196fbe3e38816f3e67cba72d940000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950102000000000000000000000000000000000000000000000000 test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000 -test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000000000077359400000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000007735940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014400692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000d75615deb798bb3e4dfa0139dfa1b3d433cc23b72f0201031903307c517c11b71f8313d19afde0a4f41cb55615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000198bcad59fd125000064000640000001747937188ffffffffffffff00295e467232b36d0000fda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c0000000068a2fd62bb289bc97591f70d8216462df40ed713011b968acd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000 +test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001dc00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000016f15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c00000000 test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016f15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000000000000000000000000000 diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index fc292a0..5f3d9c2 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -2243,30 +2243,47 @@ mod tests { let hashflow_quote_data = vec![ ( "pool".to_string(), - Bytes::from_str("0x031903307c517c11b71f8313d19afde0a4f41cb5").unwrap(), + Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), + ), + ( + "external_account".to_string(), + Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(), ), ( "trader".to_string(), - Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap(), - ), - ("nonce".to_string(), Bytes::from(1755512134141u64.to_be_bytes().to_vec())), - ( - "tx_id".to_string(), - Bytes::from_str( - "0x125000064000640000001747937188ffffffffffffff00295e467232b36d0000", - ) - .unwrap(), - ), - ("signature".to_string(), Bytes::from_str("0xfda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c").unwrap()), - ("quote_expiry".to_string(), Bytes::from(1755512162u64.to_be_bytes().to_vec())), - ( - "external_account".to_string(), - Bytes::from_str("0xbb289bc97591f70d8216462df40ed713011b968a").unwrap(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), ( + // Passing the tycho router address here has no effect "effective_trader".to_string(), Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), + ( + "base_token".to_string(), + Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), + ), + ( + "quote_token".to_string(), + Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(), + ), + ( + "base_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(3000_u64)).to_be_bytes::<32>().to_vec()), + ), + ( + "quote_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(1_u64)).to_be_bytes::<32>().to_vec()), + ), + ("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())), + ("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), ]; let hashflow_quote_data_values = hashflow_quote_data @@ -2317,10 +2334,5 @@ mod tests { )); assert_eq!(hex_swap, expected_swap + &hashflow_calldata.to_string()[2..]); } - - #[test] - fn test_encode_hashflow_aggregate_with_protocol_state() { - todo!() - } } } diff --git a/tests/optimized_transfers_integration_tests.rs b/tests/optimized_transfers_integration_tests.rs index 0aa52d1..d203143 100644 --- a/tests/optimized_transfers_integration_tests.rs +++ b/tests/optimized_transfers_integration_tests.rs @@ -847,37 +847,54 @@ fn test_uniswap_v3_hashflow() { .build(); // Second swap: USDC -> WBTC via Hashflow RFQ using real order data - let quote_amount_out = BigUint::from_str("1735332").unwrap(); + let quote_amount_out = BigUint::from_str("3714751").unwrap(); let hashflow_state = MockRFQState { quote_amount_out, quote_data: vec![ ( "pool".to_string(), - Bytes::from_str("0x031903307c517c11b71f8313d19afde0a4f41cb5").unwrap(), + Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), + ), + ( + "external_account".to_string(), + Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(), ), ( "trader".to_string(), - Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap(), - ), - ("nonce".to_string(), Bytes::from(1755512134141u64.to_be_bytes().to_vec())), - ( - "tx_id".to_string(), - Bytes::from_str( - "0x125000064000640000001747937188ffffffffffffff00295e467232b36d0000", - ) - .unwrap(), - ), - ("signature".to_string(), Bytes::from_str("0xfda99100ffd8adfc818a827e1698c1d1fa2f59f7723ff84bfeba0f80e5298b1077f590d8d99aec6f6801c611eb270b5d89fac02a680ab38f03f3c5d16039c6f11c").unwrap()), - ("quote_expiry".to_string(), Bytes::from(1755512162u64.to_be_bytes().to_vec())), - ( - "external_account".to_string(), - Bytes::from_str("0xbb289bc97591f70d8216462df40ed713011b968a").unwrap(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), ( + // Passing the tycho router address here has no effect "effective_trader".to_string(), Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), + ( + "base_token".to_string(), + Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), + ), + ( + "quote_token".to_string(), + Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(), + ), + ( + "base_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()), + ), + ( + "quote_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()), + ), + ("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())), + ("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), ], quote_partial_fill_offset: 0, }; @@ -889,7 +906,7 @@ fn test_uniswap_v3_hashflow() { }; let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone()) - .estimated_amount_in(BigUint::from_str("2000000000").unwrap()) + .estimated_amount_in(BigUint::from_str("4308094737").unwrap()) .protocol_state(&hashflow_state) .build(); @@ -898,9 +915,9 @@ fn test_uniswap_v3_hashflow() { let solution = Solution { exact_out: false, given_token: weth, - given_amount: BigUint::from_str("2000000000").unwrap(), + given_amount: BigUint::from_str("1000000000000000000").unwrap(), checked_token: wbtc, - checked_amount: BigUint::from_str("2000000000").unwrap(), + checked_amount: BigUint::from_str("3714751").unwrap(), sender: alice_address(), receiver: alice_address(), swaps: vec![swap_weth_usdc, swap_usdc_wbtc], From 2b9f9a99f2f87436d386d8a02fbb3a3edce1ffa7 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:05:57 +0200 Subject: [PATCH 04/14] test: remove partial_fill_offset from MockRFQState --- .../evm/swap_encoder/swap_encoders.rs | 21 ++++++++++++----- src/encoding/evm/testing_utils.rs | 18 ++------------- .../optimized_transfers_integration_tests.rs | 23 ++++++++++++------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 5f3d9c2..330baec 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -868,7 +868,7 @@ impl SwapEncoder for HashflowSwapEncoder { let hashflow_router_address = config .get("hashflow_router_address") .ok_or(EncodingError::FatalError( - "Missing bebop settlement address in config".to_string(), + "Missing hashflow router address in config".to_string(), ))? .to_string(); let native_token_address = chain.native_token().address; @@ -2123,8 +2123,17 @@ mod tests { }; let bebop_state = MockRFQState { quote_amount_out, - quote_data: vec![("calldata".to_string(), bebop_calldata.clone())], - quote_partial_fill_offset: partial_fill_offset, + quote_data: HashMap::from([ + ("calldata".to_string(), bebop_calldata.clone()), + ( + "partial_fill_offset".to_string(), + Bytes::from( + partial_fill_offset + .to_be_bytes() + .to_vec(), + ), + ), + ]), }; let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC @@ -2232,7 +2241,6 @@ mod tests { #[test] fn test_encode_hashflow_single_with_protocol_state() { // 3000 USDC -> 1 WETH using a mocked RFQ state to get a quote - let partial_fill_offset = 12u64; let quote_amount_out = BigUint::from_str("1000000000000000000").unwrap(); let hashflow_component = ProtocolComponent { @@ -2295,8 +2303,9 @@ mod tests { let hashflow_calldata = Bytes::from(hashflow_quote_data_values); let hashflow_state = MockRFQState { quote_amount_out, - quote_data: hashflow_quote_data, - quote_partial_fill_offset: partial_fill_offset, + quote_data: hashflow_quote_data + .into_iter() + .collect(), }; let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC diff --git a/src/encoding/evm/testing_utils.rs b/src/encoding/evm/testing_utils.rs index e769ac8..fb864a6 100644 --- a/src/encoding/evm/testing_utils.rs +++ b/src/encoding/evm/testing_utils.rs @@ -17,8 +17,7 @@ use tycho_common::{ #[derive(Debug)] pub struct MockRFQState { pub quote_amount_out: BigUint, - pub quote_data: Vec<(String, Bytes)>, - pub quote_partial_fill_offset: u64, + pub quote_data: HashMap, } impl ProtocolSim for MockRFQState { fn fee(&self) -> f64 { @@ -82,25 +81,12 @@ impl IndicativelyPriced for MockRFQState { &self, params: GetAmountOutParams, ) -> Result { - let mut quote_attributes: HashMap = HashMap::new(); - for (attr, value) in &self.quote_data { - quote_attributes.insert(attr.clone(), value.clone()); - } - quote_attributes.insert( - "partial_fill_offset".to_string(), - Bytes::from( - self.quote_partial_fill_offset - .to_be_bytes() - .to_vec(), - ), - ); - Ok(SignedQuote { base_token: params.token_in, quote_token: params.token_out, amount_in: params.amount_in, amount_out: self.quote_amount_out.clone(), - quote_attributes, + quote_attributes: self.quote_data.clone(), }) } } diff --git a/tests/optimized_transfers_integration_tests.rs b/tests/optimized_transfers_integration_tests.rs index d203143..3e20c76 100644 --- a/tests/optimized_transfers_integration_tests.rs +++ b/tests/optimized_transfers_integration_tests.rs @@ -653,8 +653,17 @@ fn test_uniswap_v3_bebop() { let bebop_state = MockRFQState { quote_amount_out, - quote_data: vec![("calldata".to_string(), bebop_calldata)], - quote_partial_fill_offset: partial_fill_offset, + quote_data: HashMap::from([ + ("calldata".to_string(), bebop_calldata), + ( + "partial_fill_offset".to_string(), + Bytes::from( + partial_fill_offset + .to_be_bytes() + .to_vec(), + ), + ), + ]), }; let bebop_component = ProtocolComponent { @@ -719,7 +728,7 @@ fn test_hashflow() { let hashflow_state = MockRFQState { quote_amount_out, - quote_data: vec![ + quote_data: HashMap::from([ ( "pool".to_string(), Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), @@ -763,8 +772,7 @@ fn test_hashflow() { .unwrap(), ), ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), - ], - quote_partial_fill_offset: 0, + ]), }; let hashflow_component = ProtocolComponent { @@ -851,7 +859,7 @@ fn test_uniswap_v3_hashflow() { let hashflow_state = MockRFQState { quote_amount_out, - quote_data: vec![ + quote_data: HashMap::from([ ( "pool".to_string(), Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), @@ -895,8 +903,7 @@ fn test_uniswap_v3_hashflow() { .unwrap(), ), ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), - ], - quote_partial_fill_offset: 0, + ]), }; let hashflow_component = ProtocolComponent { From c506f2c048a20d1ffd066d2903d2cc469fd167ed Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:07:16 +0200 Subject: [PATCH 05/14] fix: in hashflow's encode_swap, fail early if router address is not present --- .../evm/swap_encoder/swap_encoders.rs | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 330baec..d2d9bac 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -888,27 +888,28 @@ impl SwapEncoder for HashflowSwapEncoder { encoding_context: &EncodingContext, ) -> Result, EncodingError> { // Native tokens doesn't need approval, only ERC20 tokens do - let approval_needed: bool; - if let Some(router_address) = &encoding_context.router_address { - let tycho_router_address = bytes_to_address(router_address)?; - let hashflow_router_address = Address::from_str(&self.hashflow_router_address) - .map_err(|_| { - EncodingError::FatalError("Invalid hashflow router address address".to_string()) - })?; + let sender = encoding_context + .router_address + .clone() + .ok_or(EncodingError::FatalError( + "The router address is needed to perform a Hashflow swap".to_string(), + ))?; + let tycho_router_address = bytes_to_address(&sender)?; + let hashflow_router_address = + Address::from_str(&self.hashflow_router_address).map_err(|_| { + EncodingError::FatalError("Invalid hashflow router address address".to_string()) + })?; - // Native ETH doesn't need approval, only ERC20 tokens do - if swap.token_in == self.native_token_address { - approval_needed = false; - } else { - approval_needed = ProtocolApprovalsManager::new()?.approval_needed( - bytes_to_address(&swap.token_in)?, - tycho_router_address, - hashflow_router_address, - )?; - } + // Native ETH doesn't need approval, only ERC20 tokens do + let approval_needed = if swap.token_in == self.native_token_address { + false } else { - approval_needed = true; - } + ProtocolApprovalsManager::new()?.approval_needed( + bytes_to_address(&swap.token_in)?, + tycho_router_address, + hashflow_router_address, + )? + }; // Get quote let protocol_state = swap From a5b686569860ad8de8057d44a45ff31a7556e1e2 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:07:48 +0200 Subject: [PATCH 06/14] test: revert import reordering --- foundry/test/TychoRouterTestSetup.sol | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 1886275..843eeff 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -2,28 +2,28 @@ pragma solidity ^0.8.26; // Executors -import "../src/executors/HashflowExecutor.sol"; -import "./Constants.sol"; -import "./TestUtils.sol"; -import "@src/TychoRouter.sol"; -import { - UniswapV3Executor, - IUniswapV3Pool -} from "../src/executors/UniswapV3Executor.sol"; import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol"; import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol"; import {BebopExecutor} from "../src/executors/BebopExecutor.sol"; import {CurveExecutor} from "../src/executors/CurveExecutor.sol"; +import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol"; +import {HashflowExecutor} from "../src/executors/HashflowExecutor.sol"; +import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol"; +import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol"; +import { +UniswapV3Executor, +IUniswapV3Pool +} from "../src/executors/UniswapV3Executor.sol"; +import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol"; // Test utilities and mocks -import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol"; -import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; -import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol"; +import "./Constants.sol"; +import "./TestUtils.sol"; +import {Permit2TestHelper} from "./Permit2TestHelper.sol"; // Core contracts and interfaces -import {Permit2TestHelper} from "./Permit2TestHelper.sol"; -import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol"; -import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol"; +import "@src/TychoRouter.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} From 58bb3d7bdb18525e0d904fea26b048370d3946e9 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:10:08 +0200 Subject: [PATCH 07/14] test: move TychoRouterSingleSwapTestForHashflow to hashflow file --- foundry/test/TychoRouterSingleSwap.t.sol | 33 ------------------------ foundry/test/protocols/Hashflow.t.sol | 33 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index 8e3bb0e..fdc1410 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -426,36 +426,3 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { assertEq(balanceAfter - balanceBefore, 2018817438608734439722); } } - -contract TychoRouterSingleSwapTestForHashflow is TychoRouterTestSetup { - function getForkBlock() public pure override returns (uint256) { - return 23175437; - } - - function testUSV3HashflowIntegration() public { - // Performs a swap from USDC to WBTC using Hashflow RFQ - // - // USDC ───(Hashflow RFQ)──> WBTC - - // The Hashflow order expects: - // - 4308094737 USDC input -> 3714751 WBTC output - - uint256 amountIn = 4308094737; - uint256 expectedAmountOut = 3714751; - deal(USDC_ADDR, ALICE, amountIn); - uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); - - vm.startPrank(ALICE); - IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); - bytes memory callData = loadCallDataFromFile("test_hashflow"); - (bool success,) = tychoRouterAddr.call(callData); - - vm.stopPrank(); - - uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); - - assertTrue(success, "Call Failed"); - assertEq(balanceAfter - balanceBefore, expectedAmountOut); - assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); - } -} diff --git a/foundry/test/protocols/Hashflow.t.sol b/foundry/test/protocols/Hashflow.t.sol index 2ed8eca..42d32b9 100644 --- a/foundry/test/protocols/Hashflow.t.sol +++ b/foundry/test/protocols/Hashflow.t.sol @@ -280,3 +280,36 @@ contract HashflowExecutorExposed is HashflowExecutor { return _decodeData(data); } } + +contract TychoRouterSingleSwapTestForHashflow is TychoRouterTestSetup { + function getForkBlock() public pure override returns (uint256) { + return 23175437; + } + + function testHashflowIntegration() public { + // Performs a swap from USDC to WBTC using Hashflow RFQ + // + // USDC ───(Hashflow RFQ)──> WBTC + + // The Hashflow order expects: + // - 4308094737 USDC input -> 3714751 WBTC output + + uint256 amountIn = 4308094737; + uint256 expectedAmountOut = 3714751; + deal(USDC_ADDR, ALICE, amountIn); + uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); + + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); + bytes memory callData = loadCallDataFromFile("test_hashflow"); + (bool success,) = tychoRouterAddr.call(callData); + + vm.stopPrank(); + + uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balanceAfter - balanceBefore, expectedAmountOut); + assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + } +} From c013bf707225b171e76f3f3dac88624b6a3458dc Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:13:12 +0200 Subject: [PATCH 08/14] fix: in bebop's encode_swap, fail early if router address is not present --- .../evm/swap_encoder/swap_encoders.rs | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index d2d9bac..ce4cadc 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -711,28 +711,24 @@ impl SwapEncoder for BebopSwapEncoder { ) -> Result, EncodingError> { let token_in = bytes_to_address(&swap.token_in)?; let token_out = bytes_to_address(&swap.token_out)?; - - let token_approvals_manager = ProtocolApprovalsManager::new()?; - let approval_needed: bool; - - if let Some(router_address) = &encoding_context.router_address { - let tycho_router_address = bytes_to_address(router_address)?; + let sender = encoding_context + .router_address + .clone() + .ok_or(EncodingError::FatalError( + "The router address is needed to perform a Hashflow swap".to_string(), + ))?; + let approval_needed = if swap.token_in == self.native_token_address { + false + } else { + let tycho_router_address = bytes_to_address(&sender)?; let settlement_address = Address::from_str(&self.settlement_address) .map_err(|_| EncodingError::FatalError("Invalid settlement address".to_string()))?; - - // Native ETH doesn't need approval, only ERC20 tokens do - if swap.token_in == self.native_token_address { - approval_needed = false; - } else { - approval_needed = token_approvals_manager.approval_needed( - token_in, - tycho_router_address, - settlement_address, - )?; - } - } else { - approval_needed = true; - } + ProtocolApprovalsManager::new()?.approval_needed( + token_in, + tycho_router_address, + settlement_address, + )? + }; let (partial_fill_offset, original_filled_taker_amount, bebop_calldata) = if let Some(state) = swap.protocol_state { @@ -894,16 +890,16 @@ impl SwapEncoder for HashflowSwapEncoder { .ok_or(EncodingError::FatalError( "The router address is needed to perform a Hashflow swap".to_string(), ))?; - let tycho_router_address = bytes_to_address(&sender)?; - let hashflow_router_address = - Address::from_str(&self.hashflow_router_address).map_err(|_| { - EncodingError::FatalError("Invalid hashflow router address address".to_string()) - })?; // Native ETH doesn't need approval, only ERC20 tokens do let approval_needed = if swap.token_in == self.native_token_address { false } else { + let tycho_router_address = bytes_to_address(&sender)?; + let hashflow_router_address = Address::from_str(&self.hashflow_router_address) + .map_err(|_| { + EncodingError::FatalError("Invalid hashflow router address address".to_string()) + })?; ProtocolApprovalsManager::new()?.approval_needed( bytes_to_address(&swap.token_in)?, tycho_router_address, From 81e6a6ea93e549738315b23452b94d986dc16cad Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 10:16:03 +0200 Subject: [PATCH 09/14] chore: forge fmt --- foundry/test/TychoRouterTestSetup.sol | 4 ++-- foundry/test/protocols/Hashflow.t.sol | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 843eeff..aadb3e1 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -11,8 +11,8 @@ import {HashflowExecutor} from "../src/executors/HashflowExecutor.sol"; import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol"; import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol"; import { -UniswapV3Executor, -IUniswapV3Pool + UniswapV3Executor, + IUniswapV3Pool } from "../src/executors/UniswapV3Executor.sol"; import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol"; diff --git a/foundry/test/protocols/Hashflow.t.sol b/foundry/test/protocols/Hashflow.t.sol index 42d32b9..d0dc905 100644 --- a/foundry/test/protocols/Hashflow.t.sol +++ b/foundry/test/protocols/Hashflow.t.sol @@ -1,9 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.26; +import "../TychoRouterTestSetup.sol"; import "@src/executors/HashflowExecutor.sol"; -import {Constants} from "../Constants.sol"; import "forge-std/Test.sol"; +import {Constants} from "../Constants.sol"; contract HashflowUtils is Test { constructor() {} From 5c5678f2913925b0b3b10e68b3918b44e73ed236 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 20 Aug 2025 12:09:15 +0200 Subject: [PATCH 10/14] test: update hashflow sequential swap test --- foundry/test/TychoRouterSequentialSwap.t.sol | 13 ++++++++----- src/encoding/evm/swap_encoder/swap_encoders.rs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 3d24a41..8a25c0d 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -558,11 +558,14 @@ contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { // // WETH ──(USV3)──> USDC ───(Hashflow RFQ)──> WBTC - // The Hashflow order expects: - // - 4308094737 USDC input -> 3724533 WBTC output + // The Uniswap pool outputs: + // - 1 weth -> 4322430557 USDC + // The Hashflow tradeRFQT call expects: + // - 4308094737 USDC input -> 3714751 WBTC output + // The difference in USDC (14335820) will stay in the TychoRouter contract uint256 amountIn = 1 ether; - uint256 expectedAmountOut = 3724533; + uint256 expectedAmountOut = 3714751; deal(WETH_ADDR, ALICE, amountIn); uint256 balanceBefore = IERC20(WBTC_ADDR).balanceOf(ALICE); @@ -576,8 +579,8 @@ contract TychoRouterSequentialSwapTestForHashflow is TychoRouterTestSetup { uint256 balanceAfter = IERC20(WBTC_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); - assertGt(balanceAfter - balanceBefore, 0); - assertLe(balanceAfter - balanceBefore, expectedAmountOut); + assertEq(balanceAfter - balanceBefore, expectedAmountOut); assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0); + assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 14335820); } } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index ce4cadc..fed6326 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -2200,7 +2200,7 @@ mod tests { } #[test] - fn test_encode_hashflow_single_with_user_data() { + fn test_encode_hashflow_single_fails_without_protocol_data() { // Hashflow requires a swap with protocol data, otherwise will return an error let hashflow_component = ProtocolComponent { id: String::from("hashflow-rfq"), From 52c91a12b88894906a8c5670a9f3b73d4f595718 Mon Sep 17 00:00:00 2001 From: adrian Date: Thu, 21 Aug 2025 09:51:36 +0200 Subject: [PATCH 11/14] refactor: remove `effectiveTrader` from calldata and always set it to `trader`'s value --- foundry/src/executors/HashflowExecutor.sol | 24 ++++++++++--------- foundry/test/assets/calldata.txt | 4 ++-- .../evm/swap_encoder/swap_encoders.rs | 6 ----- .../optimized_transfers_integration_tests.rs | 10 -------- 4 files changed, 15 insertions(+), 29 deletions(-) diff --git a/foundry/src/executors/HashflowExecutor.sol b/foundry/src/executors/HashflowExecutor.sol index 60d4f72..56be01f 100644 --- a/foundry/src/executors/HashflowExecutor.sol +++ b/foundry/src/executors/HashflowExecutor.sol @@ -94,7 +94,7 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { TransferType transferType ) { - if (data.length != 347) { + if (data.length != 327) { revert HashflowExecutor__InvalidDataLength(); } @@ -104,16 +104,18 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom { quote.pool = address(bytes20(data[2:22])); quote.externalAccount = address(bytes20(data[22:42])); quote.trader = address(bytes20(data[42:62])); - quote.effectiveTrader = address(bytes20(data[62:82])); - quote.baseToken = address(bytes20(data[82:102])); - quote.quoteToken = address(bytes20(data[102:122])); - quote.effectiveBaseTokenAmount = 0; // Not included in the calldata, set in the swap function - quote.baseTokenAmount = uint256(bytes32(data[122:154])); - quote.quoteTokenAmount = uint256(bytes32(data[154:186])); - quote.quoteExpiry = uint256(bytes32(data[186:218])); - quote.nonce = uint256(bytes32(data[218:250])); - quote.txid = bytes32(data[250:282]); - quote.signature = data[282:347]; + // Assumes we never set the effectiveTrader when requesting a quote. + quote.effectiveTrader = quote.trader; + quote.baseToken = address(bytes20(data[62:82])); + quote.quoteToken = address(bytes20(data[82:102])); + // Not included in the calldata. Will be set in the swap function. + quote.effectiveBaseTokenAmount = 0; + quote.baseTokenAmount = uint256(bytes32(data[102:134])); + quote.quoteTokenAmount = uint256(bytes32(data[134:166])); + quote.quoteExpiry = uint256(bytes32(data[166:198])); + quote.nonce = uint256(bytes32(data[198:230])); + quote.txid = bytes32(data[230:262]); + quote.signature = data[262:327]; } function _balanceOf(address trader, address token) diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index b771a8f..ada2ac5 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -38,5 +38,5 @@ test_single_encoding_strategy_bebop_aggregate:5c4b639c00000000000000000000000000 test_single_encoding_strategy_bebop:5c4b639c000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be300000000000000000000000000000000000000000000000a8aea46aa4ec5c0f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a10000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000002d7d6bbde9174b1cdaa358d2cf4d57d1a9f7178fbffa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48faba6f8e4a5e8ab82f62fe7c39859fa577269be3000c000000000000000000000000000000000000000000000000000000000bebc20001d2068e04cf586f76eece7ba5beb779d7bb1474a14dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000000000000000000000 test_sequential_swap_strategy_encoder_unwrap:51bcc7b600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068c5498700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000689dc38f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041192fc75d79a3d76bcf3c3d193cf769446abc98ff76ce2a1de183e7e46d80073836cd6ceedc30f98085188eab1098ca2f0ef03c25ebaa69cd2758988263e563c41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48004375dff511095cc5a197a54140a24efef3a416bb2b8038a1640196fbe3e38816f3e67cba72d940000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950102000000000000000000000000000000000000000000000000 test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000 -test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001dc00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000016f15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c00000000 -test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016f15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000000000000000000000000000 +test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c800692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000015b15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c000000000000000000000000000000000000000000000000 +test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000 diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index fed6326..639a8cc 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -948,7 +948,6 @@ impl SwapEncoder for HashflowSwapEncoder { "pool", "external_account", "trader", - "effective_trader", "base_token", "quote_token", "base_token_amount", @@ -2258,11 +2257,6 @@ mod tests { "trader".to_string(), Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), - ( - // Passing the tycho router address here has no effect - "effective_trader".to_string(), - Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), - ), ( "base_token".to_string(), Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), diff --git a/tests/optimized_transfers_integration_tests.rs b/tests/optimized_transfers_integration_tests.rs index 3e20c76..b3a157e 100644 --- a/tests/optimized_transfers_integration_tests.rs +++ b/tests/optimized_transfers_integration_tests.rs @@ -741,11 +741,6 @@ fn test_hashflow() { "trader".to_string(), Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), - ( - // Passing the tycho router address here has no effect - "effective_trader".to_string(), - Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), - ), ( "base_token".to_string(), Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), @@ -872,11 +867,6 @@ fn test_uniswap_v3_hashflow() { "trader".to_string(), Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), ), - ( - // Passing the tycho router address here has no effect - "effective_trader".to_string(), - Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), - ), ( "base_token".to_string(), Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), From f4d3fe75e8325d9af2c72c9a5f01198bedc02fa3 Mon Sep 17 00:00:00 2001 From: adrian Date: Thu, 21 Aug 2025 11:20:06 +0200 Subject: [PATCH 12/14] test: fix executor tests after removing the effectiveTrader field --- foundry/test/assets/calldata.txt | 1 + foundry/test/protocols/Hashflow.t.sol | 72 +++++------ .../optimized_transfers_integration_tests.rs | 104 ---------------- tests/protocol_integration_tests.rs | 113 +++++++++++++++++- 4 files changed, 147 insertions(+), 143 deletions(-) diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index ada2ac5..41520f7 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -40,3 +40,4 @@ test_sequential_swap_strategy_encoder_unwrap:51bcc7b6000000000000000000000000000 test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000 test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c800692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000015b15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c000000000000000000000000000000000000000000000000 test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000 +test_single_encoding_strategy_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000 diff --git a/foundry/test/protocols/Hashflow.t.sol b/foundry/test/protocols/Hashflow.t.sol index d0dc905..dee68d8 100644 --- a/foundry/test/protocols/Hashflow.t.sol +++ b/foundry/test/protocols/Hashflow.t.sol @@ -20,7 +20,6 @@ contract HashflowUtils is Test { quote.pool, // pool (20 bytes) quote.externalAccount, // externalAccount (20 bytes) quote.trader, // trader (20 bytes) - quote.effectiveTrader, // effectiveTrader (20 bytes) quote.baseToken, // baseToken (20 bytes) quote.quoteToken, // quoteToken (20 bytes) quote.baseTokenAmount, // baseTokenAmount (32 bytes) @@ -52,7 +51,7 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils { IERC20 USDC = IERC20(USDC_ADDR); function setUp() public { - forkBlock = 23124977; // Using expiry date: 1755001853, ECR20 + forkBlock = 23188416; // Using expiry date: 1755766775, ECR20 vm.createSelectFork("mainnet", forkBlock); executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS); } @@ -124,54 +123,54 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils { } function testSwapNoSlippage() public { - address trader = address(executor); + address trader = address(ALICE); IHashflowRouter.RFQTQuote memory quote = rfqtQuote(); uint256 amountIn = quote.baseTokenAmount; bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote); - deal(USDC_ADDR, address(executor), amountIn); - uint256 balanceBefore = WETH.balanceOf(trader); + deal(WETH_ADDR, address(executor), amountIn); + uint256 balanceBefore = USDC.balanceOf(trader); vm.prank(trader); uint256 amountOut = executor.swap(amountIn, encodedQuote); - uint256 balanceAfter = WETH.balanceOf(trader); + uint256 balanceAfter = USDC.balanceOf(trader); assertGt(balanceAfter, balanceBefore); assertEq(balanceAfter - balanceBefore, amountOut); assertEq(amountOut, quote.quoteTokenAmount); } function testSwapRouterAmountUnderQuoteAmount() public { - address trader = address(executor); + address trader = address(ALICE); IHashflowRouter.RFQTQuote memory quote = rfqtQuote(); uint256 amountIn = quote.baseTokenAmount - 1; bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote); - deal(USDC_ADDR, address(executor), amountIn); - uint256 balanceBefore = WETH.balanceOf(trader); + deal(WETH_ADDR, address(executor), amountIn); + uint256 balanceBefore = USDC.balanceOf(trader); vm.prank(trader); uint256 amountOut = executor.swap(amountIn, encodedQuote); - uint256 balanceAfter = WETH.balanceOf(trader); + uint256 balanceAfter = USDC.balanceOf(trader); assertGt(balanceAfter, balanceBefore); assertEq(balanceAfter - balanceBefore, amountOut); assertLt(amountOut, quote.quoteTokenAmount); } function testSwapRouterAmountOverQuoteAmount() public { - address trader = address(executor); + address trader = address(ALICE); IHashflowRouter.RFQTQuote memory quote = rfqtQuote(); uint256 amountIn = quote.baseTokenAmount + 1; bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote); - deal(USDC_ADDR, address(executor), amountIn); - uint256 balanceBefore = WETH.balanceOf(trader); + deal(WETH_ADDR, address(executor), amountIn); + uint256 balanceBefore = USDC.balanceOf(trader); vm.prank(trader); uint256 amountOut = executor.swap(amountIn, encodedQuote); - uint256 balanceAfter = WETH.balanceOf(trader); + uint256 balanceAfter = USDC.balanceOf(trader); assertGt(balanceAfter, balanceBefore); assertEq(balanceAfter - balanceBefore, amountOut); assertEq(amountOut, quote.quoteTokenAmount); @@ -183,23 +182,23 @@ contract HashflowExecutorECR20Test is Constants, HashflowUtils { returns (IHashflowRouter.RFQTQuote memory) { return IHashflowRouter.RFQTQuote({ - pool: address(0x4cE18FD7b44F40Aebd6911362d3AC25F14D5007f), - externalAccount: address(0x50C03775C8E5b6227F1E00C4b3e479b4A7C57983), - trader: address(executor), + pool: address(0x5d8853028fbF6a2da43c7A828cc5f691E9456B44), + externalAccount: address(0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31), + trader: address(ALICE), effectiveTrader: address(ALICE), - baseToken: USDC_ADDR, - quoteToken: WETH_ADDR, + baseToken: WETH_ADDR, + quoteToken: USDC_ADDR, effectiveBaseTokenAmount: 0, - baseTokenAmount: 100, - quoteTokenAmount: 23224549208, - quoteExpiry: 1755001853, - nonce: 1755001793084, + baseTokenAmount: 1000000000000000000, + quoteTokenAmount: 4286117034, + quoteExpiry: 1755766775, + nonce: 1755766744988, txid: bytes32( uint256( - 0x12500006400064000000174813b960ffffffffffffff00293fdb4569fe760000 + 0x12500006400064000186078c183380ffffffffffffff00296d737ff6ae950000 ) ), - signature: hex"5b26977fecaf794c3d6900b9523b9632b5c62623f92732347dc9f24d8b5c4d611f5d733bbe82b594b6b47ab8aa1923c9f6b8aa66ef822ce412a767200f1520e11b" + signature: hex"649d31cd74f1b11b4a3b32bd38c2525d78ce8f23bc2eaf7700899c3a396d3a137c861737dc780fa154699eafb3108a34cbb2d4e31a6f0623c169cc19e0fa296a1c" }); } } @@ -214,13 +213,13 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils { IERC20 USDC = IERC20(USDC_ADDR); function setUp() public { - forkBlock = 23125321; // Using expiry date: 1755006017, Native + forkBlock = 23188504; // Using expiry date: 1755767859, Native vm.createSelectFork("mainnet", forkBlock); executor = new HashflowExecutorExposed(HASHFLOW_ROUTER, PERMIT2_ADDRESS); } function testSwapNoSlippage() public { - address trader = address(executor); + address trader = address(ALICE); IHashflowRouter.RFQTQuote memory quote = rfqtQuote(); uint256 amountIn = quote.baseTokenAmount; bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote); @@ -243,23 +242,23 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils { returns (IHashflowRouter.RFQTQuote memory) { return IHashflowRouter.RFQTQuote({ - pool: address(0x51199bE500A8c59262478b621B1096F17638dc6F), - externalAccount: address(0xCe79b081c0c924cb67848723ed3057234d10FC6b), - trader: address(executor), + pool: address(0x713DC4Df480235dBe2fB766E7120Cbd4041Dcb58), + externalAccount: address(0x111BB8c3542F2B92fb41B8d913c01D3788431111), + trader: address(ALICE), effectiveTrader: address(ALICE), baseToken: address(0x0000000000000000000000000000000000000000), quoteToken: USDC_ADDR, effectiveBaseTokenAmount: 0, baseTokenAmount: 10000000000000000, - quoteTokenAmount: 43930745, - quoteExpiry: 1755006017, - nonce: 1755005977455, + quoteTokenAmount: 42586008, + quoteExpiry: 1755767859, + nonce: 1755767819299, txid: bytes32( uint256( - 0x1250000640006400019071ef777818ffffffffffffff0029401b1bc51da00000 + 0x1250000640006400018380fd594810ffffffffffffff00296d83e467cddd0000 ) ), - signature: hex"4c3554c928e4b15cd53d1047aee69a66103effa5107047b84949e48460b6978f25da9ad5b9ed31aa9ab2130e597fabea872f14b8c1b166ea079413cbaf2f4b4c1c" + signature: hex"63c1c9c7d6902d1d4d2ae82777015433ef08366dde1c579a8c4cbc01059166064246f61f15b2cb130be8f2b28ea40d2c3586ef0133647fefa30003e70ffbd6131b" }); } } @@ -302,7 +301,8 @@ contract TychoRouterSingleSwapTestForHashflow is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(USDC_ADDR).approve(tychoRouterAddr, type(uint256).max); - bytes memory callData = loadCallDataFromFile("test_hashflow"); + bytes memory callData = + loadCallDataFromFile("test_single_encoding_strategy_hashflow"); (bool success,) = tychoRouterAddr.call(callData); vm.stopPrank(); diff --git a/tests/optimized_transfers_integration_tests.rs b/tests/optimized_transfers_integration_tests.rs index b3a157e..61c3709 100644 --- a/tests/optimized_transfers_integration_tests.rs +++ b/tests/optimized_transfers_integration_tests.rs @@ -711,110 +711,6 @@ fn test_uniswap_v3_bebop() { write_calldata_to_file("test_uniswap_v3_bebop", hex_calldata.as_str()); } -#[test] -fn test_hashflow() { - // Note: This test does not assert anything. It is only used to obtain - // integration test data for our router solidity test. - // - // Performs a swap from USDC to WBTC using Hashflow RFQ - // - // USDC ───(Hashflow RFQ)──> WBTC - - let usdc = usdc(); - let wbtc = wbtc(); - - // USDC -> WBTC via Hashflow RFQ using real order data - let quote_amount_out = BigUint::from_str("3714751").unwrap(); - - let hashflow_state = MockRFQState { - quote_amount_out, - quote_data: HashMap::from([ - ( - "pool".to_string(), - Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), - ), - ( - "external_account".to_string(), - Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(), - ), - ( - "trader".to_string(), - Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), - ), - ( - "base_token".to_string(), - Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), - ), - ( - "quote_token".to_string(), - Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(), - ), - ( - "base_token_amount".to_string(), - Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()), - ), - ( - "quote_token_amount".to_string(), - Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()), - ), - ("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())), - ("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())), - ( - "tx_id".to_string(), - Bytes::from_str( - "0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000", - ) - .unwrap(), - ), - ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), - ]), - }; - - let hashflow_component = ProtocolComponent { - id: String::from("hashflow-rfq"), - protocol_system: String::from("rfq:hashflow"), - ..Default::default() - }; - - let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone()) - .estimated_amount_in(BigUint::from_str("4308094737").unwrap()) - .protocol_state(&hashflow_state) - .build(); - - let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom); - - let solution = Solution { - exact_out: false, - given_token: usdc, - given_amount: BigUint::from_str("4308094737").unwrap(), - checked_token: wbtc, - checked_amount: BigUint::from_str("3714751").unwrap(), - sender: alice_address(), - receiver: alice_address(), - swaps: vec![swap_usdc_wbtc], - ..Default::default() - }; - - let encoded_solution = encoder - .encode_solutions(vec![solution.clone()]) - .unwrap()[0] - .clone(); - - let calldata = encode_tycho_router_call( - eth_chain().id(), - encoded_solution, - &solution, - &UserTransferType::TransferFrom, - ð(), - None, - ) - .unwrap() - .data; - - let hex_calldata = encode(&calldata); - write_calldata_to_file("test_hashflow", hex_calldata.as_str()); -} - #[test] #[ignore] fn test_uniswap_v3_hashflow() { diff --git a/tests/protocol_integration_tests.rs b/tests/protocol_integration_tests.rs index b0b416c..fb13bc7 100644 --- a/tests/protocol_integration_tests.rs +++ b/tests/protocol_integration_tests.rs @@ -5,13 +5,16 @@ use alloy::{hex, hex::encode}; use num_bigint::{BigInt, BigUint}; use tycho_common::{models::protocol::ProtocolComponent, Bytes}; use tycho_execution::encoding::{ - evm::utils::{biguint_to_u256, write_calldata_to_file}, + evm::{ + testing_utils::MockRFQState, + utils::{biguint_to_u256, write_calldata_to_file}, + }, models::{Solution, Swap, SwapBuilder, UserTransferType}, }; use crate::common::{ - build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain, get_signer, - get_tycho_router_encoder, ondo, pepe, usdc, weth, + alice_address, build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain, + get_signer, get_tycho_router_encoder, ondo, pepe, usdc, wbtc, weth, }; #[test] @@ -714,3 +717,107 @@ fn test_single_encoding_strategy_bebop_aggregate() { write_calldata_to_file("test_single_encoding_strategy_bebop_aggregate", hex_calldata.as_str()); } + +#[test] +fn test_single_encoding_strategy_hashflow() { + // Note: This test does not assert anything. It is only used to obtain + // integration test data for our router solidity test. + // + // Performs a swap from USDC to WBTC using Hashflow RFQ + // + // USDC ───(Hashflow RFQ)──> WBTC + + let usdc = usdc(); + let wbtc = wbtc(); + + // USDC -> WBTC via Hashflow RFQ using real order data + let quote_amount_out = BigUint::from_str("3714751").unwrap(); + + let hashflow_state = MockRFQState { + quote_amount_out, + quote_data: HashMap::from([ + ( + "pool".to_string(), + Bytes::from_str("0x478eca1b93865dca0b9f325935eb123c8a4af011").unwrap(), + ), + ( + "external_account".to_string(), + Bytes::from_str("0xbee3211ab312a8d065c4fef0247448e17a8da000").unwrap(), + ), + ( + "trader".to_string(), + Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap(), + ), + ( + "base_token".to_string(), + Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(), + ), + ( + "quote_token".to_string(), + Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(), + ), + ( + "base_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(4308094737_u64)).to_be_bytes::<32>().to_vec()), + ), + ( + "quote_token_amount".to_string(), + Bytes::from(biguint_to_u256(&BigUint::from(3714751_u64)).to_be_bytes::<32>().to_vec()), + ), + ("quote_expiry".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610328_u64)).to_be_bytes::<32>().to_vec())), + ("nonce".to_string(), Bytes::from(biguint_to_u256(&BigUint::from(1755610283723_u64)).to_be_bytes::<32>().to_vec())), + ( + "tx_id".to_string(), + Bytes::from_str( + "0x125000064000640000001747eb8c38ffffffffffffff0029642016edb36d0000", + ) + .unwrap(), + ), + ("signature".to_string(), Bytes::from_str("0x6ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c").unwrap()), + ]), + }; + + let hashflow_component = ProtocolComponent { + id: String::from("hashflow-rfq"), + protocol_system: String::from("rfq:hashflow"), + ..Default::default() + }; + + let swap_usdc_wbtc = SwapBuilder::new(hashflow_component, usdc.clone(), wbtc.clone()) + .estimated_amount_in(BigUint::from_str("4308094737").unwrap()) + .protocol_state(&hashflow_state) + .build(); + + let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom); + + let solution = Solution { + exact_out: false, + given_token: usdc, + given_amount: BigUint::from_str("4308094737").unwrap(), + checked_token: wbtc, + checked_amount: BigUint::from_str("3714751").unwrap(), + sender: alice_address(), + receiver: alice_address(), + swaps: vec![swap_usdc_wbtc], + ..Default::default() + }; + + let encoded_solution = encoder + .encode_solutions(vec![solution.clone()]) + .unwrap()[0] + .clone(); + + let calldata = encode_tycho_router_call( + eth_chain().id(), + encoded_solution, + &solution, + &UserTransferType::TransferFrom, + ð(), + None, + ) + .unwrap() + .data; + + let hex_calldata = encode(&calldata); + write_calldata_to_file("test_single_encoding_strategy_hashflow", hex_calldata.as_str()); +} From 3aad7926638217ecf280797ade1a0a650647b59f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 21 Aug 2025 11:13:06 +0000 Subject: [PATCH 13/14] chore(release): 0.117.0 [skip ci] ## [0.117.0](https://github.com/propeller-heads/tycho-execution/compare/0.116.0...0.117.0) (2025-08-21) ### Features * implement `SwapEncoder` for `Hashflow` ([93db953](https://github.com/propeller-heads/tycho-execution/commit/93db953c620f4d52e8852ff8148f2dfdbc580029)) ### Bug Fixes * in bebop's encode_swap, fail early if router address is not present ([c013bf7](https://github.com/propeller-heads/tycho-execution/commit/c013bf707225b171e76f3f3dac88624b6a3458dc)) * in hashflow's encode_swap, fail early if router address is not present ([c506f2c](https://github.com/propeller-heads/tycho-execution/commit/c506f2c048a20d1ffd066d2903d2cc469fd167ed)) * in HashflowExecutor, _balanceOf must use `trader` address instead of the executor's to get the balance ([a09d648](https://github.com/propeller-heads/tycho-execution/commit/a09d648f3c50f87b392c7c5eb14af07307c5ccea)) --- CHANGELOG.md | 14 ++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ad9a3..77a3d50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [0.117.0](https://github.com/propeller-heads/tycho-execution/compare/0.116.0...0.117.0) (2025-08-21) + + +### Features + +* implement `SwapEncoder` for `Hashflow` ([93db953](https://github.com/propeller-heads/tycho-execution/commit/93db953c620f4d52e8852ff8148f2dfdbc580029)) + + +### Bug Fixes + +* in bebop's encode_swap, fail early if router address is not present ([c013bf7](https://github.com/propeller-heads/tycho-execution/commit/c013bf707225b171e76f3f3dac88624b6a3458dc)) +* in hashflow's encode_swap, fail early if router address is not present ([c506f2c](https://github.com/propeller-heads/tycho-execution/commit/c506f2c048a20d1ffd066d2903d2cc469fd167ed)) +* in HashflowExecutor, _balanceOf must use `trader` address instead of the executor's to get the balance ([a09d648](https://github.com/propeller-heads/tycho-execution/commit/a09d648f3c50f87b392c7c5eb14af07307c5ccea)) + ## [0.116.0](https://github.com/propeller-heads/tycho-execution/compare/0.115.0...0.116.0) (2025-08-19) diff --git a/Cargo.lock b/Cargo.lock index d16446a..1ce4bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4659,7 +4659,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.116.0" +version = "0.117.0" dependencies = [ "alloy", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 986110d..a9f7877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.116.0" +version = "0.117.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From 80853f7ba8c290674b2a6c7de0ac4a0123748ddf Mon Sep 17 00:00:00 2001 From: adrian Date: Thu, 21 Aug 2025 14:09:32 +0200 Subject: [PATCH 14/14] chore: remove unused entry in calldata.text --- Cargo.toml | 1 - foundry/test/assets/calldata.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9f7877..7d4778b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ chrono = "0.4.39" clap = { version = "4.5.3", features = ["derive"] } once_cell = "1.20.2" tycho-common = ">=0.81.6" - alloy = { version = "1.0.6", features = ["providers", "rpc-types-eth", "eip712", "signer-local", "node-bindings"], optional = true } async-trait = { version = "0.1.88", optional = true } diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index 41520f7..85f6ea3 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -39,5 +39,4 @@ test_single_encoding_strategy_bebop:5c4b639c000000000000000000000000000000000000 test_sequential_swap_strategy_encoder_unwrap:51bcc7b600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068c5498700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000689dc38f00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041192fc75d79a3d76bcf3c3d193cf769446abc98ff76ce2a1de183e7e46d80073836cd6ceedc30f98085188eab1098ca2f0ef03c25ebaa69cd2758988263e563c41b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48004375dff511095cc5a197a54140a24efef3a416bb2b8038a1640196fbe3e38816f3e67cba72d940000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950102000000000000000000000000000000000000000000000000 test_sequential_swap_usx:0101e21dd0d300000000000000000000000000000000000000000000006c6b935b8bbd4000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000769cfd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d9da78b6a5bedca287aa5d49613ba36b90c15c40000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470b6b175474e89094c44da98b954eedeac495271d0fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000643ede3eca2a72b3aecc820e955b36f38437d013955777d92f208679db4b9778590fa3cab3ac9e2168010000692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48dac17f958d2ee523a2206206994597c13d831ec70000646d9da78b6a5bedca287aa5d49613ba36b90c15c43416cf6c708da44db2624d63ea0aaef7113527c6010100000000000000000000 test_uniswap_v3_hashflow:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c800692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400000015b15cf58144ef33af1e14b5208015d11f9143e27b90201478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c000000000000000000000000000000000000000000000000 -test_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000 test_single_encoding_strategy_hashflow:5c4b639c0000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000000000000038aebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000015b15cf58144ef33af1e14b5208015d11f9143e27b90001478eca1b93865dca0b9f325935eb123c8a4af011bee3211ab312a8d065c4fef0247448e17a8da000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb482260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000100c84f11000000000000000000000000000000000000000000000000000000000038aebf0000000000000000000000000000000000000000000000000000000068a47cd800000000000000000000000000000000000000000000000000000198c286fecb125000064000640000001747eb8c38ffffffffffffff0029642016edb36d00006ddb3b21fe8509e274ddf46c55209cdbf30360944abbca6569ed6b26740d052f419964dcb5a3bdb98b4ed1fb3642a2760b8312118599a962251f7a8f73fe4fbe1c0000000000