From 4e8c6ddc8cffdb42471b31a4b334133f2cb975b9 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Wed, 21 May 2025 13:33:46 +0100 Subject: [PATCH 1/4] feat: Separate encoding swaps from encoding txs This way the user is responsible for encoding the Tycho Router method inputs that are used as guardrails in execution. Interface changes: - Create EncodedSolution - StrategyEncoder - don't need to know have permit2 or token_in_already_in_router as attributes anymore - encode_strategy returns EncodedSolution now (no method encoding done here now) - TychoEncoder - add encode_solution() method. This is the recommended method for users - needs to have permit2, token_in_already_in_router and router_address as attributes - permit creation is made in the router now Also: - create encoding_utils.rs - update all tests Took 2 hours 42 minutes Took 3 minutes Took 13 minutes --- foundry/test/assets/calldata.txt | 24 +- src/encoding/errors.rs | 2 + src/encoding/evm/approvals/permit2.rs | 5 +- .../approvals/protocol_approvals_manager.rs | 5 +- src/encoding/evm/encoding_utils.rs | 251 ++++++ src/encoding/evm/mod.rs | 1 + .../evm/strategy_encoder/strategy_encoders.rs | 733 ++++++++++-------- src/encoding/evm/tycho_encoders.rs | 134 +++- src/encoding/evm/utils.rs | 30 +- src/encoding/models.rs | 18 + src/encoding/strategy_encoder.rs | 15 +- src/encoding/tycho_encoder.rs | 71 +- 12 files changed, 869 insertions(+), 420 deletions(-) create mode 100644 src/encoding/evm/encoding_utils.rs diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index 28bd7c1..0e1927a 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -3,23 +3,23 @@ test_single_encoding_strategy_ekubo:5c4b639c000000000000000000000000000000000000 test_uniswap_v3_uniswap_v3:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000000692e234dae75c793f67a35089c9d99245e1c58470b2260fac5e5542a773aa44fbcfedf7c193bc2c599a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc299ac8ca7087fa4a2a1fb6357269965a2014abc35010100000000000000000000 test_balancer_v2_uniswap_v2:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000c80072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e004375dff511095cc5a197a54140a24efef3a416010000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 test_sequential_swap_strategy_encoder_no_permit2:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 -test_single_encoding_strategy_usv4_grouped_swap:30ace1b1000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041b70914df20e4b2675adc5a61d26b959e0fd21e7cce6503b884d5ba9e1db2c82164112e4dc362b9a3ef794a03fcfebe0710700028b79abe8b7de525288ac904991c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000000000000000 -test_single_encoding_strategy_usv4_eth_out:7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c8000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c33e9da2d2a5bea1087e27f42b9255fcaea06efa76558843f6192d571b15c09c5818e1a4b3a111bb6accc4cb8b583c318d2386e0e94cc87b9fcc4065db0d65611c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000 -test_sequential_swap_strategy_encoder:51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 +test_single_encoding_strategy_usv4_grouped_swap:30ace1b1000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041972d1d0dd19889db47087a4653c517a455a19a1dc76a0a14a29fd12ae7d2d4f218ba986eada63bfdfacf78e3a7ffdce07c4064fcf11033f185819509f64b6b191b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000000000000000 +test_single_encoding_strategy_usv4_eth_out:7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c0bbb5f493c1d4b87c92085902e4488efba2b87740abdcc298a5adb435bfe3b23820a0677d325e34fa06e8c2a91f0d3851b00cf170885176b5f47d40983d64941b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000 +test_sequential_swap_strategy_encoder:51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000419861aedfb04e8f7b9fe147a7d19c41f9b1cf37a06850b09f645fb351c003967d43d365af50b08ef741348898578b7fa1454a8e9cd0390eaf6c2939d7b817d0d31c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 test_single_swap_strategy_encoder_no_permit2:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 test_single_swap_strategy_encoder_no_transfer_in:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 -test_single_encoding_strategy_usv4_eth_in:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000007e0a55d4322a6e93c2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041f59744bfe3e154be7c72429eb93db7ef2ec00344feeb1376195a2e3f133437f7533c3df955de146e229e4a37fda22acbe11d9393215d14cf1e19334aebbf35e61b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330102cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000 -test_sequential_strategy_cyclic_swap:51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ec8f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010000692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000000000 +test_single_encoding_strategy_usv4_eth_in:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000007e0a55d4322a6e93c2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041a8ad04262cc02545cc4934739e086f37b3ea04ba83195a151a736e55cf2cc68407f9dc20f19c68071ced3f6d5f20908ce9332526c955e66974aaf53c7e58ca581c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330102cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000 +test_sequential_strategy_cyclic_swap:51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ec8f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041d461cc5890a8a58d13249a485335b44671612c97b7a12004b5cfb163bdbf4c2f15dde7cb500c22f724f813b7d6fe5062076f63ee5b91c7e984f011a5d1ce6fca1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010000692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000000000 test_single_encoding_strategy_curve_st_eth:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe84000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f670220100010002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 -test_single_swap_strategy_encoder:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006b56051582a970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 +test_single_swap_strategy_encoder:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006b56051582a970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000419861aedfb04e8f7b9fe147a7d19c41f9b1cf37a06850b09f645fb351c003967d43d365af50b08ef741348898578b7fa1454a8e9cd0390eaf6c2939d7b817d0d31c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 test_single_encoding_strategy_curve:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 -test_single_swap_strategy_encoder_unwrap:30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041e5679a7262a283109d2e312e4ffbec563f501f347efc00418f3a6701492635977583d4e2ff29904a33496b64f1bc2ac73796a546a495ab110cca86e6de1655b61c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000000000000000000000000000000 -test_single_swap_strategy_encoder_wrap:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000059fb7d3830e6fc064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004160aa554efc922689a7f426a984f9fc278aa948b9c78f4e7cc0a5f177e5fb6859632b92d3326f710603d43d3a8ea5753c58a61316fd1bff8e0b388f74c98993b91b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 -test_split_output_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005e703f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400001006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000 -test_split_input_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000100000000000000 -test_split_swap_strategy_encoder:7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20101005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010100000000000000000000000000000000000000000000000000000000 +test_single_swap_strategy_encoder_unwrap:30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be0000000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004133c6e3010dfd7e69b717338de47a7129351aefa89191faa6c76250356c6adae041da1ef7cb664511e942ecd28f7b578b38dad510df3185241c8336259027ded11c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000000000000000000000000000000 +test_single_swap_strategy_encoder_wrap:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000059fb7d3830e6fc064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041a8ad04262cc02545cc4934739e086f37b3ea04ba83195a151a736e55cf2cc68407f9dc20f19c68071ced3f6d5f20908ce9332526c955e66974aaf53c7e58ca581c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 +test_split_output_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005e703f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000685552e700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccef000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041f208f39e98ca0821f644f4fd3be7ab23ce75c0e3d6e425b00327067b482dc3963e0c481141fe530d79cc8361ea628a0f53d69cbbd09b7c2416581f04392704681b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400001006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000 +test_split_input_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e10000000000000000000000000000000000000000000000000000000000685552e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccee000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d461cc5890a8a58d13249a485335b44671612c97b7a12004b5cfb163bdbf4c2f15dde7cb500c22f724f813b7d6fe5062076f63ee5b91c7e984f011a5d1ce6fca1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000100000000000000 +test_split_swap_strategy_encoder:7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000685552e700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccef000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041726b94d0c0b711c31e19e1b33bcdd87e4526b4fd804471bbafcb0fed1fdc5aea23922bb4d46f16add4f01136165643feb355d89bf31d1a25a8a1a126ee3af43e1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20101005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010100000000000000000000000000000000000000000000000000000000 test_uniswap_v3_curve:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000000691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae460301000102cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000 -test_multi_protocol:51bcc7b600000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2958f36da71a9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041a81d713c715dd501edf102a3c3a755a931e3c72e7322d66b620ece81d7c98bae3d46479041445649eeefdb6ccc09c2b933cd37eda3ae8c99034e84f5570eb2c31c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021400525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e3ede3eca2a72b3aecc820e955b36f38437d01395010200691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae4603010001023ede3eca2a72b3aecc820e955b36f38437d013950071a0cb889707d426a7a386870a03bc70d1b0697598013ede3eca2a72b3aecc820e955b36f38437d01395dac17f958d2ee523a2206206994597c13d831ec7a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001a36e2eb1c43200000032006cf62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c000000000000000000000000 +test_multi_protocol:51bcc7b600000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2958f36da71a9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000005150ae84a8cdf0000000000000000000000000000000000000000000000000000000000000685552e700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682dccef00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410488df0c2ac790f75505dbd2754518d70fb02c32fb3bb3e63e299259037765606ca8d38d05c9c6d2d82e3b39203f378ad489f715a2aeb7df674ee4b8e5c8d1151c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021400525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e3ede3eca2a72b3aecc820e955b36f38437d01395010200691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae4603010001023ede3eca2a72b3aecc820e955b36f38437d013950071a0cb889707d426a7a386870a03bc70d1b0697598013ede3eca2a72b3aecc820e955b36f38437d01395dac17f958d2ee523a2206206994597c13d831ec7a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001a36e2eb1c43200000032006cf62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c000000000000000000000000 test_encode_balancer_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0102 test_ekubo_encode_swap_multi:01ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032 test_encode_uniswap_v4_sequential_swap:4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990101cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c diff --git a/src/encoding/errors.rs b/src/encoding/errors.rs index 85242d2..2949261 100644 --- a/src/encoding/errors.rs +++ b/src/encoding/errors.rs @@ -20,6 +20,8 @@ pub enum EncodingError { FatalError(String), #[error("Recoverable error: {0}")] RecoverableError(String), + #[error("Not implemented: {0}")] + NotImplementedError(String), } impl From for EncodingError { diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 51b1f6a..8ad8787 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -19,7 +19,10 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, - evm::utils::{biguint_to_u256, bytes_to_address, encode_input, get_client, get_runtime}, + evm::{ + encoding_utils::encode_input, + utils::{biguint_to_u256, bytes_to_address, get_client, get_runtime}, + }, models::Chain, }; diff --git a/src/encoding/evm/approvals/protocol_approvals_manager.rs b/src/encoding/evm/approvals/protocol_approvals_manager.rs index 364a4ce..8b5769b 100644 --- a/src/encoding/evm/approvals/protocol_approvals_manager.rs +++ b/src/encoding/evm/approvals/protocol_approvals_manager.rs @@ -14,7 +14,10 @@ use tokio::{ use crate::encoding::{ errors::EncodingError, - evm::utils::{encode_input, get_client, get_runtime}, + evm::{ + encoding_utils::encode_input, + utils::{get_client, get_runtime}, + }, }; /// A manager for checking if an approval is needed for interacting with a certain spender. diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs new file mode 100644 index 0000000..c70a129 --- /dev/null +++ b/src/encoding/evm/encoding_utils.rs @@ -0,0 +1,251 @@ +use alloy_primitives::{Keccak256, U256}; +use alloy_sol_types::SolValue; +use num_bigint::BigUint; +use tycho_common::Bytes; + +use crate::encoding::{ + errors::EncodingError, + evm::utils::{biguint_to_u256, bytes_to_address, get_min_amount_for_solution}, + models::{EncodedSolution, NativeAction, Solution, Transaction}, +}; + +/// Encodes the input data for a function call to the given function selector. +pub fn encode_input(selector: &str, mut encoded_args: Vec) -> Vec { + let mut hasher = Keccak256::new(); + hasher.update(selector.as_bytes()); + let selector_bytes = &hasher.finalize()[..4]; + let mut call_data = selector_bytes.to_vec(); + // Remove extra prefix if present (32 bytes for dynamic data) + // Alloy encoding is including a prefix for dynamic data indicating the offset or length + // but at this point we don't want that + if encoded_args.len() > 32 && + encoded_args[..32] == + [0u8; 31] + .into_iter() + .chain([32].to_vec()) + .collect::>() + { + encoded_args = encoded_args[32..].to_vec(); + } + call_data.extend(encoded_args); + call_data +} + +/// Encodes a transaction for the Tycho Router using one of its supported swap methods. +/// +/// # Overview +/// +/// This function provides an **example implementation** of how to encode a call to the Tycho +/// Router. It handles all currently supported swap selectors such as: +/// - `singleSwap` +/// - `singleSwapPermit2` +/// - `sequentialSwap` +/// - `sequentialSwapPermit2` +/// - `splitSwap` +/// - `splitSwapPermit2` +/// +/// The encoding includes handling of native asset wrapping/unwrapping, permit2 support, +/// and proper input argument formatting based on the selector string. +/// +/// # ⚠️ Important Responsibility Note +/// +/// This function is intended as **an illustrative example only**. **Users must implement +/// their own encoding logic** to ensure: +/// - Full control of parameters passed to the router. +/// - Proper validation and setting of critical inputs such as `minAmountOut`. +/// +/// While Tycho is responsible for encoding the swap paths themselves, the input arguments +/// to the router's methods act as **guardrails** for on-chain execution safety. +/// Thus, the user must **take responsibility** for ensuring correctness of all input parameters, +/// including `minAmountOut`, `receiver`, and permit2 logic. +/// +/// # Min Amount Out +/// +/// The `minAmountOut` calculation used here is just an example. +/// You should ideally: +/// - Query an external service (e.g., DEX aggregators, oracle, off-chain price feed). +/// - Use your own strategy to determine an accurate and safe minimum acceptable output amount. +/// +/// ⚠️ If `minAmountOut` is too low, your swap may be front-run or sandwiched, resulting in loss of +/// funds. +/// +/// # Parameters +/// - `encoded_solution`: The solution already encoded by Tycho, including selector and swap path. +/// - `solution`: The high-level solution including tokens, amounts, and receiver info. +/// - `token_in_already_in_router`: Whether the input token is already present in the router. +/// - `router_address`: The address of the Tycho Router contract. +/// - `native_address`: The address used to represent the native token +/// +/// # Returns +/// A `Result` that either contains the full transaction data (to, +/// value, data), or an error if the inputs are invalid. +/// +/// # Errors +/// - Returns `EncodingError::FatalError` if the selector is unsupported or required fields (e.g., +/// permit or signature) are missing. +pub fn encode_tycho_router_call( + encoded_solution: EncodedSolution, + solution: &Solution, + token_in_already_in_router: bool, + router_address: Bytes, + native_address: Bytes, +) -> Result { + let min_amount_out = get_min_amount_for_solution(solution.clone()); + let (mut unwrap, mut wrap) = (false, false); + if let Some(action) = solution.native_action.clone() { + match action { + NativeAction::Wrap => wrap = true, + NativeAction::Unwrap => unwrap = true, + } + } + + let given_amount = biguint_to_u256(&solution.given_amount); + let min_amount_out = biguint_to_u256(&min_amount_out); + let given_token = bytes_to_address(&solution.given_token)?; + let checked_token = bytes_to_address(&solution.checked_token)?; + let receiver = bytes_to_address(&solution.receiver)?; + let n_tokens = U256::from(encoded_solution.n_tokens); + + let method_calldata = if encoded_solution + .selector + .contains("singleSwapPermit2") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + receiver, + encoded_solution + .permit + .ok_or(EncodingError::FatalError( + "permit2 object must be set to use permit2".to_string(), + ))?, + encoded_solution + .signature + .ok_or(EncodingError::FatalError( + "Signature must be set to use permit2".to_string(), + ))? + .as_bytes() + .to_vec(), + encoded_solution.swaps, + ) + .abi_encode() + } else if encoded_solution + .selector + .contains("singleSwap") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + receiver, + !token_in_already_in_router, + encoded_solution.swaps, + ) + .abi_encode() + } else if encoded_solution + .selector + .contains("sequentialSwapPermit2") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + receiver, + encoded_solution + .permit + .ok_or(EncodingError::FatalError( + "permit2 object must be set to use permit2".to_string(), + ))?, + encoded_solution + .signature + .ok_or(EncodingError::FatalError( + "Signature must be set to use permit2".to_string(), + ))? + .as_bytes() + .to_vec(), + encoded_solution.swaps, + ) + .abi_encode() + } else if encoded_solution + .selector + .contains("sequentialSwap") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + receiver, + !token_in_already_in_router, + encoded_solution.swaps, + ) + .abi_encode() + } else if encoded_solution + .selector + .contains("splitSwapPermit2") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + n_tokens, + receiver, + encoded_solution + .permit + .ok_or(EncodingError::FatalError( + "permit2 object must be set to use permit2".to_string(), + ))?, + encoded_solution + .signature + .ok_or(EncodingError::FatalError( + "Signature must be set to use permit2".to_string(), + ))? + .as_bytes() + .to_vec(), + encoded_solution.swaps, + ) + .abi_encode() + } else if encoded_solution + .selector + .contains("splitSwap") + { + ( + given_amount, + given_token, + checked_token, + min_amount_out, + wrap, + unwrap, + n_tokens, + receiver, + !token_in_already_in_router, + encoded_solution.swaps, + ) + .abi_encode() + } else { + Err(EncodingError::FatalError("Invalid selector for Tycho router".to_string()))? + }; + + let contract_interaction = encode_input(&encoded_solution.selector, method_calldata); + let value = if solution.given_token == native_address { + solution.given_amount.clone() + } else { + BigUint::ZERO + }; + Ok(Transaction { to: router_address, value, data: contract_interaction }) +} diff --git a/src/encoding/evm/mod.rs b/src/encoding/evm/mod.rs index 344aaa1..eea7873 100644 --- a/src/encoding/evm/mod.rs +++ b/src/encoding/evm/mod.rs @@ -1,6 +1,7 @@ pub mod approvals; mod constants; pub mod encoder_builders; +pub mod encoding_utils; mod group_swaps; pub mod strategy_encoder; mod swap_encoder; diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index df9719e..bd487ab 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1,25 +1,20 @@ use std::{collections::HashSet, str::FromStr}; -use alloy_primitives::{aliases::U24, U256, U8}; -use alloy_sol_types::SolValue; +use alloy_primitives::{aliases::U24, U8}; use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, evm::{ - approvals::permit2::Permit2, group_swaps::group_swaps, strategy_encoder::{ strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator}, transfer_optimizations::TransferOptimization, }, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, - utils::{ - biguint_to_u256, bytes_to_address, encode_input, get_min_amount_for_solution, - get_token_position, percentage_to_uint24, ple_encode, - }, + utils::{get_token_position, percentage_to_uint24, ple_encode}, }, - models::{Chain, EncodingContext, NativeAction, Solution}, + models::{Chain, EncodedSolution, EncodingContext, NativeAction, Solution}, strategy_encoder::StrategyEncoder, swap_encoder::SwapEncoder, }; @@ -28,41 +23,32 @@ use crate::encoding::{ /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary -/// signatures and permit2 objects for calling the router /// * `selector`: String, the selector for the swap function in the router contract /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers -/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - permit2: Option, selector: String, router_address: Bytes, transfer_optimization: TransferOptimization, - token_in_already_in_router: bool, } impl SingleSwapStrategyEncoder { pub fn new( chain: Chain, swap_encoder_registry: SwapEncoderRegistry, - swapper_pk: Option, + permit_2_active: bool, router_address: Bytes, token_in_already_in_router: bool, ) -> Result { - let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { - (Some(Permit2::new(swapper_pk, chain.clone())?), "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) + let selector = if permit_2_active { + "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { - ( - None, - "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" - .to_string(), - ) - }; + "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" + }.to_string(); + Ok(Self { - permit2, selector, swap_encoder_registry, router_address: router_address.clone(), @@ -72,7 +58,6 @@ impl SingleSwapStrategyEncoder { token_in_already_in_router, router_address, ), - token_in_already_in_router, }) } @@ -87,7 +72,7 @@ impl SingleSwapStrategyEncoder { } impl StrategyEncoder for SingleSwapStrategyEncoder { - fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { + fn encode_strategy(&self, solution: Solution) -> Result { let grouped_swaps = group_swaps(solution.clone().swaps); let number_of_groups = grouped_swaps.len(); if number_of_groups != 1 { @@ -106,8 +91,6 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { )) } - let min_amount_out = get_min_amount_for_solution(solution.clone()); - let (mut unwrap, mut wrap) = (false, false); if let Some(action) = solution.native_action.clone() { match action { @@ -150,44 +133,13 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?, grouped_protocol_data, ); - - let method_calldata = if let Some(permit2) = self.permit2.clone() { - let (permit, signature) = permit2.get_permit( - &self.router_address, - &solution.sender, - &solution.given_token, - &solution.given_amount, - )?; - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - bytes_to_address(&solution.receiver)?, - permit, - signature.as_bytes().to_vec(), - swap_data, - ) - .abi_encode() - } else { - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - bytes_to_address(&solution.receiver)?, - !self.token_in_already_in_router, - swap_data, - ) - .abi_encode() - }; - - let contract_interaction = encode_input(&self.selector, method_calldata); - Ok((contract_interaction, self.router_address.clone())) + Ok(EncodedSolution { + selector: self.selector.clone(), + swaps: swap_data, + permit: None, + signature: None, + n_tokens: 0, + }) } fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box> { @@ -204,8 +156,6 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary -/// signatures and permit2 objects for calling the router /// * `selector`: String, the selector for the swap function in the router contract /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped token @@ -213,39 +163,32 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of /// sequential swap solutions /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers -/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - permit2: Option, selector: String, router_address: Bytes, native_address: Bytes, wrapped_address: Bytes, sequential_swap_validator: SequentialSwapValidator, transfer_optimization: TransferOptimization, - token_in_already_in_router: bool, } impl SequentialSwapStrategyEncoder { pub fn new( chain: Chain, swap_encoder_registry: SwapEncoderRegistry, - swapper_pk: Option, + permit_2_active: bool, router_address: Bytes, token_in_already_in_router: bool, ) -> Result { - let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { - (Some(Permit2::new(swapper_pk, chain.clone())?), "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) + let selector = if permit_2_active { + "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { - ( - None, - "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" - .to_string(), - ) - }; + "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" + + }.to_string(); Ok(Self { - permit2, selector, swap_encoder_registry, router_address: router_address.clone(), @@ -258,7 +201,6 @@ impl SequentialSwapStrategyEncoder { token_in_already_in_router, router_address, ), - token_in_already_in_router, }) } @@ -273,7 +215,7 @@ impl SequentialSwapStrategyEncoder { } impl StrategyEncoder for SequentialSwapStrategyEncoder { - fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { + fn encode_strategy(&self, solution: Solution) -> Result { self.sequential_swap_validator .validate_solution_min_amounts(&solution)?; self.sequential_swap_validator @@ -286,14 +228,12 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { &self.wrapped_address, )?; - let min_amount_out = get_min_amount_for_solution(solution.clone()); let grouped_swaps = group_swaps(solution.swaps); - let (mut unwrap, mut wrap) = (false, false); + let mut wrap = false; if let Some(action) = solution.native_action.clone() { - match action { - NativeAction::Wrap => wrap = true, - NativeAction::Unwrap => unwrap = true, + if action == NativeAction::Wrap { + wrap = true } } @@ -350,43 +290,13 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { } let encoded_swaps = ple_encode(swaps); - let method_calldata = if let Some(permit2) = self.permit2.clone() { - let (permit, signature) = permit2.get_permit( - &self.router_address, - &solution.sender, - &solution.given_token, - &solution.given_amount, - )?; - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - bytes_to_address(&solution.receiver)?, - permit, - signature.as_bytes().to_vec(), - encoded_swaps, - ) - .abi_encode() - } else { - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - bytes_to_address(&solution.receiver)?, - !self.token_in_already_in_router, - encoded_swaps, - ) - .abi_encode() - }; - - let contract_interaction = encode_input(&self.selector, method_calldata); - Ok((contract_interaction, self.router_address.clone())) + Ok(EncodedSolution { + selector: self.selector.clone(), + swaps: encoded_swaps, + permit: None, + signature: None, + n_tokens: 0, + }) } fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box> { @@ -403,8 +313,6 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary -/// signatures and permit2 objects for calling the router /// * `selector`: String, the selector for the swap function in the router contract /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped token @@ -412,39 +320,31 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// solutions /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers -/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - permit2: Option, selector: String, native_address: Bytes, wrapped_address: Bytes, split_swap_validator: SplitSwapValidator, router_address: Bytes, transfer_optimization: TransferOptimization, - token_in_already_in_router: bool, } impl SplitSwapStrategyEncoder { pub fn new( chain: Chain, swap_encoder_registry: SwapEncoderRegistry, - swapper_pk: Option, + permit_2_active: bool, router_address: Bytes, token_in_already_in_router: bool, ) -> Result { - let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { - (Some(Permit2::new(swapper_pk, chain.clone())?), "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) + let selector = if permit_2_active{ + "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { - ( - None, "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)" - .to_string(), - ) - }; + }.to_string(); Ok(Self { - permit2, selector, swap_encoder_registry, native_address: chain.native_token()?, @@ -457,7 +357,6 @@ impl SplitSwapStrategyEncoder { token_in_already_in_router, router_address, ), - token_in_already_in_router, }) } @@ -482,7 +381,7 @@ impl SplitSwapStrategyEncoder { } impl StrategyEncoder for SplitSwapStrategyEncoder { - fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { + fn encode_strategy(&self, solution: Solution) -> Result { self.split_swap_validator .validate_solution_min_amounts(&solution)?; self.split_swap_validator @@ -497,8 +396,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { &self.wrapped_address, )?; - let min_amount_out = get_min_amount_for_solution(solution.clone()); - // The tokens array is composed of the given token, the checked token and all the // intermediary tokens in between. The contract expects the tokens to be in this order. let solution_tokens: HashSet = @@ -597,45 +494,13 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { } else { tokens.len() }; - let method_calldata = if let Some(permit2) = self.permit2.clone() { - let (permit, signature) = permit2.get_permit( - &self.router_address, - &solution.sender, - &solution.given_token, - &solution.given_amount, - )?; - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - U256::from(tokens_len), - bytes_to_address(&solution.receiver)?, - permit, - signature.as_bytes().to_vec(), - encoded_swaps, - ) - .abi_encode() - } else { - ( - biguint_to_u256(&solution.given_amount), - bytes_to_address(&solution.given_token)?, - bytes_to_address(&solution.checked_token)?, - biguint_to_u256(&min_amount_out), - wrap, - unwrap, - U256::from(tokens_len), - bytes_to_address(&solution.receiver)?, - !self.token_in_already_in_router, - encoded_swaps, - ) - .abi_encode() - }; - - let contract_interaction = encode_input(&self.selector, method_calldata); - Ok((contract_interaction, self.router_address.clone())) + Ok(EncodedSolution { + selector: self.selector.clone(), + swaps: encoded_swaps, + permit: None, + signature: None, + n_tokens: tokens_len, + }) } fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box> { @@ -653,7 +518,7 @@ mod tests { use std::{collections::HashMap, str::FromStr}; use alloy::hex::encode; - use alloy_primitives::{hex, Address}; + use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, U256}; use num_bigint::{BigInt, BigUint}; use rstest::rstest; use tycho_common::{ @@ -662,7 +527,14 @@ mod tests { }; use super::*; - use crate::encoding::{evm::utils::write_calldata_to_file, models::Swap}; + use crate::encoding::{ + evm::{ + approvals::permit2::{Permit2, PermitSingle}, + encoding_utils::encode_tycho_router_call, + utils::write_calldata_to_file, + }, + models::Swap, + }; fn eth_chain() -> Chain { TychoCommonChain::Ethereum.into() @@ -682,7 +554,33 @@ mod tests { .unwrap() } + fn router_address() -> Bytes { + Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap() + } + + fn get_permit( + chain: Chain, + router_address: Bytes, + solution: &Solution, + ) -> (PermitSingle, Signature) { + // Set up a mock private key for signing (Alice's pk in our contract tests) + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + + let permit2 = Permit2::new(private_key, chain.clone()).unwrap(); + permit2 + .get_permit( + &router_address, + &solution.sender, + &solution.given_token, + &solution.given_amount, + ) + .unwrap() + } + mod single { + use alloy_sol_types::SolValue; + use super::*; #[rstest] #[case::with_check_no_slippage( @@ -712,10 +610,6 @@ mod tests { // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping // optimizations. - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -733,8 +627,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + true, + router_address(), false, ) .unwrap(); @@ -752,9 +646,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "30ace1b1", // Function selector @@ -823,8 +730,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -842,9 +749,18 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "5c4b639c", // Function selector @@ -906,8 +822,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), true, ) .unwrap(); @@ -925,9 +841,18 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + true, + router_address(), + eth(), + ) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "5c4b639c", // Function selector @@ -968,10 +893,6 @@ mod tests { // Note: This test does not assert anything. It is only used to obtain integration test // data for our router solidity test. - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let swap = Swap { @@ -988,8 +909,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), - Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), + true, + router_address(), false, ) .unwrap(); @@ -1007,10 +928,23 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_swap_strategy_encoder_wrap", hex_calldata.as_str()); } @@ -1021,10 +955,6 @@ mod tests { // Note: This test does not assert anything. It is only used to obtain integration test // data for our router solidity test. - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let swap = Swap { @@ -1041,7 +971,7 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -1060,10 +990,24 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file( "test_single_swap_strategy_encoder_unwrap", @@ -1084,10 +1028,6 @@ mod tests { // // WETH ───(USV2)──> WBTC ───(USV2)──> USDC - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = weth(); let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -1116,8 +1056,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + true, + router_address(), false, ) .unwrap(); @@ -1134,9 +1074,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_sequential_swap_strategy_encoder", hex_calldata.as_str()); @@ -1176,8 +1129,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -1194,10 +1147,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); let expected = String::from(concat!( @@ -1245,10 +1208,6 @@ mod tests { // The flow is: // USDC -> WETH -> USDC using two pools - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -1299,7 +1258,7 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -1312,8 +1271,7 @@ mod tests { checked_token: usdc.clone(), expected_amount: None, checked_amount: Some(BigUint::from_str("99389294").unwrap()), /* Expected output - * from - * test */ + * from test */ slippage: None, swaps: vec![swap_usdc_weth, swap_weth_usdc], sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -1321,9 +1279,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ "51bcc7b6", // selector @@ -1417,8 +1388,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -1436,10 +1407,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_uniswap_v2", hex_calldata.as_str()); } @@ -1499,8 +1480,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -1518,10 +1499,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_uniswap_v3", hex_calldata.as_str()); } @@ -1590,8 +1581,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -1609,10 +1600,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_curve", hex_calldata.as_str()); } @@ -1657,8 +1658,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -1676,10 +1677,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str()); } @@ -1700,11 +1711,6 @@ mod tests { let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); let dai = Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(); - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234" - .to_string(); - let usv2_swap_dai_weth = Swap { component: ProtocolComponent { id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), @@ -1805,8 +1811,8 @@ mod tests { let encoder = SequentialSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + true, + router_address(), false, ) .unwrap(); @@ -1814,7 +1820,7 @@ mod tests { exact_out: false, given_token: dai, given_amount: BigUint::from_str("1500_000000000000000000").unwrap(), - checked_token: eth, + checked_token: eth.clone(), expected_amount: None, checked_amount: Some(BigUint::from_str("732214216964381330").unwrap()), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -1830,9 +1836,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth, + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_multi_protocol", hex_calldata.as_str()); @@ -1855,10 +1874,6 @@ mod tests { // └──(USV2)──> DAI ───(USV2)──> USDC // - // Set up a mock private key for signing - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = weth(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); @@ -1911,7 +1926,7 @@ mod tests { let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -1929,9 +1944,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_split_swap_strategy_encoder", hex_calldata.as_str()); @@ -1947,10 +1975,6 @@ mod tests { // │ │ // └─ (USV3, 40% split) ──> WETH ─┘ - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -2021,7 +2045,7 @@ mod tests { let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key.clone()), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -2043,9 +2067,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ @@ -2114,10 +2151,6 @@ mod tests { // │ │ // └─── (USV3, 40% split) ───┘ - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -2184,7 +2217,7 @@ mod tests { let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key.clone()), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -2206,9 +2239,22 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ @@ -2310,7 +2356,7 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, + false, Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(), false, ) @@ -2331,10 +2377,19 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str()); } @@ -2360,7 +2415,7 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, + false, Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(), false, ) @@ -2381,10 +2436,19 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str()); } @@ -2397,10 +2461,6 @@ mod tests { // // ETH ───(USV4)──> PEPE // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let eth = eth(); let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); @@ -2426,7 +2486,7 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -2434,7 +2494,7 @@ mod tests { let solution = Solution { exact_out: false, - given_token: eth, + given_token: eth.clone(), given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: pepe, expected_amount: None, @@ -2446,9 +2506,17 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = + encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( @@ -2465,10 +2533,6 @@ mod tests { // // USDC ───(USV4)──> ETH // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let eth = eth(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -2498,7 +2562,7 @@ mod tests { let encoder = SplitSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -2508,7 +2572,7 @@ mod tests { exact_out: false, given_token: usdc, given_amount: BigUint::from_str("3000_000000").unwrap(), - checked_token: eth, + checked_token: eth.clone(), expected_amount: None, checked_amount: Some(BigUint::from_str("1117254495486192350").unwrap()), slippage: None, @@ -2518,9 +2582,17 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = + encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( @@ -2537,10 +2609,6 @@ mod tests { // USDC ──(USV4)──> ETH ───(USV4)──> PEPE // - // Set up a mock private key for signing (Alice's pk in our router tests) - let private_key = - "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let eth = eth(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let pepe = Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap(); @@ -2589,7 +2657,7 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - Some(private_key), + true, Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"), false, ) @@ -2608,9 +2676,17 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let mut encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let (permit, signature) = get_permit(eth_chain(), router_address(), &solution); + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + + let calldata = + encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) + .unwrap() + .data; let expected_input = [ "30ace1b1", // Function selector (single swap) @@ -2694,8 +2770,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -2715,10 +2791,20 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; + let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_curve", hex_calldata.as_str()); } @@ -2758,8 +2844,8 @@ mod tests { let encoder = SingleSwapStrategyEncoder::new( eth_chain(), swap_encoder_registry, - None, - Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + false, + router_address(), false, ) .unwrap(); @@ -2779,9 +2865,18 @@ mod tests { ..Default::default() }; - let (calldata, _) = encoder - .encode_strategy(solution) + let encoded_solution = encoder + .encode_strategy(solution.clone()) .unwrap(); + let calldata = encode_tycho_router_call( + encoded_solution, + &solution, + false, + router_address(), + eth(), + ) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 51d27f5..45dd497 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -6,14 +6,18 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, evm::{ + approvals::permit2::Permit2, constants::{GROUPABLE_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS}, + encoding_utils::encode_tycho_router_call, group_swaps::group_swaps, strategy_encoder::strategy_encoders::{ SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder, }, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, }, - models::{Chain, EncodingContext, NativeAction, Solution, Transaction, TransferType}, + models::{ + Chain, EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType, + }, strategy_encoder::StrategyEncoder, tycho_encoder::TychoEncoder, }; @@ -26,6 +30,8 @@ use crate::encoding::{ /// * `split_swap_strategy`: Encoder for split swaps /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped native token +/// * `router_address`: Address of the Tycho router contract +/// * `token_in_already_in_router`: Indicates if the token in is already in the router at swap time #[derive(Clone)] pub struct TychoRouterEncoder { single_swap_strategy: SingleSwapStrategyEncoder, @@ -33,6 +39,9 @@ pub struct TychoRouterEncoder { split_swap_strategy: SplitSwapStrategyEncoder, native_address: Bytes, wrapped_address: Bytes, + router_address: Bytes, + token_in_already_in_router: bool, + permit2: Option, } impl TychoRouterEncoder { @@ -45,78 +54,111 @@ impl TychoRouterEncoder { ) -> Result { let native_address = chain.native_token()?; let wrapped_address = chain.wrapped_token()?; + let permit2 = if let Some(swapper_pk) = swapper_pk.clone() { + Some(Permit2::new(swapper_pk, chain.clone())?) + } else { + None + }; Ok(TychoRouterEncoder { single_swap_strategy: SingleSwapStrategyEncoder::new( chain.clone(), swap_encoder_registry.clone(), - swapper_pk.clone(), + permit2.is_some(), router_address.clone(), token_in_already_in_router, )?, sequential_swap_strategy: SequentialSwapStrategyEncoder::new( chain.clone(), swap_encoder_registry.clone(), - swapper_pk.clone(), + permit2.is_some(), router_address.clone(), token_in_already_in_router, )?, split_swap_strategy: SplitSwapStrategyEncoder::new( chain, swap_encoder_registry, - None, + permit2.is_some(), router_address.clone(), token_in_already_in_router, )?, native_address, wrapped_address, + router_address, + token_in_already_in_router, + permit2, }) } + + fn encode_solution(&self, solution: &Solution) -> Result { + self.validate_solution(solution)?; + let protocols: HashSet = solution + .clone() + .swaps + .into_iter() + .map(|swap| swap.component.protocol_system) + .collect(); + + let mut encoded_solution = if (solution.swaps.len() == 1) || + (protocols.len() == 1 && + protocols + .iter() + .any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str()))) + { + self.single_swap_strategy + .encode_strategy(solution.clone())? + } else if solution + .swaps + .iter() + .all(|swap| swap.split == 0.0) + { + self.sequential_swap_strategy + .encode_strategy(solution.clone())? + } else { + self.split_swap_strategy + .encode_strategy(solution.clone())? + }; + + if let Some(permit2) = self.permit2.clone() { + let (permit, signature) = permit2.get_permit( + &self.router_address, + &solution.sender, + &solution.given_token, + &solution.given_amount, + )?; + encoded_solution.permit = Some(permit); + encoded_solution.signature = Some(signature); + } + Ok(encoded_solution) + } } impl TychoEncoder for TychoRouterEncoder { + fn encode_solutions( + &self, + solutions: Vec, + ) -> Result, EncodingError> { + let mut result: Vec = Vec::new(); + for solution in solutions.iter() { + let encoded_solution = self.encode_solution(solution)?; + result.push(encoded_solution); + } + Ok(result) + } + fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError> { let mut transactions: Vec = Vec::new(); for solution in solutions.iter() { - self.validate_solution(solution)?; + let encoded_solution = self.encode_solution(solution)?; - let protocols: HashSet = solution - .clone() - .swaps - .into_iter() - .map(|swap| swap.component.protocol_system) - .collect(); + let transaction = encode_tycho_router_call( + encoded_solution, + solution, + self.token_in_already_in_router, + self.router_address.clone(), + self.native_address.clone(), + )?; - let (contract_interaction, target_address) = if (solution.swaps.len() == 1) || - (protocols.len() == 1 && - protocols - .iter() - .any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str()))) - { - self.single_swap_strategy - .encode_strategy(solution.clone())? - } else if solution - .swaps - .iter() - .all(|swap| swap.split == 0.0) - { - self.sequential_swap_strategy - .encode_strategy(solution.clone())? - } else { - self.split_swap_strategy - .encode_strategy(solution.clone())? - }; - - let value = if solution.given_token == self.native_address { - solution.given_amount.clone() - } else { - BigUint::ZERO - }; - - transactions.push(Transaction { - value, - data: contract_interaction, - to: target_address, - }); + transactions.push(transaction); } Ok(transactions) } @@ -300,6 +342,14 @@ impl TychoExecutorEncoder { } impl TychoEncoder for TychoExecutorEncoder { + fn encode_solutions( + &self, + _solutions: Vec, + ) -> Result, EncodingError> { + Err(EncodingError::NotImplementedError( + "Encoding solutions for TychoExecutorEncoder is not implemented".to_string(), + )) + } fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError> { let mut transactions: Vec = Vec::new(); let solution = solutions diff --git a/src/encoding/evm/utils.rs b/src/encoding/evm/utils.rs index 20e821e..ab53b4d 100644 --- a/src/encoding/evm/utils.rs +++ b/src/encoding/evm/utils.rs @@ -10,7 +10,7 @@ use alloy::{ providers::{ProviderBuilder, RootProvider}, transports::BoxTransport, }; -use alloy_primitives::{aliases::U24, keccak256, Address, FixedBytes, Keccak256, U256, U8}; +use alloy_primitives::{aliases::U24, Address, U256, U8}; use alloy_sol_types::SolValue; use num_bigint::BigUint; use once_cell::sync::Lazy; @@ -40,28 +40,6 @@ pub fn biguint_to_u256(value: &BigUint) -> U256 { U256::from_be_slice(&bytes) } -/// Encodes the input data for a function call to the given function selector. -pub fn encode_input(selector: &str, mut encoded_args: Vec) -> Vec { - let mut hasher = Keccak256::new(); - hasher.update(selector.as_bytes()); - let selector_bytes = &hasher.finalize()[..4]; - let mut call_data = selector_bytes.to_vec(); - // Remove extra prefix if present (32 bytes for dynamic data) - // Alloy encoding is including a prefix for dynamic data indicating the offset or length - // but at this point we don't want that - if encoded_args.len() > 32 && - encoded_args[..32] == - [0u8; 31] - .into_iter() - .chain([32].to_vec()) - .collect::>() - { - encoded_args = encoded_args[32..].to_vec(); - } - call_data.extend(encoded_args); - call_data -} - /// Converts a decimal to a `U24` value. The percentage is a `f64` value between 0 and 1. /// MAX_UINT24 corresponds to 100%. pub fn percentage_to_uint24(decimal: f64) -> U24 { @@ -116,12 +94,6 @@ pub fn pad_to_fixed_size(input: &[u8]) -> Result<[u8; N], Encodi Ok(padded) } -/// Encodes a function selector to a fixed size array of 4 bytes. -pub fn encode_function_selector(selector: &str) -> FixedBytes<4> { - let hash = keccak256(selector.as_bytes()); - FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]]) -} - /// Extracts a static attribute from a swap. pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result, EncodingError> { Ok(swap diff --git a/src/encoding/models.rs b/src/encoding/models.rs index ef8ae1b..53406e2 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -1,3 +1,4 @@ +use alloy_primitives::PrimitiveSignature as Signature; use hex; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; @@ -8,6 +9,7 @@ use tycho_common::{ use crate::encoding::{ errors::EncodingError, + evm::approvals::permit2::PermitSingle, serde_primitives::{biguint_string, biguint_string_option}, }; @@ -96,6 +98,22 @@ pub struct Transaction { pub data: Vec, } +/// Represents a solution that has been encoded for execution. +/// +/// # Fields +/// * `swaps`: Encoded swaps to be executed. +/// * `selector`: The selector of the function to be called. +/// * `n_tokens`: Number of tokens in the swap. +/// * `permit`: Optional permit for the swap (if permit2 is enabled). +/// * `signature`: Optional signature for the swap (if permit2 is enabled). +pub struct EncodedSolution { + pub swaps: Vec, + pub selector: String, + pub n_tokens: usize, + pub permit: Option, + pub signature: Option, +} + /// Represents necessary attributes for encoding an order. /// /// # Fields diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 57630a0..f983507 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,6 +1,8 @@ -use tycho_common::Bytes; - -use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::SwapEncoder}; +use crate::encoding::{ + errors::EncodingError, + models::{EncodedSolution, Solution}, + swap_encoder::SwapEncoder, +}; /// A trait that defines how to encode a `Solution` for execution. pub trait StrategyEncoder { @@ -13,11 +15,8 @@ pub trait StrategyEncoder { /// path /// /// # Returns - /// * `Result<(Vec, Bytes, Option), EncodingError>` - A tuple containing: - /// - The encoded data as bytes - /// - The address of the contract to call (router or executor) - /// - Optionally, the function selector to use when calling the contract - fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError>; + /// * `Result` + fn encode_strategy(&self, solution: Solution) -> Result; /// Retrieves the swap encoder for a specific protocol system. /// diff --git a/src/encoding/tycho_encoder.rs b/src/encoding/tycho_encoder.rs index b724c39..7e0365a 100644 --- a/src/encoding/tycho_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -1,20 +1,75 @@ use crate::encoding::{ errors::EncodingError, - models::{Solution, Transaction}, + models::{EncodedSolution, Solution, Transaction}, }; -/// A high-level encoder that converts solutions into executable transactions. Allows for modularity -/// in the encoding process. +/// A high-level interface for encoding solutions into Tycho-compatible transactions or raw call +/// data. +/// +/// This trait is designed to abstract the encoding logic required to prepare swap transactions for +/// the Tycho Router. It enables modular and customizable construction of transactions, allowing +/// integrators to maintain full control over the execution constraints. +/// +/// # User Responsibility +/// +/// While this trait provides convenience methods, it is **strongly recommended** that users favor +/// [`encode_solutions`] over [`encode_calldata`]. This is because: +/// +/// - `encode_solutions` returns raw [`EncodedSolution`] objects, which include Tycho’s swap path +/// encoding, but leave **function argument encoding entirely in the user’s hands**. +/// - The function arguments to the router (e.g., `minAmountOut`, `receiver`, `unwrap`, `permit2`, +/// etc.) are used as **guardrails** to ensure safe on-chain execution. +/// - Automatically constructing full transactions via [`encode_calldata`] can obscure these +/// important safeguards and may result in unexpected behavior or vulnerability to MEV. +/// +/// Tycho is only responsible for generating the internal swap plan. **The user must encode the +/// outer function call arguments themselves** and verify that they enforce correct and secure +/// behavior. pub trait TychoEncoder { - /// Encodes solutions into transactions that can be executed by the Tycho router. + /// Encodes a list of [`Solution`]s into [`EncodedSolution`]s, which include the selector and + /// internal swap call data. /// - /// # Arguments - /// * `solutions` - Vector of solutions to encode, each potentially using different setups (swap - /// paths, protocols, wrapping, etc.) + /// This method gives users maximum flexibility and control. It **does not** produce full + /// transaction objects. Users are responsible for: + /// - Constructing the full calldata using their own encoding logic. + /// - Managing execution-critical parameters like `minAmountOut`. /// /// # Returns - /// * `Result, EncodingError>` - Vector of executable transactions + /// A vector of encoded solutions, each containing: + /// - The Tycho method selector + /// - The encoded swap path + /// - Additional metadata (e.g., permit2 information) + /// + /// # Recommendation + /// Use this method if you care about execution safety and want to avoid surprises. + fn encode_solutions( + &self, + solutions: Vec, + ) -> Result, EncodingError>; + + /// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router. + /// + /// This method wraps around Tycho’s example encoding logic (see [`encode_tycho_router_call`]) + /// and should only be used for **prototyping or development**. + /// + /// # Warning + /// This implementation uses default logic to construct the outer calldata (e.g., for setting + /// `minAmountOut`). This might not be optimal or safe for production use. + /// + /// To ensure correctness, **users should implement their own encoding pipeline** using + /// [`encode_solutions`]. + /// + /// # Returns + /// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler. fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError>; + /// Performs solution-level validation and sanity checks. + /// + /// This function can be used to verify whether a proposed solution is structurally sound and + /// ready for encoding. + /// + /// # Returns + /// - `Ok(())` if the solution is valid. + /// - `Err(EncodingError)` if the solution is malformed or unsupported. fn validate_solution(&self, solution: &Solution) -> Result<(), EncodingError>; } From 08056c4a6c153d5d02db846d8dbc5a3a0e3601df Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Wed, 21 May 2025 16:44:39 +0100 Subject: [PATCH 2/4] feat: Remove slippage and expected_amount from Solution The user is responsible for coming up with a sensible value for this themselves Took 37 minutes --- examples/encoding-example/main.rs | 2 +- src/encoding/evm/encoding_utils.rs | 5 +- .../evm/strategy_encoder/strategy_encoders.rs | 139 ++++-------------- .../strategy_encoder/strategy_validators.rs | 93 +----------- src/encoding/evm/tycho_encoders.rs | 20 +-- src/encoding/evm/utils.rs | 55 +------ src/encoding/models.rs | 14 +- 7 files changed, 45 insertions(+), 283 deletions(-) diff --git a/examples/encoding-example/main.rs b/examples/encoding-example/main.rs index 2209258..81685f9 100644 --- a/examples/encoding-example/main.rs +++ b/examples/encoding-example/main.rs @@ -55,7 +55,7 @@ fn main() { given_amount: BigUint::from_str("1_000000000000000000").expect("Failed to create amount"), checked_token: usdc.clone(), exact_out: false, // it's an exact in solution - checked_amount: Some(BigUint::from(1u64)), + checked_amount: BigUint::from(1u64), swaps: vec![simple_swap], ..Default::default() }; diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index c70a129..a6dae96 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -5,7 +5,7 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, - evm::utils::{biguint_to_u256, bytes_to_address, get_min_amount_for_solution}, + evm::utils::{biguint_to_u256, bytes_to_address}, models::{EncodedSolution, NativeAction, Solution, Transaction}, }; @@ -90,7 +90,6 @@ pub fn encode_tycho_router_call( router_address: Bytes, native_address: Bytes, ) -> Result { - let min_amount_out = get_min_amount_for_solution(solution.clone()); let (mut unwrap, mut wrap) = (false, false); if let Some(action) = solution.native_action.clone() { match action { @@ -100,7 +99,7 @@ pub fn encode_tycho_router_call( } let given_amount = biguint_to_u256(&solution.given_amount); - let min_amount_out = biguint_to_u256(&min_amount_out); + let min_amount_out = biguint_to_u256(&solution.checked_amount); let given_token = bytes_to_address(&solution.given_token)?; let checked_token = bytes_to_address(&solution.checked_token)?; let receiver = bytes_to_address(&solution.receiver)?; diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index bd487ab..087178b 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -216,8 +216,6 @@ impl SequentialSwapStrategyEncoder { impl StrategyEncoder for SequentialSwapStrategyEncoder { fn encode_strategy(&self, solution: Solution) -> Result { - self.sequential_swap_validator - .validate_solution_min_amounts(&solution)?; self.sequential_swap_validator .validate_swap_path( &solution.swaps, @@ -382,8 +380,6 @@ impl SplitSwapStrategyEncoder { impl StrategyEncoder for SplitSwapStrategyEncoder { fn encode_strategy(&self, solution: Solution) -> Result { - self.split_swap_validator - .validate_solution_min_amounts(&solution)?; self.split_swap_validator .validate_split_percentages(&solution.swaps)?; self.split_swap_validator @@ -520,7 +516,6 @@ mod tests { use alloy::hex::encode; use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, U256}; use num_bigint::{BigInt, BigUint}; - use rstest::rstest; use tycho_common::{ models::{protocol::ProtocolComponent, Chain as TychoCommonChain}, Bytes, @@ -582,34 +577,12 @@ mod tests { use alloy_sol_types::SolValue; use super::*; - #[rstest] - #[case::with_check_no_slippage( - None, - None, - Some(BigUint::from_str("2659881924818443699787").unwrap()), - U256::from_str("2659881924818443699787").unwrap(), - )] - #[case::no_check_with_slippage( - Some(BigUint::from_str("2_000_000000000000000000").unwrap()), - Some(0.01f64), - None, - U256::from_str("1_980_000000000000000000").unwrap(), - )] - #[case::with_check_and_slippage( - Some(BigUint::from_str("2_000_000000000000000000").unwrap()), - Some(0.01f64), - Some(BigUint::from_str("1_999_000000000000000000").unwrap()), - U256::from_str("1_999_000000000000000000").unwrap(), - )] - fn test_single_swap_strategy_encoder( - #[case] expected_amount: Option, - #[case] slippage: Option, - #[case] checked_amount: Option, - #[case] expected_min_amount: U256, - ) { + #[test] + fn test_single_swap_strategy_encoder() { // Performs a single swap from WETH to DAI on a USV2 pool, with no grouping // optimizations. - + let checked_amount = BigUint::from_str("2659881924818443699787").unwrap(); + let expected_min_amount = U256::from_str("2659881924818443699787").unwrap(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -637,8 +610,6 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: dai, - expected_amount: expected_amount.clone(), - slippage, checked_amount: checked_amount.clone(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -694,13 +665,7 @@ mod tests { assert_eq!(hex_calldata[..456], expected_input); assert_eq!(hex_calldata[1224..], expected_swap); - if expected_amount.is_some() & slippage.is_some() & checked_amount.is_none() { - // only write to file for 1 test case - write_calldata_to_file( - "test_single_swap_strategy_encoder", - &hex_calldata.to_string(), - ); - } + write_calldata_to_file("test_single_swap_strategy_encoder", &hex_calldata.to_string()); } #[test] @@ -711,9 +676,7 @@ mod tests { let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - let expected_amount = Some(BigUint::from_str("1_650_000000000000000000").unwrap()); - let slippage = Some(0.01f64); - let checked_amount = Some(BigUint::from_str("1_640_000000000000000000").unwrap()); + let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap(); let expected_min_amount = U256::from_str("1_640_000000000000000000").unwrap(); let swap = Swap { @@ -740,8 +703,6 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: dai, - expected_amount, - slippage, checked_amount, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -803,9 +764,7 @@ mod tests { let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - let expected_amount = Some(BigUint::from_str("1_650_000000000000000000").unwrap()); - let slippage = Some(0.01f64); - let checked_amount = Some(BigUint::from_str("1_640_000000000000000000").unwrap()); + let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap(); let expected_min_amount = U256::from_str("1_640_000000000000000000").unwrap(); let swap = Swap { @@ -832,8 +791,6 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: dai, - expected_amount, - slippage, checked_amount, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -919,13 +876,11 @@ mod tests { given_token: eth(), given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: dai, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1659881924818443699787").unwrap()), + checked_amount: BigUint::from_str("1659881924818443699787").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap], native_action: Some(NativeAction::Wrap), - ..Default::default() }; let mut encoded_solution = encoder @@ -981,13 +936,11 @@ mod tests { given_token: dai, given_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), checked_token: eth(), - expected_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), - checked_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), + checked_amount: BigUint::from_str("1_000000000000000000").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap], native_action: Some(NativeAction::Unwrap), - ..Default::default() }; let mut encoded_solution = encoder @@ -1066,8 +1019,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], @@ -1139,8 +1091,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_weth_wbtc, swap_wbtc_usdc], @@ -1269,10 +1220,8 @@ mod tests { given_token: usdc.clone(), given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99389294").unwrap()), /* Expected output - * from test */ - slippage: None, + checked_amount: BigUint::from_str("99389294").unwrap(), /* Expected output + * from test */ swaps: vec![swap_usdc_weth, swap_weth_usdc], sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -1398,8 +1347,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .unwrap(), @@ -1490,8 +1438,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .unwrap(), @@ -1591,8 +1538,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdt, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .unwrap(), @@ -1668,8 +1614,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .unwrap(), @@ -1821,8 +1766,7 @@ mod tests { given_token: dai, given_amount: BigUint::from_str("1500_000000000000000000").unwrap(), checked_token: eth.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("732214216964381330").unwrap()), + checked_amount: BigUint::from_str("732214216964381330").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .unwrap(), @@ -1936,8 +1880,7 @@ mod tests { given_token: weth, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: usdc, - expected_amount: None, - checked_amount: Some(BigUint::from_str("26173932").unwrap()), + checked_amount: BigUint::from_str("26173932").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc], @@ -2056,13 +1999,11 @@ mod tests { given_token: usdc.clone(), given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99574171").unwrap()), /* Expected output - * from - * test */ + checked_amount: BigUint::from_str("99574171").unwrap(), /* Expected output + * from + * test */ sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - slippage: None, swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2], ..Default::default() }; @@ -2228,13 +2169,11 @@ mod tests { given_token: usdc.clone(), given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals) checked_token: usdc.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("99025908").unwrap()), /* Expected output - * from - * test */ + checked_amount: BigUint::from_str("99025908").unwrap(), /* Expected output + * from + * test */ sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), - slippage: None, swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2], ..Default::default() }; @@ -2367,9 +2306,7 @@ mod tests { given_token: token_in, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1000").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("1000").unwrap(), // Alice sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -2426,9 +2363,7 @@ mod tests { given_token: token_in, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1000").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("1000").unwrap(), // Alice sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -2497,9 +2432,7 @@ mod tests { given_token: eth.clone(), given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: pepe, - expected_amount: None, - checked_amount: Some(BigUint::from_str("152373460199848577067005852").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("152373460199848577067005852").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_eth_pepe], @@ -2573,9 +2506,7 @@ mod tests { given_token: usdc, given_amount: BigUint::from_str("3000_000000").unwrap(), checked_token: eth.clone(), - expected_amount: None, - checked_amount: Some(BigUint::from_str("1117254495486192350").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("1117254495486192350").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_usdc_eth], @@ -2667,9 +2598,7 @@ mod tests { given_token: usdc, given_amount: BigUint::from_str("1000_000000").unwrap(), checked_token: pepe, - expected_amount: None, - checked_amount: Some(BigUint::from_str("97191013220606467325121599").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("97191013220606467325121599").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_usdc_eth, swap_eth_pepe], @@ -2781,9 +2710,7 @@ mod tests { given_token: token_in, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("1").unwrap(), // Alice sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -2855,9 +2782,7 @@ mod tests { given_token: token_in, given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: token_out, - expected_amount: None, - checked_amount: Some(BigUint::from_str("1").unwrap()), - slippage: None, + checked_amount: BigUint::from_str("1").unwrap(), // Alice sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), diff --git a/src/encoding/evm/strategy_encoder/strategy_validators.rs b/src/encoding/evm/strategy_encoder/strategy_validators.rs index 4ace21f..b34256e 100644 --- a/src/encoding/evm/strategy_encoder/strategy_validators.rs +++ b/src/encoding/evm/strategy_encoder/strategy_validators.rs @@ -4,23 +4,10 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, - models::{NativeAction, Solution, Swap}, + models::{NativeAction, Swap}, }; pub trait SwapValidator { - /// Raises an error if the solution does not have checked amount set or slippage with checked - /// amount set. - fn validate_solution_min_amounts(&self, solution: &Solution) -> Result<(), EncodingError> { - if solution.checked_amount.is_none() && - (solution.slippage.is_none() || solution.expected_amount.is_none()) - { - return Err(EncodingError::InvalidInput( - "Checked amount or slippage with expected amount must be provided".to_string(), - )) - } - Ok(()) - } - /// Raises an error if swaps do not represent a valid path from the given token to the checked /// token. /// @@ -207,8 +194,6 @@ impl SwapValidator for SequentialSwapValidator {} mod tests { use std::str::FromStr; - use num_bigint::BigUint; - use rstest::rstest; use tycho_common::{models::protocol::ProtocolComponent, Bytes}; use super::*; @@ -615,80 +600,4 @@ mod tests { ); assert_eq!(result, Ok(())); } - - #[rstest] - #[case::slippage_with_expected_amount_set( - Some(0.01), - Some(BigUint::from(1000u32)), - None, - Ok(()) - )] - #[case::min_amount_set( - None, - None, - Some(BigUint::from(1000u32)), - Ok(()) - )] - #[case::slippage_with_min_amount_set( - Some(0.01), - Some(BigUint::from(1000u32)), - Some(BigUint::from(1000u32)), - Ok(()) - )] - #[case::slippage_without_expected_amount_set( - Some(0.01), - None, - None, - Err( - EncodingError::InvalidInput( - "Checked amount or slippage with expected amount must be provided".to_string() - ) - ) - )] - #[case::none_set( - None, - None, - None, - Err( - EncodingError::InvalidInput( - "Checked amount or slippage with expected amount must be provided".to_string() - ) - ) - )] - fn test_validate_min_amount_passed( - #[case] slippage: Option, - #[case] expected_amount: Option, - #[case] min_amount: Option, - #[case] expected_result: Result<(), EncodingError>, - ) { - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); - let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - - let validator = SplitSwapValidator; - let swap = Swap { - component: ProtocolComponent { - id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), - protocol_system: "uniswap_v2".to_string(), - ..Default::default() - }, - token_in: weth.clone(), - token_out: usdc.clone(), - split: 0f64, - }; - - let solution = Solution { - exact_out: false, - given_token: weth, - checked_token: usdc, - slippage, - checked_amount: min_amount, - expected_amount, - swaps: vec![swap], - native_action: Some(NativeAction::Wrap), - ..Default::default() - }; - - let result = validator.validate_solution_min_amounts(&solution); - assert_eq!(result, expected_result); - } } diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 45dd497..8d67a01 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -529,9 +529,7 @@ mod tests { given_token: usdc(), given_amount: BigUint::from_str("1000_000000").unwrap(), checked_token: pepe(), - expected_amount: Some(BigUint::from_str("105_152_000000000000000000").unwrap()), - checked_amount: None, - slippage: None, + checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()], @@ -580,7 +578,7 @@ mod tests { swaps: vec![swap_weth_dai, swap_dai_usdc], receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), native_action: Some(NativeAction::Wrap), - checked_amount: Some(BigUint::from(1000u32)), + checked_amount: BigUint::from(1000u32), ..Default::default() }; @@ -629,7 +627,6 @@ mod tests { exact_out: false, given_token: eth(), checked_token: dai(), - checked_amount: None, swaps: vec![swap], native_action: Some(NativeAction::Wrap), ..Default::default() @@ -743,7 +740,6 @@ mod tests { let solution = Solution { exact_out: false, checked_token: eth(), - checked_amount: None, swaps: vec![swap], native_action: Some(NativeAction::Unwrap), ..Default::default() @@ -1083,14 +1079,12 @@ mod tests { exact_out: false, given_token: token_in, given_amount: BigUint::from(1000000000000000000u64), - expected_amount: Some(BigUint::from(1000000000000000000u64)), checked_token: token_out, - checked_amount: None, + checked_amount: BigUint::from(1000000000000000000u64), sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), // The receiver was generated with `makeAddr("bob") using forge` receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap], - slippage: None, native_action: None, }; @@ -1146,13 +1140,11 @@ mod tests { exact_out: false, given_token: token_in, given_amount: BigUint::from(1000000000000000000u64), - expected_amount: Some(BigUint::from(1000000000000000000u64)), checked_token: token_out, - checked_amount: None, + checked_amount: BigUint::from(1000000000000000000u64), sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap.clone(), swap], - slippage: None, native_action: None, }; @@ -1175,9 +1167,7 @@ mod tests { given_token: usdc, given_amount: BigUint::from_str("1000_000000").unwrap(), checked_token: pepe, - expected_amount: Some(BigUint::from_str("105_152_000000000000000000").unwrap()), - checked_amount: None, - slippage: None, + checked_amount: BigUint::from(1000000000000000000u64), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap_usdc_eth_univ4(), swap_eth_pepe_univ4()], diff --git a/src/encoding/evm/utils.rs b/src/encoding/evm/utils.rs index ab53b4d..df61ef4 100644 --- a/src/encoding/evm/utils.rs +++ b/src/encoding/evm/utils.rs @@ -1,5 +1,4 @@ use std::{ - cmp::max, env, fs::OpenOptions, io::{BufRead, BufReader, Write}, @@ -17,10 +16,7 @@ use once_cell::sync::Lazy; use tokio::runtime::{Handle, Runtime}; use tycho_common::Bytes; -use crate::encoding::{ - errors::EncodingError, - models::{Solution, Swap}, -}; +use crate::encoding::{errors::EncodingError, models::Swap}; /// Safely converts a `Bytes` object to an `Address` object. /// @@ -49,30 +45,6 @@ pub fn percentage_to_uint24(decimal: f64) -> U24 { U24::from(scaled.round()) } -/// Gets the minimum amount out for a solution to pass when executed on-chain. -/// -/// The minimum amount is calculated based on the expected amount and the slippage percentage, if -/// passed. If this information is not passed, the user-passed checked amount will be used. -/// If both the slippage and minimum user-passed checked amount are passed, the maximum of the two -/// will be used. -/// If neither are passed, the minimum amount will be zero. -pub fn get_min_amount_for_solution(solution: Solution) -> BigUint { - let mut min_amount_out = solution - .checked_amount - .unwrap_or(BigUint::ZERO); - - if let (Some(expected_amount), Some(slippage)) = - (solution.expected_amount.as_ref(), solution.slippage) - { - let bps = BigUint::from(10_000u32); - let slippage_percent = BigUint::from((slippage * 10000.0) as u32); - let multiplier = &bps - slippage_percent; - let expected_amount_with_slippage = (expected_amount * &multiplier) / &bps; - min_amount_out = max(min_amount_out, expected_amount_with_slippage); - } - min_amount_out -} - /// Gets the position of a token in a list of tokens. pub fn get_token_position(tokens: Vec, token: Bytes) -> Result { let position = U8::from( @@ -187,28 +159,3 @@ pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) { writeln!(file, "{line}").expect("Failed to write calldata"); } } - -#[cfg(test)] -mod tests { - use num_bigint::BigUint; - - use super::*; - use crate::encoding::models::Solution; - - #[test] - fn test_min_amount_out_small_slippage() { - // Tests that the calculation's precision is high enough to support a slippage of 0.1%. - - let solution = Solution { - exact_out: false, - given_amount: BigUint::from(1000000000000000000u64), - checked_amount: None, - slippage: Some(0.001f64), - expected_amount: Some(BigUint::from(1000000000000000000u64)), - ..Default::default() - }; - - let min_amount_out = get_min_amount_for_solution(solution); - assert_eq!(min_amount_out, BigUint::from(999000000000000000u64)); - } -} diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 53406e2..444e1aa 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -8,9 +8,7 @@ use tycho_common::{ }; use crate::encoding::{ - errors::EncodingError, - evm::approvals::permit2::PermitSingle, - serde_primitives::{biguint_string, biguint_string_option}, + errors::EncodingError, evm::approvals::permit2::PermitSingle, serde_primitives::biguint_string, }; /// Represents a solution containing details describing an order, and instructions for filling @@ -32,15 +30,9 @@ pub struct Solution { /// supported. #[serde(default)] pub exact_out: bool, - /// If set, it will be applied to expected_amount - pub slippage: Option, - /// Expected amount of the bought token (exact in) or sold token (exact out). - #[serde(with = "biguint_string_option")] - pub expected_amount: Option, /// Minimum amount to be checked for the solution to be valid. - /// If not set, the check will not be performed. - #[serde(with = "biguint_string_option")] - pub checked_amount: Option, + #[serde(with = "biguint_string")] + pub checked_amount: BigUint, /// List of swaps to fulfill the solution. pub swaps: Vec, /// If set, the corresponding native action will be executed. From facdf716bdb972013292918247583a4a74ae0bca Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Wed, 21 May 2025 18:00:19 +0100 Subject: [PATCH 3/4] feat: Add interacting_with to EncodedSolution - Remove encode_full_calldata from the TychoEncoder trait - Make the TychoExecutorEncoder use encode_solutions instead of encode_full_calldata - Update tycho-encode.rs accordingly Took 1 hour 3 minutes Took 12 seconds --- examples/encoding-example/main.rs | 24 +- src/bin/tycho-encode.rs | 18 +- src/encoding/evm/encoder_builders.rs | 2 +- src/encoding/evm/encoding_utils.rs | 3 +- .../evm/strategy_encoder/strategy_encoders.rs | 264 +++++------------- src/encoding/evm/tycho_encoders.rs | 141 +++++----- src/encoding/models.rs | 3 + src/encoding/tycho_encoder.rs | 18 +- 8 files changed, 174 insertions(+), 299 deletions(-) diff --git a/examples/encoding-example/main.rs b/examples/encoding-example/main.rs index 81685f9..b489310 100644 --- a/examples/encoding-example/main.rs +++ b/examples/encoding-example/main.rs @@ -61,17 +61,17 @@ fn main() { }; // Encode the solution - let tx = encoder - .encode_calldata(vec![solution.clone()]) + let encoded_solution = encoder + .encode_solutions(vec![solution.clone()]) .expect("Failed to encode router calldata")[0] .clone(); println!(" ====== Simple swap WETH -> USDC ======"); println!( - "The simple swap encoded transaction should be sent to address {:?} with the value of {:?} and the \ + "The simple swap encoded solution should be sent to address {:?} and selector {:?} and the \ following encoded data: {:?}", - tx.to, - tx.value, - hex::encode(tx.data) + encoded_solution.interacting_with, + encoded_solution.selector, + hex::encode(encoded_solution.swaps) ); // ------------------- Encode a swap with multiple splits ------------------- @@ -134,17 +134,17 @@ fn main() { complex_solution.swaps = vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc]; // Encode the solution - let complex_tx = encoder - .encode_calldata(vec![complex_solution]) + let complex_encoded_solution = encoder + .encode_solutions(vec![complex_solution]) .expect("Failed to encode router calldata")[0] .clone(); println!(" ====== Complex split swap WETH -> USDC ======"); println!( - "The complex solution encoded transaction should be sent to address {:?} with the value of {:?} and the \ + "The complex swaps encoded solution should be sent to address {:?} and selector {:?} and the \ following encoded data: {:?}", - complex_tx.to, - complex_tx.value, - hex::encode(complex_tx.data) + complex_encoded_solution.interacting_with, + complex_encoded_solution.selector, + hex::encode(complex_encoded_solution.swaps) ); } diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index a1fcf00..fa21fbd 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -1,5 +1,6 @@ use std::io::{self, Read}; +use alloy_sol_types::SolValue; use clap::{Parser, Subcommand}; use tycho_common::{hex_bytes::Bytes, models::Chain}; use tycho_execution::encoding::{ @@ -99,11 +100,20 @@ fn main() -> Result<(), Box> { .build()?, }; - let transactions = encoder.encode_calldata(vec![solution])?; + let encoded_solutions = encoder.encode_solutions(vec![solution])?; let encoded = serde_json::json!({ - "to": format!("0x{}", hex::encode(&transactions[0].to)), - "value": format!("0x{}", hex::encode(transactions[0].value.to_bytes_be())), - "data": format!("0x{}", hex::encode(&transactions[0].data)), + "swaps": format!("0x{}", hex::encode(&encoded_solutions[0].swaps)), + "interacting_with": format!("0x{}", hex::encode(&encoded_solutions[0].interacting_with)), + "selector": format!("{}",&encoded_solutions[0].selector), + "n_tokens": format!("{}", &encoded_solutions[0].n_tokens), + "permit": encoded_solutions[0].permit + .as_ref() + .map(|permit| format!("0x{}", hex::encode(permit.abi_encode()))) + .unwrap_or_else(String::new), + "signature": encoded_solutions[0].signature + .as_ref() + .map(|signature| format!("0x{}", hex::encode(signature.as_bytes()))) + .unwrap_or_else(String::new), }); // Output the encoded result as JSON to stdout println!( diff --git a/src/encoding/evm/encoder_builders.rs b/src/encoding/evm/encoder_builders.rs index ee412ac..590605b 100644 --- a/src/encoding/evm/encoder_builders.rs +++ b/src/encoding/evm/encoder_builders.rs @@ -145,7 +145,7 @@ impl TychoExecutorEncoderBuilder { if let Some(chain) = self.chain { let swap_encoder_registry = SwapEncoderRegistry::new(self.executors_file_path.clone(), chain.clone())?; - Ok(Box::new(TychoExecutorEncoder::new(chain, swap_encoder_registry)?)) + Ok(Box::new(TychoExecutorEncoder::new(swap_encoder_registry)?)) } else { Err(EncodingError::FatalError( "Please set the chain and strategy before building the encoder".to_string(), diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index a6dae96..64f973a 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -87,7 +87,6 @@ pub fn encode_tycho_router_call( encoded_solution: EncodedSolution, solution: &Solution, token_in_already_in_router: bool, - router_address: Bytes, native_address: Bytes, ) -> Result { let (mut unwrap, mut wrap) = (false, false); @@ -246,5 +245,5 @@ pub fn encode_tycho_router_call( } else { BigUint::ZERO }; - Ok(Transaction { to: router_address, value, data: contract_interaction }) + Ok(Transaction { to: encoded_solution.interacting_with, value, data: contract_interaction }) } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 087178b..7463ae9 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -135,6 +135,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { ); Ok(EncodedSolution { selector: self.selector.clone(), + interacting_with: self.router_address.clone(), swaps: swap_data, permit: None, signature: None, @@ -289,6 +290,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let encoded_swaps = ple_encode(swaps); Ok(EncodedSolution { + interacting_with: self.router_address.clone(), selector: self.selector.clone(), swaps: encoded_swaps, permit: None, @@ -491,6 +493,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { tokens.len() }; Ok(EncodedSolution { + interacting_with: self.router_address.clone(), selector: self.selector.clone(), swaps: encoded_swaps, permit: None, @@ -624,15 +627,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "30ace1b1", // Function selector @@ -713,15 +710,9 @@ mod tests { let encoded_solution = encoder .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "5c4b639c", // Function selector @@ -801,15 +792,9 @@ mod tests { let encoded_solution = encoder .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - true, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, true, eth()) + .unwrap() + .data; let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "5c4b639c", // Function selector @@ -891,15 +876,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_swap_strategy_encoder_wrap", hex_calldata.as_str()); } @@ -951,15 +930,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( @@ -1033,15 +1006,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_sequential_swap_strategy_encoder", hex_calldata.as_str()); @@ -1102,15 +1069,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); @@ -1235,15 +1196,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ "51bcc7b6", // selector @@ -1359,15 +1314,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_uniswap_v2", hex_calldata.as_str()); @@ -1450,15 +1399,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_uniswap_v3", hex_calldata.as_str()); @@ -1550,15 +1493,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_uniswap_v3_curve", hex_calldata.as_str()); @@ -1626,15 +1563,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str()); @@ -1787,15 +1718,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth, - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_multi_protocol", hex_calldata.as_str()); @@ -1894,15 +1819,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_split_swap_strategy_encoder", hex_calldata.as_str()); @@ -2015,15 +1934,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ @@ -2185,15 +2098,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = hex::encode(&calldata); let expected_input = [ @@ -2318,15 +2225,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str()); } @@ -2375,15 +2276,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str()); } @@ -2446,10 +2341,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = - encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( @@ -2520,10 +2414,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = - encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( @@ -2612,10 +2505,9 @@ mod tests { encoded_solution.permit = Some(permit); encoded_solution.signature = Some(signature); - let calldata = - encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth) + .unwrap() + .data; let expected_input = [ "30ace1b1", // Function selector (single swap) @@ -2722,15 +2614,9 @@ mod tests { .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file("test_single_encoding_strategy_curve", hex_calldata.as_str()); @@ -2793,15 +2679,9 @@ mod tests { let encoded_solution = encoder .encode_strategy(solution.clone()) .unwrap(); - let calldata = encode_tycho_router_call( - encoded_solution, - &solution, - false, - router_address(), - eth(), - ) - .unwrap() - .data; + let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth()) + .unwrap() + .data; let hex_calldata = encode(&calldata); write_calldata_to_file( diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 8d67a01..0d66c77 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -1,6 +1,5 @@ use std::{collections::HashSet, str::FromStr}; -use num_bigint::BigUint; use tycho_common::Bytes; use crate::encoding::{ @@ -40,6 +39,7 @@ pub struct TychoRouterEncoder { native_address: Bytes, wrapped_address: Bytes, router_address: Bytes, + #[allow(dead_code)] token_in_already_in_router: bool, permit2: Option, } @@ -130,6 +130,41 @@ impl TychoRouterEncoder { } Ok(encoded_solution) } + + /// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router. + /// + /// This method wraps around Tycho’s example encoding logic (see [`encode_tycho_router_call`]) + /// and should only be used for **prototyping or development**. + /// + /// # Warning + /// This implementation uses default logic to construct the outer calldata (e.g., for setting + /// `minAmountOut`). This might not be optimal or safe for production use. + /// + /// To ensure correctness, **users should implement their own encoding pipeline** using + /// [`encode_solutions`]. + /// + /// # Returns + /// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler. + #[allow(dead_code)] + fn encode_full_calldata( + &self, + solutions: Vec, + ) -> Result, EncodingError> { + let mut transactions: Vec = Vec::new(); + for solution in solutions.iter() { + let encoded_solution = self.encode_solution(solution)?; + + let transaction = encode_tycho_router_call( + encoded_solution, + solution, + self.token_in_already_in_router, + self.native_address.clone(), + )?; + + transactions.push(transaction); + } + Ok(transactions) + } } impl TychoEncoder for TychoRouterEncoder { @@ -145,24 +180,6 @@ impl TychoEncoder for TychoRouterEncoder { Ok(result) } - fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError> { - let mut transactions: Vec = Vec::new(); - for solution in solutions.iter() { - let encoded_solution = self.encode_solution(solution)?; - - let transaction = encode_tycho_router_call( - encoded_solution, - solution, - self.token_in_already_in_router, - self.router_address.clone(), - self.native_address.clone(), - )?; - - transactions.push(transaction); - } - Ok(transactions) - } - /// Raises an `EncodingError` if the solution is not considered valid. /// /// A solution is considered valid if all the following conditions are met: @@ -273,22 +290,17 @@ impl TychoEncoder for TychoRouterEncoder { #[derive(Clone)] pub struct TychoExecutorEncoder { swap_encoder_registry: SwapEncoderRegistry, - native_address: Bytes, } impl TychoExecutorEncoder { - pub fn new( - chain: Chain, - swap_encoder_registry: SwapEncoderRegistry, - ) -> Result { - let native_address = chain.native_token()?; - Ok(TychoExecutorEncoder { swap_encoder_registry, native_address }) + pub fn new(swap_encoder_registry: SwapEncoderRegistry) -> Result { + Ok(TychoExecutorEncoder { swap_encoder_registry }) } fn encode_executor_calldata( &self, solution: Solution, - ) -> Result<(Vec, Bytes), EncodingError> { + ) -> Result { let grouped_swaps = group_swaps(solution.clone().swaps); let number_of_groups = grouped_swaps.len(); if number_of_groups > 1 { @@ -337,37 +349,30 @@ impl TychoExecutorEncoder { let executor_address = Bytes::from_str(swap_encoder.executor_address()) .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; - Ok((grouped_protocol_data, executor_address)) + Ok(EncodedSolution { + swaps: grouped_protocol_data, + interacting_with: executor_address, + permit: None, + signature: None, + selector: "".to_string(), + n_tokens: 0, + }) } } impl TychoEncoder for TychoExecutorEncoder { fn encode_solutions( &self, - _solutions: Vec, + solutions: Vec, ) -> Result, EncodingError> { - Err(EncodingError::NotImplementedError( - "Encoding solutions for TychoExecutorEncoder is not implemented".to_string(), - )) - } - fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError> { - let mut transactions: Vec = Vec::new(); let solution = solutions .first() .ok_or(EncodingError::FatalError("No solutions found".to_string()))?; self.validate_solution(solution)?; - let (contract_interaction, target_address) = - self.encode_executor_calldata(solution.clone())?; + let encoded_solution = self.encode_executor_calldata(solution.clone())?; - let value = if solution.given_token == self.native_address { - solution.given_amount.clone() - } else { - BigUint::ZERO - }; - - transactions.push(Transaction { value, data: contract_interaction, to: target_address }); - Ok(transactions) + Ok(vec![encoded_solution]) } /// Raises an `EncodingError` if the solution is not considered valid. @@ -388,7 +393,7 @@ impl TychoEncoder for TychoExecutorEncoder { mod tests { use std::{collections::HashMap, str::FromStr}; - use num_bigint::BigInt; + use num_bigint::{BigInt, BigUint}; use tycho_common::models::{protocol::ProtocolComponent, Chain as TychoCommonChain}; use super::*; @@ -508,7 +513,7 @@ mod tests { ..Default::default() }; - let transactions = encoder.encode_calldata(vec![solution]); + let transactions = encoder.encode_full_calldata(vec![solution]); assert!(transactions.is_ok()); let transactions = transactions.unwrap(); assert_eq!(transactions.len(), 1); @@ -536,7 +541,7 @@ mod tests { ..Default::default() }; - let transactions = encoder.encode_calldata(vec![solution]); + let transactions = encoder.encode_full_calldata(vec![solution]); assert!(transactions.is_ok()); let transactions = transactions.unwrap(); assert_eq!(transactions.len(), 1); @@ -582,7 +587,7 @@ mod tests { ..Default::default() }; - let transactions = encoder.encode_calldata(vec![solution]); + let transactions = encoder.encode_full_calldata(vec![solution]); assert!(transactions.is_ok()); let transactions = transactions.unwrap(); assert_eq!(transactions.len(), 1); @@ -1057,9 +1062,7 @@ mod tests { #[test] fn test_executor_encoder_encode() { let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = - TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry) - .unwrap(); + let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap(); let token_in = weth(); let token_out = dai(); @@ -1088,15 +1091,15 @@ mod tests { native_action: None, }; - let transactions = encoder - .encode_calldata(vec![solution]) + let encoded_solutions = encoder + .encode_solutions(vec![solution]) .unwrap(); - let transaction = transactions + let encoded = encoded_solutions .first() - .expect("Expected at least one transaction"); - let hex_protocol_data = encode(&transaction.data); + .expect("Expected at least one encoded solution"); + let hex_protocol_data = encode(&encoded.swaps); assert_eq!( - transaction.to, + encoded.interacting_with, Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap() ); assert_eq!( @@ -1119,9 +1122,7 @@ mod tests { #[test] fn test_executor_encoder_too_many_swaps() { let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = - TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry) - .unwrap(); + let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap(); let token_in = weth(); let token_out = dai(); @@ -1148,16 +1149,14 @@ mod tests { native_action: None, }; - let result = encoder.encode_calldata(vec![solution]); + let result = encoder.encode_solutions(vec![solution]); assert!(result.is_err()); } #[test] fn test_executor_encoder_grouped_swaps() { let swap_encoder_registry = get_swap_encoder_registry(); - let encoder = - TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry) - .unwrap(); + let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap(); let usdc = usdc(); let pepe = pepe(); @@ -1174,15 +1173,15 @@ mod tests { ..Default::default() }; - let transactions = encoder - .encode_calldata(vec![solution]) + let encoded_solutions = encoder + .encode_solutions(vec![solution]) .unwrap(); - let transaction = transactions + let encoded_solution = encoded_solutions .first() - .expect("Expected at least one transaction"); - let hex_protocol_data = encode(&transaction.data); + .expect("Expected at least one encoded solution"); + let hex_protocol_data = encode(&encoded_solution.swaps); assert_eq!( - transaction.to, + encoded_solution.interacting_with, Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap() ); assert_eq!( diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 444e1aa..1c95979 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -94,12 +94,15 @@ pub struct Transaction { /// /// # Fields /// * `swaps`: Encoded swaps to be executed. +/// * `interacting_with`: Address of the contract to be called. /// * `selector`: The selector of the function to be called. /// * `n_tokens`: Number of tokens in the swap. /// * `permit`: Optional permit for the swap (if permit2 is enabled). /// * `signature`: Optional signature for the swap (if permit2 is enabled). +#[derive(Clone, Debug)] pub struct EncodedSolution { pub swaps: Vec, + pub interacting_with: Bytes, pub selector: String, pub n_tokens: usize, pub permit: Option, diff --git a/src/encoding/tycho_encoder.rs b/src/encoding/tycho_encoder.rs index 7e0365a..49c2334 100644 --- a/src/encoding/tycho_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -1,6 +1,6 @@ use crate::encoding::{ errors::EncodingError, - models::{EncodedSolution, Solution, Transaction}, + models::{EncodedSolution, Solution}, }; /// A high-level interface for encoding solutions into Tycho-compatible transactions or raw call @@ -47,22 +47,6 @@ pub trait TychoEncoder { solutions: Vec, ) -> Result, EncodingError>; - /// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router. - /// - /// This method wraps around Tycho’s example encoding logic (see [`encode_tycho_router_call`]) - /// and should only be used for **prototyping or development**. - /// - /// # Warning - /// This implementation uses default logic to construct the outer calldata (e.g., for setting - /// `minAmountOut`). This might not be optimal or safe for production use. - /// - /// To ensure correctness, **users should implement their own encoding pipeline** using - /// [`encode_solutions`]. - /// - /// # Returns - /// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler. - fn encode_calldata(&self, solutions: Vec) -> Result, EncodingError>; - /// Performs solution-level validation and sanity checks. /// /// This function can be used to verify whether a proposed solution is structurally sound and From 5aff28e34589c1b3db78b3c3b56a789eeadd71cb Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 22 May 2025 12:51:35 +0100 Subject: [PATCH 4/4] feat: Add encode_full_calldata to TychoEncoder trait This is so that people can easily use it without downcasting the encoder Took 32 minutes --- src/encoding/evm/tycho_encoders.rs | 53 +++++++++++++----------------- src/encoding/tycho_encoder.rs | 21 +++++++++++- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 0d66c77..f22f59d 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -39,7 +39,6 @@ pub struct TychoRouterEncoder { native_address: Bytes, wrapped_address: Bytes, router_address: Bytes, - #[allow(dead_code)] token_in_already_in_router: bool, permit2: Option, } @@ -130,22 +129,21 @@ impl TychoRouterEncoder { } Ok(encoded_solution) } +} + +impl TychoEncoder for TychoRouterEncoder { + fn encode_solutions( + &self, + solutions: Vec, + ) -> Result, EncodingError> { + let mut result: Vec = Vec::new(); + for solution in solutions.iter() { + let encoded_solution = self.encode_solution(solution)?; + result.push(encoded_solution); + } + Ok(result) + } - /// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router. - /// - /// This method wraps around Tycho’s example encoding logic (see [`encode_tycho_router_call`]) - /// and should only be used for **prototyping or development**. - /// - /// # Warning - /// This implementation uses default logic to construct the outer calldata (e.g., for setting - /// `minAmountOut`). This might not be optimal or safe for production use. - /// - /// To ensure correctness, **users should implement their own encoding pipeline** using - /// [`encode_solutions`]. - /// - /// # Returns - /// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler. - #[allow(dead_code)] fn encode_full_calldata( &self, solutions: Vec, @@ -165,20 +163,6 @@ impl TychoRouterEncoder { } Ok(transactions) } -} - -impl TychoEncoder for TychoRouterEncoder { - fn encode_solutions( - &self, - solutions: Vec, - ) -> Result, EncodingError> { - let mut result: Vec = Vec::new(); - for solution in solutions.iter() { - let encoded_solution = self.encode_solution(solution)?; - result.push(encoded_solution); - } - Ok(result) - } /// Raises an `EncodingError` if the solution is not considered valid. /// @@ -375,6 +359,15 @@ impl TychoEncoder for TychoExecutorEncoder { Ok(vec![encoded_solution]) } + fn encode_full_calldata( + &self, + _solutions: Vec, + ) -> Result, EncodingError> { + Err(EncodingError::NotImplementedError( + "Full calldata encoding is not supported for TychoExecutorEncoder".to_string(), + )) + } + /// Raises an `EncodingError` if the solution is not considered valid. /// /// A solution is considered valid if all the following conditions are met: diff --git a/src/encoding/tycho_encoder.rs b/src/encoding/tycho_encoder.rs index 49c2334..f66b1d4 100644 --- a/src/encoding/tycho_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -1,6 +1,6 @@ use crate::encoding::{ errors::EncodingError, - models::{EncodedSolution, Solution}, + models::{EncodedSolution, Solution, Transaction}, }; /// A high-level interface for encoding solutions into Tycho-compatible transactions or raw call @@ -47,6 +47,25 @@ pub trait TychoEncoder { solutions: Vec, ) -> Result, EncodingError>; + /// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router. + /// + /// This method wraps around Tycho’s example encoding logic (see [`encode_tycho_router_call`]) + /// and should only be used for **prototyping or development**. + /// + /// # Warning + /// This implementation uses default logic to construct the outer calldata (e.g., for setting + /// `minAmountOut`). This might not be optimal or safe for production use. + /// + /// To ensure correctness, **users should implement their own encoding pipeline** using + /// [`encode_solutions`]. + /// + /// # Returns + /// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler. + fn encode_full_calldata( + &self, + solutions: Vec, + ) -> Result, EncodingError>; + /// Performs solution-level validation and sanity checks. /// /// This function can be used to verify whether a proposed solution is structurally sound and