From 20e6419a208965e4d80e3d36455bd39078fe613b Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 20 Feb 2025 17:14:49 +0000 Subject: [PATCH 1/7] feat: Adapt SplitSwapStrategyEncoder to have optional permit2 logic - If the signer_pk is not passed -> use the swap method that expects the tokens to be already in the Router - If it is passed -> compute permit2 and use swap method that does the token in transfer - Extend builder to have another shortcut - Add integration test with contract - Update bin (and simplified it) and quickstart --- don't change below this line --- ENG-4255 Took 1 hour 51 minutes Took 2 minutes Took 7 seconds --- README.md | 15 +- examples/quickstart/main.rs | 2 +- foundry/test/TychoRouter.t.sol | 27 +-- src/bin/tycho-encode.rs | 61 +++---- src/encoding/evm/encoder_builder.rs | 34 +++- .../evm/strategy_encoder/strategy_encoders.rs | 166 +++++++++++++++--- 6 files changed, 213 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index ada92e4..1c0dbd2 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,18 @@ The command lets you choose the encoding strategy to be used. The available stra #### Tycho Router -`tycho-router`: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for signing +`tycho-router`: Encodes a transaction using the Tycho Router encoding strategy. + +Example: + +```bash +echo '' | tycho-encode tycho-router +``` + +#### Tycho Router With Permit2 + +`tycho-router-with-permit2`: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for +signing Permit2. Example: @@ -64,7 +75,7 @@ The commands accept the following options: Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2 and the Tycho Router strategy: ```bash -echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","checked_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":0.0}],"direct_execution":true}' | tycho-encode tycho-router --swapper-pk 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234 +echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","checked_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":0.0}],"direct_execution":true}' | tycho-encode tycho-router-permit2 --swapper-pk 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234 ``` #### JSON Payload Structure: Solution struct diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index d74cd9a..7f5983a 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -23,7 +23,7 @@ fn main() { // Initialize the encoder let encoder = EVMEncoderBuilder::new() .chain(TychoCoreChain::Ethereum) - .tycho_router(swapper_pk, None) + .tycho_router_with_permit2(None, swapper_pk) .expect("Failed to create encoder builder") .build() .expect("Failed to build encoder"); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index e31f082..ccfb7ea 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -710,7 +710,7 @@ contract TychoRouterTest is TychoRouterTestSetup { uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); - assertGt(balancerAfter - balancerBefore, 26173932); + assertEq(balancerAfter - balancerBefore, 2659881924818443699787); } function testSingleSwapWithoutPermit2Integration() public { @@ -720,17 +720,18 @@ contract TychoRouterTest is TychoRouterTestSetup { // Tests swapping WETH -> DAI on a USV2 pool without permit2 deal(WETH_ADDR, tychoRouterAddr, 1 ether); uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE); + // Encoded solution generated using `test_split_swap_strategy_encoder_simple_route_no_permit2` + // but manually replacing the executor address + // `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test + // `5615deb798bb3e4dfa0139dfa1b3d433cc23b72f` + (bool success,) = tychoRouterAddr.call( + hex"0a83cb080000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae37400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005c005a00010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000" + ); - // TODO replace this calldata once we have the encoder side - // (bool success,) = tychoRouterAddr.call( - // hex"d499aa880000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067d481bb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067acfbc3000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041f2740fde9662d8bc1f8fe8e8fc29447c1832d625f06f4a56ee5103ad555c12323af5d50eb840f73d17873383ae3b7573956d5df7b2bf76bddba768c2837894a51b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a00010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000" - // ); - // - // vm.stopPrank(); - // - // uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); - // assertTrue(success, "Call Failed"); - // assertGt(balancerAfter - balancerBefore, 26173932); + vm.stopPrank(); + uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); + assertTrue(success, "Call Failed"); + assertEq(balancerAfter - balancerBefore, 2659881924818443699787); } function testUSV4Integration() public { @@ -790,7 +791,7 @@ contract TychoRouterTest is TychoRouterTestSetup { uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE); assertTrue(success, "Call Failed"); - assertGt(balancerAfter - balancerBefore, 26173932); + assertEq(balancerAfter - balancerBefore, 2659881924818443699787); } function testSingleSwapWithUnwrapIntegration() public { @@ -819,7 +820,7 @@ contract TychoRouterTest is TychoRouterTestSetup { uint256 balancerAfter = ALICE.balance; assertTrue(success, "Call Failed"); - assertGt(balancerAfter - balancerBefore, 26173932); + assertEq(balancerAfter - balancerBefore, 1120007305574805922); } function testSplitSwapIntegration() public { diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index 96f9387..2178269 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -1,11 +1,9 @@ use std::io::{self, Read}; use clap::{Parser, Subcommand}; -use serde_json::Value; use tycho_core::models::Chain; use tycho_execution::encoding::{ - errors::EncodingError, evm::encoder_builder::EVMEncoderBuilder, models::Solution, - tycho_encoder::TychoEncoder, + evm::encoder_builder::EVMEncoderBuilder, models::Solution, tycho_encoder::TychoEncoder, }; #[derive(Parser)] @@ -52,6 +50,11 @@ pub enum Commands { TychoRouter { #[arg(short, long)] config_path: Option, + }, + /// Use the Tycho router encoding strategy with Permit2 approval and token in transfer + TychoRouterPermit2 { + #[arg(short, long)] + config_path: Option, #[arg(short, long)] swapper_pk: String, }, @@ -64,6 +67,7 @@ pub enum Commands { fn main() -> Result<(), Box> { let cli = Cli::parse(); + let chain = Chain::Ethereum; // Read from stdin until EOF let mut buffer = String::new(); @@ -74,16 +78,24 @@ fn main() -> Result<(), Box> { if buffer.trim().is_empty() { return Err("No input provided. Expected JSON input on stdin.".into()); } + let solution: Solution = serde_json::from_str(&buffer)?; - // Encode the solution - let encoded = match cli.command { - Commands::TychoRouter { config_path, swapper_pk } => { - encode_swaps(&buffer, config_path, Some(swapper_pk), true)? - } - Commands::DirectExecution { config_path } => { - encode_swaps(&buffer, config_path, None, false)? + let mut builder = EVMEncoderBuilder::new().chain(chain); + + builder = match cli.command { + Commands::TychoRouter { config_path } => builder.tycho_router(config_path)?, + Commands::TychoRouterPermit2 { config_path, swapper_pk } => { + builder.tycho_router_with_permit2(config_path, swapper_pk)? } + Commands::DirectExecution { config_path } => builder.direct_execution(config_path)?, }; + let encoder = builder.build()?; + let transactions = encoder.encode_router_calldata(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)), + }); // Output the encoded result as JSON to stdout println!( "{}", @@ -93,32 +105,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -fn encode_swaps( - input: &str, - config_path: Option, - swapper_pk: Option, - use_tycho_router: bool, -) -> Result { - let solution: Solution = serde_json::from_str(input)?; - let chain = Chain::Ethereum; - - let mut builder = EVMEncoderBuilder::new().chain(chain); - builder = if use_tycho_router { - let private_key = swapper_pk.ok_or(EncodingError::FatalError( - "Swapper private key is required for tycho_router".to_string(), - ))?; - builder.tycho_router(private_key, config_path)? - } else { - builder.direct_execution(config_path)? - }; - let encoder = builder.build()?; - - let transactions = encoder.encode_router_calldata(vec![solution])?; - - Ok(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)), - })) -} diff --git a/src/encoding/evm/encoder_builder.rs b/src/encoding/evm/encoder_builder.rs index eb9011f..f7dfa56 100644 --- a/src/encoding/evm/encoder_builder.rs +++ b/src/encoding/evm/encoder_builder.rs @@ -42,17 +42,35 @@ impl EVMEncoderBuilder { self } - /// Shortcut method to initialize a `SplitSwapStrategyEncoder` with a given `swapper_pk`. - /// **Note**: Should not be used at the same time as `strategy_encoder`. - pub fn tycho_router( - self, - swapper_pk: String, - executors_file_path: Option, - ) -> Result { + /// Shortcut method to initialize a `SplitSwapStrategyEncoder` without any approval nor token in + /// transfer. **Note**: Should not be used at the same time as `strategy_encoder`. + pub fn tycho_router(self, executors_file_path: Option) -> Result { if let Some(chain) = self.chain { let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?; let strategy = - Box::new(SplitSwapStrategyEncoder::new(swapper_pk, chain, swap_encoder_registry)?); + Box::new(SplitSwapStrategyEncoder::new(chain, swap_encoder_registry, None)?); + Ok(EVMEncoderBuilder { chain: Some(chain), strategy: Some(strategy) }) + } else { + Err(EncodingError::FatalError( + "Please set the chain before setting the tycho router".to_string(), + )) + } + } + + /// Shortcut method to initialize a `SplitSwapStrategyEncoder` with Permit2 approval and token + /// in transfer. **Note**: Should not be used at the same time as `strategy_encoder`. + pub fn tycho_router_with_permit2( + self, + executors_file_path: Option, + swapper_pk: String, + ) -> Result { + if let Some(chain) = self.chain { + let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?; + let strategy = Box::new(SplitSwapStrategyEncoder::new( + chain, + swap_encoder_registry, + Some(swapper_pk), + )?); Ok(EVMEncoderBuilder { chain: Some(chain), strategy: Some(strategy) }) } else { Err(EncodingError::FatalError( diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 6983268..3749e0f 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -79,7 +79,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder { #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - permit2: Permit2, + permit2: Option, selector: String, native_address: Bytes, wrapped_address: Bytes, @@ -88,14 +88,21 @@ pub struct SplitSwapStrategyEncoder { impl SplitSwapStrategyEncoder { pub fn new( - swapper_pk: String, blockchain: tycho_core::models::Chain, swap_encoder_registry: SwapEncoderRegistry, + swapper_pk: Option, ) -> Result { let chain = Chain::from(blockchain); - let selector = "swapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string(); + let (permit2, selector) = if let Some(swapper_pk) = swapper_pk { + (Some(Permit2::new(swapper_pk, chain.clone())?), "swapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()) + } else { + ( + None, + "swap(uint256,address,address,uint256,bool,bool,uint256,address,bytes)".to_string(), + ) + }; Ok(Self { - permit2: Permit2::new(swapper_pk, chain.clone())?, + permit2, selector, swap_encoder_registry, native_address: chain.native_token()?, @@ -122,12 +129,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { &self.native_address, &self.wrapped_address, )?; - let (permit, signature) = self.permit2.get_permit( - &solution.router_address, - &solution.sender, - &solution.given_token, - &solution.given_amount, - )?; + 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 @@ -214,20 +216,41 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { } let encoded_swaps = self.ple_encode(swaps); - let method_calldata = ( - 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(); + let method_calldata = if let Some(permit2) = self.permit2.clone() { + let (permit, signature) = permit2.get_permit( + &solution.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)?, + encoded_swaps, + ) + .abi_encode() + }; let contract_interaction = encode_input(&self.selector, method_calldata); Ok((contract_interaction, solution.router_address, None)) @@ -606,7 +629,8 @@ mod tests { }; let swap_encoder_registry = get_swap_encoder_registry(); let encoder = - SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, Some(private_key)) + .unwrap(); let solution = Solution { exact_out: false, given_token: weth, @@ -707,7 +731,8 @@ mod tests { }; let swap_encoder_registry = get_swap_encoder_registry(); let encoder = - SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, Some(private_key)) + .unwrap(); let solution = Solution { exact_out: false, given_token: eth(), @@ -755,7 +780,8 @@ mod tests { }; let swap_encoder_registry = get_swap_encoder_registry(); let encoder = - SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, Some(private_key)) + .unwrap(); let solution = Solution { exact_out: false, given_token: dai, @@ -844,7 +870,8 @@ mod tests { }; let swap_encoder_registry = get_swap_encoder_registry(); let encoder = - SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, Some(private_key)) + .unwrap(); let solution = Solution { exact_out: false, given_token: weth, @@ -924,7 +951,8 @@ mod tests { }; let swap_encoder_registry = get_swap_encoder_registry(); let encoder = - SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, Some(private_key)) + .unwrap(); let solution = Solution { exact_out: false, given_token: usdc, @@ -1010,4 +1038,84 @@ mod tests { assert_eq!(hex_calldata[..520], expected_input); assert_eq!(hex_calldata[1288..], expected_swaps); } + + #[test] + fn test_split_swap_strategy_encoder_simple_route_no_permit2() { + // Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no grouping + // optimizations. + + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap()); + let slippage = Some(0.01f64); + let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap()); + let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap(); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = + SplitSwapStrategyEncoder::new(eth_chain(), swap_encoder_registry, None).unwrap(); + let solution = Solution { + exact_out: false, + 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(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + swaps: vec![swap], + ..Default::default() + }; + + let (calldata, _, _) = encoder + .encode_strategy(solution) + .unwrap(); + let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); + let expected_input = [ + "0a83cb08", // Function selector + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out + "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out + &expected_min_amount_encoded, // min amount out + "0000000000000000000000000000000000000000000000000000000000000000", // wrap + "0000000000000000000000000000000000000000000000000000000000000000", // unwrap + "0000000000000000000000000000000000000000000000000000000000000002", // tokens length + "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver + "0000000000000000000000000000000000000000000000000000000000000120", // offset of ple encoded swaps + "000000000000000000000000000000000000000000000000000000000000005c", // length of ple encoded swaps without padding + "005a", // ple encoded swaps + // Swap header + "00", // token in index + "01", // token out index + "000000", // split + // Swap data + "5c2f5a71f67c01775180adc06909288b4c329308", // executor address + "bd0625ab", // selector + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id + "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver + "00", // zero2one + "00", // exact out + "000000", // padding + ] + .join(""); + + let hex_calldata = encode(&calldata); + + assert_eq!(hex_calldata, expected_input); + println!("{}", hex_calldata); + } } From c6c734d4940303f862b7b0275d57a87e4ef81d56 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 20 Feb 2025 17:34:13 +0000 Subject: [PATCH 2/7] feat: Update tycho-core --- don't change below this line --- ENG-4255 Took 7 minutes --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 364e529..46cd809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4317,8 +4317,8 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-core" -version = "0.55.2" -source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.55.2#dfa50d5e318253001938655a49aa3e05f958d89e" +version = "0.56.5" +source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.56.5#2af8c1a5a61c5479eab5f7903b69943efa61e2c8" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 64ce4e5..94c6602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ clap = { version = "4.5.3", features = ["derive"] } alloy = { version = "0.9.2", features = ["providers", "rpc-types-eth", "eip712", "signer-local"], optional = true } alloy-sol-types = { version = "0.8.14", optional = true } alloy-primitives = { version = "0.8.9", optional = true } -tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.55.2" } +tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.56.5" } once_cell = "1.20.2" [dev-dependencies] From 8b2af4f5775a14fd508c6660e43c541e9faeeb0d Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 20 Feb 2025 17:44:41 +0000 Subject: [PATCH 3/7] feat: Implement Clone for EVMTychoEncoder --- don't change below this line --- ENG-4255 Took 6 minutes --- src/encoding/evm/tycho_encoder.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index aef79b7..cfd4375 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -20,6 +20,16 @@ pub struct EVMTychoEncoder { wrapped_address: Bytes, } +impl Clone for EVMTychoEncoder { + fn clone(&self) -> Self { + Self { + strategy_encoder: self.strategy_encoder.clone_box(), + native_address: self.native_address.clone(), + wrapped_address: self.wrapped_address.clone(), + } + } +} + impl EVMTychoEncoder { pub fn new( chain: tycho_core::models::Chain, From 918bc27e397a4f1790ca5d359043b9be19504d6d Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 20 Feb 2025 18:31:28 +0000 Subject: [PATCH 4/7] chore: Don't call Chain TychoCoreChain unnecessarily --- don't change below this line --- ENG-4255 Took 2 minutes --- examples/quickstart/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index 7f5983a..7c0ca51 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use num_bigint::BigUint; use tycho_core::{ - models::{protocol::ProtocolComponent, Chain as TychoCoreChain}, + models::{protocol::ProtocolComponent, Chain}, Bytes, }; use tycho_execution::encoding::{ @@ -22,7 +22,7 @@ fn main() { // Initialize the encoder let encoder = EVMEncoderBuilder::new() - .chain(TychoCoreChain::Ethereum) + .chain(Chain::Ethereum) .tycho_router_with_permit2(None, swapper_pk) .expect("Failed to create encoder builder") .build() From a7aa4d7ebb956ec290b5853245f0e1f0077d708a Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 21 Feb 2025 00:23:24 -0500 Subject: [PATCH 5/7] fix: Native ETH input/output integration tests/fixes - Only for single swaps - Used USV4 for this because it's the only DEX we support that allows native ETH swaps - For Native ETH input single swaps, we need to properly check the remaining amount (we were treating them wronly like ERC20 tokens) - For Native ETH output single swaps, we were passing the incorrect currency (the settle always needs to be the out token and the take always needs to be the in token, this should not depend on the zeroForOne value). --- foundry/src/TychoRouter.sol | 16 ++- foundry/src/executors/UniswapV4Executor.sol | 4 +- foundry/test/TychoRouter.t.sol | 61 ++++++++- .../evm/strategy_encoder/strategy_encoders.rs | 123 +++++++++++++++++- 4 files changed, 195 insertions(+), 9 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 377d64b..41ddde3 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -124,7 +124,13 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); } - uint256 leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); + uint256 leftoverAmountIn; + if (tokenIn == address(0)) { + leftoverAmountIn = address(this).balance; + } else { + leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); + } + if (leftoverAmountIn > 0) { revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); } @@ -209,7 +215,13 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { revert TychoRouter__NegativeSlippage(amountOut, minAmountOut); } - uint256 leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); + uint256 leftoverAmountIn; + if (tokenIn == address(0)) { + leftoverAmountIn = address(this).balance; + } else { + leftoverAmountIn = IERC20(tokenIn).balanceOf(address(this)); + } + if (leftoverAmountIn > 0) { revert TychoRouter__AmountInNotFullySpent(leftoverAmountIn); } diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index be069e6..b47a835 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -71,8 +71,8 @@ contract UniswapV4Executor is IExecutor, V4Router { hookData: bytes("") }) ); - params[1] = abi.encode(key.currency0, amountIn); - params[2] = abi.encode(key.currency1, uint256(0)); + params[1] = abi.encode(tokenIn, amountIn); // currency to settle + params[2] = abi.encode(tokenOut, uint256(0)); // currency to take swapData = abi.encode(actions, params); } else { PathKey[] memory path = new PathKey[](pools.length); diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index e31f082..5396d93 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -734,8 +734,7 @@ contract TychoRouterTest is TychoRouterTestSetup { } function testUSV4Integration() public { - // Test created with calldata from our router encoder, replacing the executor - // address with the USV4 executor address. + // Test created with calldata from our router encoder. // Performs a sequential swap from USDC to PEPE though ETH using two // consecutive USV4 pools @@ -749,8 +748,7 @@ contract TychoRouterTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(USDC_ADDR).approve(address(permit2Address), type(uint256).max); // Encoded solution generated using `test_split_encoding_strategy_usv4` - // but manually replacing the executor address - // `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test + // and ensuring that the encoded executor address is the one in this test // `f62849f9a0b5bf2913b396098f7c7019b51a820a` (bool success,) = tychoRouterAddr.call( hex"d499aa88000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000067ddee9e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067b668a6000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041bdf91011918dcb5f59ab3588212a035c770a2839fe2c19060491370fa89685b8469def9e83c7b9cf8f0ef5088a3179556a6ba1096cefbe83c09a1182981c93e41c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009400920001000000f62849f9a0b5bf2913b396098f7c7019b51a820abd0625aba0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300f62849f9a0b5bf2913b396098f7c7019b51a820a91dd73460000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f4000000000000000000000000" @@ -764,6 +762,61 @@ contract TychoRouterTest is TychoRouterTestSetup { assertEq(balancerAfter - balancerBefore, 97191013220606467325121599); } + function testUSV4IntegrationInputETH() public { + // Test created with calldata from our router encoder. + + // Performs a single swap from ETH to PEPE without wrapping or unwrapping + // + // ETH ───(USV4)──> PEPE + // + deal(ALICE, 1 ether); + uint256 balancerBefore = IERC20(PEPE_ADDR).balanceOf(ALICE); + + // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` + // and ensuring that the encoded executor address is the one in this test + // `f62849f9a0b5bf2913b396098f7c7019b51a820a` + (bool success,) = tychoRouterAddr.call{value: 1 ether}( + hex"d499aa880000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067def8e900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067b772f100000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004192bf6f59a6e114588b4b5fb00f3acae3eb2dd18b673924b9cf27d1414be469b70113e4ceef228e11c91d178fea26673d9edcd013dee23fa3c45abdfcb573c9371c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a00780001000000f62849f9a0b5bf2913b396098f7c7019b51a820abd0625ab00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301f62849f9a0b5bf2913b396098f7c7019b51a820a91dd73466982508145454ce325ddbe47a25d4ec3d23119330061a80001f4000000000000" + ); + + vm.stopPrank(); + + uint256 balancerAfter = IERC20(PEPE_ADDR).balanceOf(ALICE); + + assertTrue(success, "Call Failed"); + assertEq(balancerAfter - balancerBefore, 242373460199848577067005852); + } + + function testUSV4IntegrationOutputETH() public { + // Test created with calldata from our router encoder. + + // Performs a single swap from ETH to PEPE without wrapping or unwrapping + // + // USDC ───(USV4)──> ETH + // + deal(USDC_ADDR, ALICE, 3000_000000); + uint256 balancerBefore = ALICE.balance; + + // Approve permit2 + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(address(permit2Address), type(uint256).max); + + // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` + // and ensuring that the encoded executor address is the one in this test + // `f62849f9a0b5bf2913b396098f7c7019b51a820a` + (bool success,) = tychoRouterAddr.call( + hex"d499aa8800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000067df206000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067b79a68000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041d603720f916c45405d4655476fc8f5d5e93e561d1bc1bbd944f865ac2b53638e28fa06fde0c1097d688029c85940a53ba54902b42d17378159ae4affb8b958b01b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007a00780001000000f62849f9a0b5bf2913b396098f7c7019b51a820abd0625aba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a91dd73460000000000000000000000000000000000000000000bb800003c000000000000" + ); + + vm.stopPrank(); + + uint256 balancerAfter = ALICE.balance; + + assertTrue(success, "Call Failed"); + console.logUint(balancerAfter - balancerBefore); + assertEq(balancerAfter - balancerBefore, 1117254495486192350); + } + function testSingleSwapWithWrapIntegration() public { // Test created with calldata from our router encoder, replacing the executor // address with the USV2 executor address. diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 6983268..3b6a883 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1006,8 +1006,129 @@ mod tests { )); let hex_calldata = encode(&calldata); - println!("{}", hex_calldata); assert_eq!(hex_calldata[..520], expected_input); assert_eq!(hex_calldata[1288..], expected_swaps); } + + #[test] + fn test_split_encoding_strategy_usv4_eth_in() { + // Performs a single swap from ETH to PEPE using a USV4 pool + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + // + // 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(); + + let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be()); + let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be()); + let mut static_attributes_eth_pepe: HashMap = HashMap::new(); + static_attributes_eth_pepe.insert("fee".into(), pool_fee_eth_pepe); + static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe); + + let swap_eth_pepe = Swap { + component: ProtocolComponent { + id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9" + .to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_eth_pepe, + ..Default::default() + }, + token_in: eth.clone(), + token_out: pepe.clone(), + split: 0f64, + }; + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = + SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + + let solution = Solution { + exact_out: false, + given_token: eth, + given_amount: BigUint::from_str("1_000000000000000000").unwrap(), + checked_token: pepe, + expected_amount: Some(BigUint::from_str("300_000_000000000000000000").unwrap()), + checked_amount: None, + slippage: None, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + swaps: vec![swap_eth_pepe], + ..Default::default() + }; + + let (calldata, _, _) = encoder + .encode_strategy(solution) + .unwrap(); + let hex_calldata = encode(&calldata); + + println!("{}", hex_calldata); + } + #[test] + fn test_split_encoding_strategy_usv4_eth_out() { + // Performs a single swap from USDC to ETH using a USV4 pool + // Note: This test does not assert anything. It is only used to obtain integration test + // data for our router solidity test. + // + // 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(); + + // Fee and tick spacing information for this test is obtained by querying the + // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e + // Using the poolKeys function with the first 25 bytes of the pool id + let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be()); + let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be()); + let mut static_attributes_usdc_eth: HashMap = HashMap::new(); + static_attributes_usdc_eth.insert("fee".into(), pool_fee_usdc_eth); + static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth); + + let swap_usdc_eth = Swap { + component: ProtocolComponent { + id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d" + .to_string(), + protocol_system: "uniswap_v4".to_string(), + static_attributes: static_attributes_usdc_eth, + ..Default::default() + }, + token_in: usdc.clone(), + token_out: eth.clone(), + split: 0f64, + }; + + let swap_encoder_registry = get_swap_encoder_registry(); + let encoder = + SplitSwapStrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap(); + let solution = Solution { + exact_out: false, + given_token: usdc, + given_amount: BigUint::from_str("3000_000000").unwrap(), + checked_token: eth, + expected_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), + checked_amount: None, + slippage: None, + sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), + swaps: vec![swap_usdc_eth], + ..Default::default() + }; + + let (calldata, _, _) = encoder + .encode_strategy(solution) + .unwrap(); + + let hex_calldata = encode(&calldata); + println!("{}", hex_calldata); + } } From be1e679e5107ca0e8e6aa198f8af9ed26abd308c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 21 Feb 2025 09:43:43 +0000 Subject: [PATCH 6/7] chore(release): 0.48.0 [skip ci] ## [0.48.0](https://github.com/propeller-heads/tycho-execution/compare/0.47.0...0.48.0) (2025-02-21) ### Features * Adapt SplitSwapStrategyEncoder to have optional permit2 logic ([20e6419](https://github.com/propeller-heads/tycho-execution/commit/20e6419a208965e4d80e3d36455bd39078fe613b)) * Implement Clone for EVMTychoEncoder ([8b2af4f](https://github.com/propeller-heads/tycho-execution/commit/8b2af4f5775a14fd508c6660e43c541e9faeeb0d)) * Update tycho-core ([c6c734d](https://github.com/propeller-heads/tycho-execution/commit/c6c734d4940303f862b7b0275d57a87e4ef81d56)) --- CHANGELOG.md | 9 +++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a9dd3..144e34d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [0.48.0](https://github.com/propeller-heads/tycho-execution/compare/0.47.0...0.48.0) (2025-02-21) + + +### Features + +* Adapt SplitSwapStrategyEncoder to have optional permit2 logic ([20e6419](https://github.com/propeller-heads/tycho-execution/commit/20e6419a208965e4d80e3d36455bd39078fe613b)) +* Implement Clone for EVMTychoEncoder ([8b2af4f](https://github.com/propeller-heads/tycho-execution/commit/8b2af4f5775a14fd508c6660e43c541e9faeeb0d)) +* Update tycho-core ([c6c734d](https://github.com/propeller-heads/tycho-execution/commit/c6c734d4940303f862b7b0275d57a87e4ef81d56)) + ## [0.47.0](https://github.com/propeller-heads/tycho-execution/compare/0.46.1...0.47.0) (2025-02-20) diff --git a/Cargo.lock b/Cargo.lock index 46cd809..e7498bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,7 +4340,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.47.0" +version = "0.48.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 94c6602..fc82394 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.47.0" +version = "0.48.0" edition = "2021" [[bin]] From c42421718b1d16b90bad2004048b987590293a90 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Fri, 21 Feb 2025 09:44:21 -0500 Subject: [PATCH 7/7] docs: Fix typos in code comments --- foundry/test/TychoRouter.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foundry/test/TychoRouter.t.sol b/foundry/test/TychoRouter.t.sol index 5396d93..a42d2ff 100644 --- a/foundry/test/TychoRouter.t.sol +++ b/foundry/test/TychoRouter.t.sol @@ -790,7 +790,7 @@ contract TychoRouterTest is TychoRouterTestSetup { function testUSV4IntegrationOutputETH() public { // Test created with calldata from our router encoder. - // Performs a single swap from ETH to PEPE without wrapping or unwrapping + // Performs a single swap from USDC to ETH without wrapping or unwrapping // // USDC ───(USV4)──> ETH // @@ -801,7 +801,7 @@ contract TychoRouterTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(USDC_ADDR).approve(address(permit2Address), type(uint256).max); - // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in` + // Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out` // and ensuring that the encoded executor address is the one in this test // `f62849f9a0b5bf2913b396098f7c7019b51a820a` (bool success,) = tychoRouterAddr.call(