From b047d9762ec8afea004c2fbd7057d42611b4d40f Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 31 Jan 2025 12:41:37 +0000 Subject: [PATCH 1/7] test: Add test for tycho encoder Rename RouterEncoder to TychoEncoder --- don't change below this line --- ENG-4081 Took 21 minutes --- src/encoding/errors.rs | 2 +- src/encoding/evm/mod.rs | 2 +- src/encoding/evm/router_encoder.rs | 73 -------- src/encoding/evm/tycho_encoder.rs | 163 ++++++++++++++++++ src/encoding/mod.rs | 2 +- .../{router_encoder.rs => tycho_encoder.rs} | 2 +- 6 files changed, 167 insertions(+), 77 deletions(-) delete mode 100644 src/encoding/evm/router_encoder.rs create mode 100644 src/encoding/evm/tycho_encoder.rs rename src/encoding/{router_encoder.rs => tycho_encoder.rs} (85%) diff --git a/src/encoding/errors.rs b/src/encoding/errors.rs index f4aa617..6581c6c 100644 --- a/src/encoding/errors.rs +++ b/src/encoding/errors.rs @@ -12,7 +12,7 @@ use thiserror::Error; /// - `RecoverableError`: Indicates that the encoding has failed with a recoverable error. Retrying /// at a later time may succeed. It may have failed due to a temporary issue, such as a network /// problem. -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] #[allow(dead_code)] pub enum EncodingError { #[error("Invalid input: {0}")] diff --git a/src/encoding/evm/mod.rs b/src/encoding/evm/mod.rs index 031dbc4..bffb498 100644 --- a/src/encoding/evm/mod.rs +++ b/src/encoding/evm/mod.rs @@ -1,6 +1,6 @@ pub mod approvals; mod models; -mod router_encoder; mod strategy_encoder; mod swap_encoder; +mod tycho_encoder; mod utils; diff --git a/src/encoding/evm/router_encoder.rs b/src/encoding/evm/router_encoder.rs deleted file mode 100644 index 896f707..0000000 --- a/src/encoding/evm/router_encoder.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::str::FromStr; - -use num_bigint::BigUint; -use tycho_core::{models::Chain, Bytes}; - -use crate::encoding::{ - errors::EncodingError, - models::{NativeAction, Solution, Transaction}, - router_encoder::RouterEncoder, - strategy_encoder::StrategySelector, -}; - -#[allow(dead_code)] -pub struct EVMRouterEncoder { - strategy_selector: S, - signer: Option, - chain: Chain, - router_address: Bytes, -} - -#[allow(dead_code)] -impl EVMRouterEncoder { - pub fn new( - strategy_selector: S, - router_address: String, - signer: Option, - chain: Chain, - ) -> Result { - let router_address = Bytes::from_str(&router_address) - .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; - Ok(EVMRouterEncoder { strategy_selector, signer, chain, router_address }) - } -} -impl RouterEncoder for EVMRouterEncoder { - fn encode_router_calldata( - &self, - solutions: Vec, - ) -> Result, EncodingError> { - let mut transactions: Vec = Vec::new(); - for solution in solutions.iter() { - if solution.exact_out { - return Err(EncodingError::FatalError( - "Currently only exact input solutions are supported".to_string(), - )); - } - - let router_address = solution - .router_address - .clone() - .unwrap_or(self.router_address.clone()); - let strategy = self.strategy_selector.select_strategy( - solution, - self.signer.clone(), - self.chain, - )?; - - let (contract_interaction, target_address) = - strategy.encode_strategy(solution.clone(), router_address)?; - - let value = match solution.native_action.as_ref() { - Some(NativeAction::Wrap) => solution.given_amount.clone(), - _ => BigUint::ZERO, - }; - - transactions.push(Transaction { - value, - data: contract_interaction, - to: target_address, - }); - } - Ok(transactions) - } -} diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs new file mode 100644 index 0000000..5be718d --- /dev/null +++ b/src/encoding/evm/tycho_encoder.rs @@ -0,0 +1,163 @@ +use std::str::FromStr; + +use num_bigint::BigUint; +use tycho_core::{models::Chain, Bytes}; + +use crate::encoding::{ + errors::EncodingError, + models::{NativeAction, Solution, Transaction}, + strategy_encoder::StrategySelector, + tycho_encoder::TychoEncoder, +}; + +#[allow(dead_code)] +pub struct EVMTychoEncoder { + strategy_selector: S, + signer: Option, + chain: Chain, + router_address: Bytes, +} + +#[allow(dead_code)] +impl EVMTychoEncoder { + pub fn new( + strategy_selector: S, + router_address: String, + signer: Option, + chain: Chain, + ) -> Result { + let router_address = Bytes::from_str(&router_address) + .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; + Ok(EVMTychoEncoder { strategy_selector, signer, chain, router_address }) + } +} +impl TychoEncoder for EVMTychoEncoder { + fn encode_router_calldata( + &self, + solutions: Vec, + ) -> Result, EncodingError> { + let mut transactions: Vec = Vec::new(); + for solution in solutions.iter() { + if solution.exact_out { + return Err(EncodingError::FatalError( + "Currently only exact input solutions are supported".to_string(), + )); + } + + let router_address = solution + .router_address + .clone() + .unwrap_or(self.router_address.clone()); + + let strategy = self.strategy_selector.select_strategy( + solution, + self.signer.clone(), + self.chain, + )?; + let (contract_interaction, target_address) = + strategy.encode_strategy(solution.clone(), router_address)?; + + let value = match solution.native_action.as_ref() { + Some(NativeAction::Wrap) => solution.given_amount.clone(), + _ => BigUint::ZERO, + }; + + transactions.push(Transaction { + value, + data: contract_interaction, + to: target_address, + }); + } + Ok(transactions) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::encoding::strategy_encoder::StrategyEncoder; + + struct MockStrategySelector; + + impl StrategySelector for MockStrategySelector { + fn select_strategy( + &self, + _solution: &Solution, + _signer: Option, + _chain: Chain, + ) -> Result, EncodingError> { + Ok(Box::new(MockStrategy)) + } + } + + struct MockStrategy; + + impl StrategyEncoder for MockStrategy { + fn encode_strategy( + &self, + _solution: Solution, + _router_address: Bytes, + ) -> Result<(Vec, Bytes), EncodingError> { + Ok(( + Bytes::from_str("0x1234") + .unwrap() + .to_vec(), + Bytes::from_str("0xabcd").unwrap(), + )) + } + } + + fn get_mocker_tycho_encoder() -> EVMTychoEncoder { + let strategy_selector = MockStrategySelector; + EVMTychoEncoder::new( + strategy_selector, + "0x1234567890abcdef1234567890abcdef12345678".to_string(), + Some("0xabcdef".to_string()), + Chain::Ethereum, + ) + .unwrap() + } + + #[test] + fn test_encode_router_calldata() { + let encoder = get_mocker_tycho_encoder(); + + let eth_amount_in = BigUint::from(1000u32); + let solution = Solution { + exact_out: false, + given_amount: eth_amount_in.clone(), + router_address: None, + native_action: Some(NativeAction::Wrap), + ..Default::default() + }; + + let transactions = encoder.encode_router_calldata(vec![solution]); + + assert!(transactions.is_ok()); + let transactions = transactions.unwrap(); + assert_eq!(transactions.len(), 1); + assert_eq!(transactions[0].value, eth_amount_in); + assert_eq!(transactions[0].data, Bytes::from_str("0x1234").unwrap()); + assert_eq!(transactions[0].to, Bytes::from_str("0xabcd").unwrap()); + } + + #[test] + fn test_encode_router_calldata_fails_for_exact_out() { + let encoder = get_mocker_tycho_encoder(); + + let solution = Solution { + exact_out: true, // This should cause an error + ..Default::default() + }; + + let result = encoder.encode_router_calldata(vec![solution]); + + assert!(result.is_err()); + assert_eq!( + result.err().unwrap(), + EncodingError::FatalError( + "Currently only exact input solutions are supported".to_string() + ) + ); + } +} diff --git a/src/encoding/mod.rs b/src/encoding/mod.rs index ef2afaa..6d169f5 100644 --- a/src/encoding/mod.rs +++ b/src/encoding/mod.rs @@ -2,6 +2,6 @@ mod errors; #[cfg(feature = "evm")] mod evm; mod models; -mod router_encoder; mod strategy_encoder; mod swap_encoder; +mod tycho_encoder; diff --git a/src/encoding/router_encoder.rs b/src/encoding/tycho_encoder.rs similarity index 85% rename from src/encoding/router_encoder.rs rename to src/encoding/tycho_encoder.rs index e3af9bb..bfec250 100644 --- a/src/encoding/router_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -5,7 +5,7 @@ use crate::encoding::{ }; #[allow(dead_code)] -pub trait RouterEncoder { +pub trait TychoEncoder { fn encode_router_calldata( &self, solutions: Vec, From a9c68a4de0e42199fc9a298d5b7a07df71e704c0 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 30 Jan 2025 17:39:51 -0500 Subject: [PATCH 2/7] test: (WIP) Router integration tests TODO it don't pass Took 14 seconds Took 12 minutes --- foundry/test/TychoRouter.t.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 4c424d5..9a572ac 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -671,4 +671,23 @@ contract TychoRouterTest is TychoRouterTestSetup { uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr); assertGe(finalBalance, expAmountOut); } + + function testSingleSwapIntegration() public { + // Test created with calldata from our router encoder + // Tests swapping WETH -> DAI on a USV2 pool + vm.rollFork(21740300); + deal(WETH_ADDR, tychoRouterAddr, 1 ether); + uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); + + // Encoded solution generated using `test_split_swap_strategy_encoder` with + // Alice's private key + (bool success,) = tychoRouterAddr.call( + hex"e73e3baa0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c38a9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f400000000000000000000000000000000000000000000000000000000679c0498000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041eea332470b38c64899045460469146ef146969d3ed5f64fc58f25f58bc709dfb172ef15397b7591fcfd6642cd5a50509702c39a39d87ab79575d5c5388203e3b1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a01000000005c2f5a71f67c01775180adc06909288b4c329308bd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56402c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f40000000000" + ); + uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertGt(balancerAfter, balancerBefore); + + } } From 5d6f0c1673932e55c1ba64b25a02a51426328e3e Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 30 Jan 2025 22:57:31 -0500 Subject: [PATCH 3/7] fix: Fix selector - shouldn't contain spaces I found this because I kept hitting the fallback function, meaning the selector wasn't recognized --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 3f2c6d1..b93405b 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -60,7 +60,7 @@ pub struct SplitSwapStrategyEncoder { impl SplitSwapStrategyEncoder { pub fn new(signer_pk: String, chain: Chain) -> Result { - let selector = "swap(uint256, address, address, uint256, bool, bool, uint256, address, ((address,uint160,uint48,uint48),address,uint256),bytes, bytes)".to_string(); + let selector = "swap(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string(); Ok(Self { permit2: Permit2::new(signer_pk, chain)?, selector }) } } From b01fa4cf04c1b439c94e7d2682d335f26ced5c74 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 30 Jan 2025 23:25:11 -0500 Subject: [PATCH 4/7] test: Change some addresses to match contract test - Use Alice's PK to match our router test, set her as the receiver too - Component id should be a usv2 pool - Use our router test's router address as the test's router address - Use our test's contract (address(this)) as the sender --- .../evm/strategy_encoder/strategy_encoders.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index b93405b..50c1737 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -299,14 +299,14 @@ mod tests { fn test_split_swap_strategy_encoder() { // Set up a mock private key for signing let private_key = - "4c0883a69102937d6231471b5dbb6204fe512961708279feb1be6ae5538da033".to_string(); + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let swap = Swap { component: ProtocolComponent { - id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), protocol_system: "uniswap_v2".to_string(), ..Default::default() }, @@ -323,19 +323,19 @@ mod tests { checked_token: dai, expected_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), check_amount: None, - sender: Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(), - receiver: Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(), + sender: Bytes::from_str("0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap], ..Default::default() }; - let router_address = Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(); + let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(); let (calldata, _) = encoder .encode_strategy(solution, router_address) .unwrap(); let expected_input = String::from(concat!( - "e73e3baa", + "4860f9ed", "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out @@ -343,7 +343,7 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // wrap "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "0000000000000000000000000000000000000000000000000000000000000002", // tokens length - "0000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // receiver + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver )); // after this there is the permit and because of the deadlines (that depend on block time) // it's hard to assert @@ -377,8 +377,8 @@ mod tests { "5c2f5a71f67c01775180adc06909288b4c329308", // executor address "bd0625ab", // selector "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id - "2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // receiver + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "00", // zero2one "00", // exact out "000000", // padding From 4d697bfebfcb2827bb4411e42a4c5001b5f9d61e Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Thu, 30 Jan 2025 23:36:10 -0500 Subject: [PATCH 5/7] test: Router integration test with many TODOs --- foundry/src/TychoRouter.sol | 6 ++++ foundry/test/TychoRouter.t.sol | 32 ++++++++++++++----- foundry/test/TychoRouterTestSetup.sol | 5 ++- .../evm/strategy_encoder/strategy_encoders.rs | 3 ++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index fffffb8..497c858 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -14,6 +14,7 @@ import "@uniswap/v3-updated/CallbackValidationV2.sol"; import "./ExecutionDispatcher.sol"; import "./CallbackVerificationDispatcher.sol"; import {LibSwap} from "../lib/LibSwap.sol"; +import "forge-std/console.sol"; error TychoRouter__WithdrawalFailed(); error TychoRouter__AddressZero(); @@ -197,6 +198,7 @@ contract TychoRouter is uint256[] memory remainingAmounts = new uint256[](nTokens); uint256[] memory amounts = new uint256[](nTokens); + console.logUint(amountIn); amounts[0] = amountIn; remainingAmounts[0] = amountIn; @@ -208,9 +210,13 @@ contract TychoRouter is currentAmountIn = split > 0 ? (amounts[tokenInIndex] * split) / 0xffffff : remainingAmounts[tokenInIndex]; + console.logUint(split); + console.logUint(tokenInIndex); // This gives 1, I guess it should be 0 + console.logUint(currentAmountIn); currentAmountOut = _callExecutor( swapData.executor(), swapData.executorSelector(), + // TODO 0 is being passed here which makes it fail currentAmountIn, swapData.protocolData() ); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 9a572ac..daf8957 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -673,21 +673,37 @@ contract TychoRouterTest is TychoRouterTestSetup { } function testSingleSwapIntegration() public { - // Test created with calldata from our router encoder + // Test created with calldata from our router encoder, replacing the executor + // address with the USV2 executor address. + // Tests swapping WETH -> DAI on a USV2 pool - vm.rollFork(21740300); - deal(WETH_ADDR, tychoRouterAddr, 1 ether); + deal(WETH_ADDR, ALICE, 1 ether); uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); - // Encoded solution generated using `test_split_swap_strategy_encoder` with - // Alice's private key - (bool success,) = tychoRouterAddr.call( - hex"e73e3baa0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c38a9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f400000000000000000000000000000000000000000000000000000000679c0498000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041eea332470b38c64899045460469146ef146969d3ed5f64fc58f25f58bc709dfb172ef15397b7591fcfd6642cd5a50509702c39a39d87ab79575d5c5388203e3b1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a01000000005c2f5a71f67c01775180adc06909288b4c329308bd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56402c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f40000000000" + // Approve permit2 + vm.startPrank(ALICE); + IERC20(WETH_ADDR).approve( + address(0x000000000022D473030F116dDEE9F6B43aC78BA3), + type(uint256).max ); + + // Encoded solution generated using `test_split_swap_strategy_encoder` but + // manually replacing the executor address `5c2f5a71f67c01775180adc06909288b4c329308` + // with the one in this test `5615deb798bb3e4dfa0139dfa1b3d433cc23b72f` + (bool success,) = tychoRouterAddr.call( + hex"4860f9ed0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c3df5a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000679c5962000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d3d0a64c002bdbedfa0dd859cba103fed3337b0bac6ec26ed22f83475426b83a58fd79adfeefea58c5f2e52e915e20a57220c8f32578d942ab5dc15fca3f47241c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a01000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000" + ); + + // TODO why does this only work when Alice is the caller? I tried to say the + // sender is address(this) but that doesn't work... + vm.stopPrank(); + + console.logAddress((address(usv2Executor))); + console.logAddress(address(this)); + console.logUint(block.timestamp); uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); assertGt(balancerAfter, balancerBefore); - } } diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 839e3ee..96b7107 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -39,7 +39,10 @@ contract TychoRouterTestSetup is Test, Constants { MockERC20[] tokens; function setUp() public { - uint256 forkBlock = 21000000; + // TODO I changed the forked block to match the signature + // of the integration test and now all the other tests fail + // fix this when the integration test passes. + uint256 forkBlock = 21742149; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.startPrank(ADMIN); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 50c1737..c9dd737 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -124,6 +124,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { U8::from( tokens .iter() + // TODO Something is wrong with our token in and out indices .position(|t| *t == swap.token_in) .ok_or_else(|| { EncodingError::InvalidInput( @@ -134,6 +135,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { U8::from( tokens .iter() + // TODO Something is wrong with our token in and out indices .position(|t| *t == swap.token_out) .ok_or_else(|| { EncodingError::InvalidInput( @@ -385,6 +387,7 @@ mod tests { )); let hex_calldata = encode(&calldata); + println!("{}", hex_calldata); assert_eq!(hex_calldata[..520], expected_input); assert_eq!(hex_calldata[1288..], expected_swaps); } From c85c353e344a1fec4a2fbcd5b460b37f2edfc91e Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 31 Jan 2025 12:17:24 +0000 Subject: [PATCH 6/7] fix: Fix token index order in strategy encoding. The contract relies on this order!! Remove TODOs and comments --- don't change below this line --- ENG-4081 Took 1 hour 12 minutes Took 12 seconds --- foundry/src/TychoRouter.sol | 7 +--- foundry/test/TychoRouter.t.sol | 13 ++----- foundry/test/TychoRouterTestSetup.sol | 5 +-- .../evm/strategy_encoder/strategy_encoders.rs | 35 ++++++++++++------- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 497c858..d125212 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -14,7 +14,6 @@ import "@uniswap/v3-updated/CallbackValidationV2.sol"; import "./ExecutionDispatcher.sol"; import "./CallbackVerificationDispatcher.sol"; import {LibSwap} from "../lib/LibSwap.sol"; -import "forge-std/console.sol"; error TychoRouter__WithdrawalFailed(); error TychoRouter__AddressZero(); @@ -198,7 +197,6 @@ contract TychoRouter is uint256[] memory remainingAmounts = new uint256[](nTokens); uint256[] memory amounts = new uint256[](nTokens); - console.logUint(amountIn); amounts[0] = amountIn; remainingAmounts[0] = amountIn; @@ -210,13 +208,10 @@ contract TychoRouter is currentAmountIn = split > 0 ? (amounts[tokenInIndex] * split) / 0xffffff : remainingAmounts[tokenInIndex]; - console.logUint(split); - console.logUint(tokenInIndex); // This gives 1, I guess it should be 0 - console.logUint(currentAmountIn); + currentAmountOut = _callExecutor( swapData.executor(), swapData.executorSelector(), - // TODO 0 is being passed here which makes it fail currentAmountIn, swapData.protocolData() ); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index daf8957..a8073ab 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -682,25 +682,16 @@ contract TychoRouterTest is TychoRouterTestSetup { // Approve permit2 vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve( - address(0x000000000022D473030F116dDEE9F6B43aC78BA3), - type(uint256).max - ); - + IERC20(WETH_ADDR).approve(address(permit2Address), type(uint256).max); // Encoded solution generated using `test_split_swap_strategy_encoder` but // manually replacing the executor address `5c2f5a71f67c01775180adc06909288b4c329308` // with the one in this test `5615deb798bb3e4dfa0139dfa1b3d433cc23b72f` (bool success,) = tychoRouterAddr.call( - hex"4860f9ed0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c3df5a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000679c5962000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d3d0a64c002bdbedfa0dd859cba103fed3337b0bac6ec26ed22f83475426b83a58fd79adfeefea58c5f2e52e915e20a57220c8f32578d942ab5dc15fca3f47241c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a01000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000" + hex"4860f9ed0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c43ba900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000679cb5b10000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415bfd02ffd61c11192d1b54d76e0af125afbb32568aad37ec35f918bd5fb304cd314954213ed77c0d071301ddc45243ad57e86fe18f2905b682acc4f1a43ad8dc1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a00010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000" ); - // TODO why does this only work when Alice is the caller? I tried to say the - // sender is address(this) but that doesn't work... vm.stopPrank(); - console.logAddress((address(usv2Executor))); - console.logAddress(address(this)); - console.logUint(block.timestamp); uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 96b7107..839e3ee 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -39,10 +39,7 @@ contract TychoRouterTestSetup is Test, Constants { MockERC20[] tokens; function setUp() public { - // TODO I changed the forked block to match the signature - // of the integration test and now all the other tests fail - // fix this when the integration test passes. - uint256 forkBlock = 21742149; + uint256 forkBlock = 21000000; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.startPrank(ADMIN); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index c9dd737..7970a09 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1,6 +1,6 @@ -use std::{cmp::max, str::FromStr}; +use std::{cmp::max, collections::HashSet, str::FromStr}; -use alloy_primitives::{aliases::U24, map::HashSet, FixedBytes, U256, U8}; +use alloy_primitives::{aliases::U24, FixedBytes, U256, U8}; use alloy_sol_types::SolValue; use num_bigint::BigUint; use tycho_core::{keccak256, models::Chain, Bytes}; @@ -88,18 +88,30 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { min_amount_out = max(user_specified_min_amount, expected_amount_with_slippage); } } + // 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 = + vec![solution.given_token.clone(), solution.checked_token.clone()] + .into_iter() + .collect(); - let mut tokens: Vec = solution + let intermediary_tokens: HashSet = solution .swaps .iter() .flat_map(|swap| vec![swap.token_in.clone(), swap.token_out.clone()]) - .collect::>() - .into_iter() .collect(); - + let mut intermediary_tokens: Vec = intermediary_tokens + .difference(&solution_tokens) + .cloned() + .collect(); // this is only to make the test deterministic (same index for the same token for different // runs) - tokens.sort(); + intermediary_tokens.sort(); + + let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len()); + tokens.push(solution.given_token.clone()); + tokens.extend(intermediary_tokens); + tokens.push(solution.checked_token.clone()); let mut swaps = vec![]; for swap in solution.swaps.iter() { @@ -124,7 +136,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { U8::from( tokens .iter() - // TODO Something is wrong with our token in and out indices .position(|t| *t == swap.token_in) .ok_or_else(|| { EncodingError::InvalidInput( @@ -135,7 +146,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { U8::from( tokens .iter() - // TODO Something is wrong with our token in and out indices .position(|t| *t == swap.token_out) .ok_or_else(|| { EncodingError::InvalidInput( @@ -325,7 +335,7 @@ mod tests { checked_token: dai, expected_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), check_amount: None, - sender: Bytes::from_str("0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496").unwrap(), + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), swaps: vec![swap], ..Default::default() @@ -372,8 +382,8 @@ mod tests { // ple encoded swaps "005a", // Swap header - "01", // token in index - "00", // token out index + "00", // token in index + "01", // token out index "000000", // split // Swap data "5c2f5a71f67c01775180adc06909288b4c329308", // executor address @@ -387,7 +397,6 @@ mod tests { )); let hex_calldata = encode(&calldata); - println!("{}", hex_calldata); assert_eq!(hex_calldata[..520], expected_input); assert_eq!(hex_calldata[1288..], expected_swaps); } From b0b24edbd654bfa61542bb0a428064a5ed773a36 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 31 Jan 2025 15:40:53 +0000 Subject: [PATCH 7/7] chore(release): 0.25.1 [skip ci] ## [0.25.1](https://github.com/propeller-heads/tycho-execution/compare/0.25.0...0.25.1) (2025-01-31) ### Bug Fixes * Fix selector - shouldn't contain spaces ([5d6f0c1](https://github.com/propeller-heads/tycho-execution/commit/5d6f0c1673932e55c1ba64b25a02a51426328e3e)) * Fix token index order in strategy encoding. ([c85c353](https://github.com/propeller-heads/tycho-execution/commit/c85c353e344a1fec4a2fbcd5b460b37f2edfc91e)) --- CHANGELOG.md | 8 ++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7574ece..aa2644b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [0.25.1](https://github.com/propeller-heads/tycho-execution/compare/0.25.0...0.25.1) (2025-01-31) + + +### Bug Fixes + +* Fix selector - shouldn't contain spaces ([5d6f0c1](https://github.com/propeller-heads/tycho-execution/commit/5d6f0c1673932e55c1ba64b25a02a51426328e3e)) +* Fix token index order in strategy encoding. ([c85c353](https://github.com/propeller-heads/tycho-execution/commit/c85c353e344a1fec4a2fbcd5b460b37f2edfc91e)) + ## [0.25.0](https://github.com/propeller-heads/tycho-execution/compare/0.24.0...0.25.0) (2025-01-31) diff --git a/Cargo.lock b/Cargo.lock index 5262f23..4359395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4163,7 +4163,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.25.0" +version = "0.25.1" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index a590847..86975b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.25.0" +version = "0.25.1" edition = "2021" [dependencies]