From f80ffa924f1da626bef0751c92c09fb133d2ba85 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Mon, 3 Feb 2025 21:24:44 +0530 Subject: [PATCH 01/15] feat: add validation for split swap --- .../evm/strategy_encoder/strategy_encoders.rs | 153 +++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 8dc9eb7..59c359d 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1,4 +1,8 @@ -use std::{cmp::max, collections::HashSet, str::FromStr}; +use std::{ + cmp::max, + collections::{HashMap, HashSet, VecDeque}, + str::FromStr, +}; use alloy_primitives::{aliases::U24, FixedBytes, U256, U8}; use alloy_sol_types::SolValue; @@ -13,7 +17,7 @@ use crate::encoding::{ swap_encoder::SWAP_ENCODER_REGISTRY, utils::{biguint_to_u256, bytes_to_address, encode_input, percentage_to_uint24}, }, - models::{EncodingContext, NativeAction, Solution}, + models::{EncodingContext, NativeAction, Solution, Swap}, strategy_encoder::StrategyEncoder, }; @@ -71,6 +75,12 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { solution: Solution, router_address: Bytes, ) -> Result<(Vec, Bytes), EncodingError> { + validate_swaps(&solution.swaps)?; + validate_token_path_connectivity( + &solution.swaps, + &solution.given_token, + &solution.checked_token, + )?; let (permit, signature) = self.permit2.get_permit( &router_address, &solution.sender, @@ -203,6 +213,145 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { } } +fn validate_swaps(swaps: &[Swap]) -> Result<(), EncodingError> { + for swap in swaps { + if swap.split >= 1.0 { + return Err(EncodingError::InvalidInput(format!( + "Split percentage must be less than 1 (100%), got {}", + swap.split + ))); + } + } + + let mut swaps_by_token: HashMap> = HashMap::new(); + + for swap in swaps { + swaps_by_token + .entry(swap.token_in.clone()) + .or_default() + .push(swap); + } + + for (token, token_swaps) in swaps_by_token { + if token_swaps.is_empty() { + return Err(EncodingError::InvalidInput(format!( + "No swaps found for token {:?}", + token + ))); + } + + // Single swaps don't need remainder handling + if token_swaps.len() == 1 { + continue; + } + + // Check if exactly one swap has 0% split and it's the last one + let zero_splits: Vec<_> = token_swaps + .iter() + .enumerate() + .filter(|(_, s)| s.split == 0.0) + .collect(); + + if zero_splits.len() != 1 { + return Err(EncodingError::InvalidInput(format!( + "Token {:?} must have exactly one 0% split for remainder handling", + token + ))); + } + + if zero_splits[0].0 != token_swaps.len() - 1 { + return Err(EncodingError::InvalidInput(format!( + "The 0% split for token {:?} must be the last swap", + token + ))); + } + + // Sum non-zero splits and validate each is >0% and <100% + let mut total_percentage = 0.0; + for swap in token_swaps + .iter() + .take(token_swaps.len() - 1) + { + if swap.split <= 0.0 { + return Err(EncodingError::InvalidInput(format!( + "Non-remainder splits must be >0% for token {:?}", + token + ))); + } + total_percentage += swap.split; + } + + // Total must be <100% to leave room for remainder + if total_percentage >= 1.0 { + return Err(EncodingError::InvalidInput(format!( + "Total of non-remainder splits for token {:?} must be <100%, got {}%", + token, + total_percentage * 100.0 + ))); + } + } + + Ok(()) +} + +fn validate_token_path_connectivity( + swaps: &[Swap], + given_token: &Bytes, + checked_token: &Bytes, +) -> Result<(), EncodingError> { + // Build directed graph of token flows + let mut graph: HashMap> = HashMap::new(); + for swap in swaps { + graph + .entry(swap.token_in.clone()) + .or_default() + .insert(swap.token_out.clone()); + } + + // BFS from given_token + let mut visited = HashSet::new(); + let mut queue = VecDeque::new(); + queue.push_back(given_token.clone()); + + while let Some(token) = queue.pop_front() { + if !visited.insert(token.clone()) { + continue; + } + + if let Some(next_tokens) = graph.get(&token) { + for next_token in next_tokens { + if !visited.contains(next_token) { + queue.push_back(next_token.clone()); + } + } + } + } + + // Verify all tokens are visited + let all_tokens: HashSet<_> = graph + .keys() + .chain(graph.values().flat_map(|v| v.iter())) + .collect(); + + for token in all_tokens { + if !visited.contains(token) { + return Err(EncodingError::InvalidInput(format!( + "Token {:?} is not connected to the main path", + token + ))); + } + } + + // Verify checked_token is reachable + if !visited.contains(checked_token) { + return Err(EncodingError::InvalidInput( + "Checked token is not reachable through swap path".to_string(), + )); + } + + Ok(()) +} + /// This strategy encoder is used for solutions that are sent directly to the pool. /// Only 1 solution with 1 swap is supported. pub struct ExecutorStrategyEncoder {} From 95edd5b1fe99fd96163dcf74c2a570a7c8a480a1 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 18:44:09 +0530 Subject: [PATCH 02/15] fix: checks in validations --- .../evm/strategy_encoder/strategy_encoders.rs | 81 +++++++++---------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 59c359d..26f61ad 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -242,26 +242,33 @@ fn validate_swaps(swaps: &[Swap]) -> Result<(), EncodingError> { // Single swaps don't need remainder handling if token_swaps.len() == 1 { + if token_swaps[0].split != 1.0 { + return Err(EncodingError::InvalidInput(format!( + "Single swap must have 100% split for token {:?}", + token + ))); + } continue; } // Check if exactly one swap has 0% split and it's the last one - let zero_splits: Vec<_> = token_swaps - .iter() - .enumerate() - .filter(|(_, s)| s.split == 0.0) - .collect(); - - if zero_splits.len() != 1 { - return Err(EncodingError::InvalidInput(format!( - "Token {:?} must have exactly one 0% split for remainder handling", - token - ))); + let mut found_zero_split = false; + for (i, swap) in token_swaps.iter().enumerate() { + match (swap.split == 0.0, i == token_swaps.len() - 1) { + (true, false) => { + return Err(EncodingError::InvalidInput(format!( + "The 0% split for token {:?} must be the last swap", + token + ))) + } + (true, true) => found_zero_split = true, + (false, _) => (), + } } - if zero_splits[0].0 != token_swaps.len() - 1 { + if !found_zero_split { return Err(EncodingError::InvalidInput(format!( - "The 0% split for token {:?} must be the last swap", + "Token {:?} must have exactly one 0% split for remainder handling", token ))); } @@ -300,56 +307,48 @@ fn validate_token_path_connectivity( checked_token: &Bytes, ) -> Result<(), EncodingError> { // Build directed graph of token flows - let mut graph: HashMap> = HashMap::new(); + let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); for swap in swaps { graph - .entry(swap.token_in.clone()) + .entry(&swap.token_in) .or_default() - .insert(swap.token_out.clone()); + .insert(&swap.token_out); } // BFS from given_token let mut visited = HashSet::new(); let mut queue = VecDeque::new(); - queue.push_back(given_token.clone()); + queue.push_back(given_token); while let Some(token) = queue.pop_front() { - if !visited.insert(token.clone()) { + if !visited.insert(token) { continue; } - if let Some(next_tokens) = graph.get(&token) { - for next_token in next_tokens { + // Early success check + if token == checked_token && visited.len() == graph.len() + 1 { + return Ok(()); + } + + if let Some(next_tokens) = graph.get(token) { + for &next_token in next_tokens { if !visited.contains(next_token) { - queue.push_back(next_token.clone()); + queue.push_back(next_token); } } } } - // Verify all tokens are visited - let all_tokens: HashSet<_> = graph - .keys() - .chain(graph.values().flat_map(|v| v.iter())) - .collect(); - - for token in all_tokens { - if !visited.contains(token) { - return Err(EncodingError::InvalidInput(format!( - "Token {:?} is not connected to the main path", - token - ))); - } - } - - // Verify checked_token is reachable + // If we get here, either checked_token wasn't reached or not all tokens were visited if !visited.contains(checked_token) { - return Err(EncodingError::InvalidInput( + Err(EncodingError::InvalidInput( "Checked token is not reachable through swap path".to_string(), - )); + )) + } else { + Err(EncodingError::InvalidInput( + "Some tokens are not connected to the main path".to_string(), + )) } - - Ok(()) } /// This strategy encoder is used for solutions that are sent directly to the pool. From b69aef9b8f1d253bb465a39669bd18aa5f355aa5 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 19:08:17 +0530 Subject: [PATCH 03/15] feat: add tests for split swap validations --- .../evm/strategy_encoder/strategy_encoders.rs | 275 +++++++++++++++++- 1 file changed, 273 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 26f61ad..0de3fb8 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -242,9 +242,9 @@ fn validate_swaps(swaps: &[Swap]) -> Result<(), EncodingError> { // Single swaps don't need remainder handling if token_swaps.len() == 1 { - if token_swaps[0].split != 1.0 { + if token_swaps[0].split != 0.0 { return Err(EncodingError::InvalidInput(format!( - "Single swap must have 100% split for token {:?}", + "Single swap must have 0% split for token {:?}", token ))); } @@ -779,4 +779,275 @@ mod tests { let _hex_calldata = encode(&calldata); println!("{}", _hex_calldata); } + + #[test] + fn test_validate_token_path_connectivity_single_swap() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let swaps = vec![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 result = validate_token_path_connectivity(&swaps, &weth, &dai); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_validate_token_path_connectivity_multiple_swaps() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let swaps = vec![ + Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5f64, + }, + Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: dai.clone(), + token_out: usdc.clone(), + split: 0f64, + }, + ]; + let result = validate_token_path_connectivity(&swaps, &weth, &usdc); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_validate_token_path_connectivity_multiple_swaps_failure() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); + + // Test case 1: Disconnected path + let disconnected_swaps = vec![ + Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5, + }, + // This swap is disconnected from the WETH->DAI path + Swap { + component: ProtocolComponent { + id: "pool2".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: wbtc.clone(), + token_out: usdc.clone(), + split: 0.0, + }, + ]; + let result = validate_token_path_connectivity(&disconnected_swaps, &weth, &usdc); + assert!(matches!( + result, + Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") + )); + + // Test case 2: Unreachable checked token + let unreachable_swaps = vec![Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 1.0, + }]; + let result = validate_token_path_connectivity(&unreachable_swaps, &weth, &usdc); + assert!(matches!( + result, + Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") + )); + + // Test case 3: Empty swaps + let empty_swaps: Vec = vec![]; + let result = validate_token_path_connectivity(&empty_swaps, &weth, &usdc); + assert!(matches!( + result, + Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") + )); + } + + #[test] + fn test_validate_swaps_single_swap() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let swaps = vec![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 result = validate_swaps(&swaps); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_validate_swaps_multiple_swaps() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + // Valid case: Multiple swaps with proper splits (50%, 30%, remainder) + let valid_swaps = vec![ + Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5, + }, + Swap { + component: ProtocolComponent { + id: "pool2".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.3, + }, + Swap { + component: ProtocolComponent { + id: "pool3".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.0, // Remainder (20%) + }, + ]; + assert!(validate_swaps(&valid_swaps).is_ok()); + } + + #[test] + fn test_validate_swaps_multiple_swaps_failures() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + + // Test case 1: Invalid - splits sum to 100% with no remainder + let invalid_total_swaps = vec![ + Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.7, + }, + Swap { + component: ProtocolComponent { + id: "pool2".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.3, + }, + ]; + assert!(matches!( + validate_swaps(&invalid_total_swaps), + Err(EncodingError::InvalidInput(msg)) if msg.contains("must have exactly one 0% split") + )); + + // Test case 2: Invalid - zero split not at end + let invalid_zero_position_swaps = vec![ + Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.0, + }, + Swap { + component: ProtocolComponent { + id: "pool2".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5, + }, + ]; + assert!(matches!( + validate_swaps(&invalid_zero_position_swaps), + Err(EncodingError::InvalidInput(msg)) if msg.contains("must be the last swap") + )); + + // Test case 3: Invalid - splits exceed 100% + let invalid_overflow_swaps = vec![ + Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.6, + }, + Swap { + component: ProtocolComponent { + id: "pool2".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.5, + }, + Swap { + component: ProtocolComponent { + id: "pool3".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: dai.clone(), + split: 0.0, + }, + ]; + assert!(matches!( + validate_swaps(&invalid_overflow_swaps), + Err(EncodingError::InvalidInput(msg)) if msg.contains("must be <100%") + )); + } } From b8013c6e7e3cc2d3190529ea1efb661bfa099dff Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 21:25:06 +0530 Subject: [PATCH 04/15] chore: move validation methods inside impl --- .../evm/strategy_encoder/strategy_encoders.rs | 316 +++++++++--------- 1 file changed, 166 insertions(+), 150 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 0de3fb8..070b166 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -67,6 +67,145 @@ impl SplitSwapStrategyEncoder { 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 }) } + + fn validate_swaps(&self, swaps: &[Swap]) -> Result<(), EncodingError> { + for swap in swaps { + if swap.split >= 1.0 { + return Err(EncodingError::InvalidInput(format!( + "Split percentage must be less than 1 (100%), got {}", + swap.split + ))); + } + } + + let mut swaps_by_token: HashMap> = HashMap::new(); + + for swap in swaps { + swaps_by_token + .entry(swap.token_in.clone()) + .or_default() + .push(swap); + } + + for (token, token_swaps) in swaps_by_token { + if token_swaps.is_empty() { + return Err(EncodingError::InvalidInput(format!( + "No swaps found for token {:?}", + token + ))); + } + + // Single swaps don't need remainder handling + if token_swaps.len() == 1 { + if token_swaps[0].split != 0.0 { + return Err(EncodingError::InvalidInput(format!( + "Single swap must have 0% split for token {:?}", + token + ))); + } + continue; + } + + // Check if exactly one swap has 0% split and it's the last one + let mut found_zero_split = false; + for (i, swap) in token_swaps.iter().enumerate() { + match (swap.split == 0.0, i == token_swaps.len() - 1) { + (true, false) => { + return Err(EncodingError::InvalidInput(format!( + "The 0% split for token {:?} must be the last swap", + token + ))) + } + (true, true) => found_zero_split = true, + (false, _) => (), + } + } + + if !found_zero_split { + return Err(EncodingError::InvalidInput(format!( + "Token {:?} must have exactly one 0% split for remainder handling", + token + ))); + } + + // Sum non-zero splits and validate each is >0% and <100% + let mut total_percentage = 0.0; + for swap in token_swaps + .iter() + .take(token_swaps.len() - 1) + { + if swap.split <= 0.0 { + return Err(EncodingError::InvalidInput(format!( + "Non-remainder splits must be >0% for token {:?}", + token + ))); + } + total_percentage += swap.split; + } + + // Total must be <100% to leave room for remainder + if total_percentage >= 1.0 { + return Err(EncodingError::InvalidInput(format!( + "Total of non-remainder splits for token {:?} must be <100%, got {}%", + token, + total_percentage * 100.0 + ))); + } + } + + Ok(()) + } + + fn validate_token_path_connectivity( + &self, + swaps: &[Swap], + given_token: &Bytes, + checked_token: &Bytes, + ) -> Result<(), EncodingError> { + // Build directed graph of token flows + let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); + for swap in swaps { + graph + .entry(&swap.token_in) + .or_default() + .insert(&swap.token_out); + } + + // BFS from given_token + let mut visited = HashSet::new(); + let mut queue = VecDeque::new(); + queue.push_back(given_token); + + while let Some(token) = queue.pop_front() { + if !visited.insert(token) { + continue; + } + + // Early success check + if token == checked_token && visited.len() == graph.len() + 1 { + return Ok(()); + } + + if let Some(next_tokens) = graph.get(token) { + for &next_token in next_tokens { + if !visited.contains(next_token) { + queue.push_back(next_token); + } + } + } + } + + // If we get here, either checked_token wasn't reached or not all tokens were visited + if !visited.contains(checked_token) { + Err(EncodingError::InvalidInput( + "Checked token is not reachable through swap path".to_string(), + )) + } else { + Err(EncodingError::InvalidInput( + "Some tokens are not connected to the main path".to_string(), + )) + } + } } impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} impl StrategyEncoder for SplitSwapStrategyEncoder { @@ -75,8 +214,8 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { solution: Solution, router_address: Bytes, ) -> Result<(Vec, Bytes), EncodingError> { - validate_swaps(&solution.swaps)?; - validate_token_path_connectivity( + self.validate_swaps(&solution.swaps)?; + self.validate_token_path_connectivity( &solution.swaps, &solution.given_token, &solution.checked_token, @@ -213,144 +352,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { } } -fn validate_swaps(swaps: &[Swap]) -> Result<(), EncodingError> { - for swap in swaps { - if swap.split >= 1.0 { - return Err(EncodingError::InvalidInput(format!( - "Split percentage must be less than 1 (100%), got {}", - swap.split - ))); - } - } - - let mut swaps_by_token: HashMap> = HashMap::new(); - - for swap in swaps { - swaps_by_token - .entry(swap.token_in.clone()) - .or_default() - .push(swap); - } - - for (token, token_swaps) in swaps_by_token { - if token_swaps.is_empty() { - return Err(EncodingError::InvalidInput(format!( - "No swaps found for token {:?}", - token - ))); - } - - // Single swaps don't need remainder handling - if token_swaps.len() == 1 { - if token_swaps[0].split != 0.0 { - return Err(EncodingError::InvalidInput(format!( - "Single swap must have 0% split for token {:?}", - token - ))); - } - continue; - } - - // Check if exactly one swap has 0% split and it's the last one - let mut found_zero_split = false; - for (i, swap) in token_swaps.iter().enumerate() { - match (swap.split == 0.0, i == token_swaps.len() - 1) { - (true, false) => { - return Err(EncodingError::InvalidInput(format!( - "The 0% split for token {:?} must be the last swap", - token - ))) - } - (true, true) => found_zero_split = true, - (false, _) => (), - } - } - - if !found_zero_split { - return Err(EncodingError::InvalidInput(format!( - "Token {:?} must have exactly one 0% split for remainder handling", - token - ))); - } - - // Sum non-zero splits and validate each is >0% and <100% - let mut total_percentage = 0.0; - for swap in token_swaps - .iter() - .take(token_swaps.len() - 1) - { - if swap.split <= 0.0 { - return Err(EncodingError::InvalidInput(format!( - "Non-remainder splits must be >0% for token {:?}", - token - ))); - } - total_percentage += swap.split; - } - - // Total must be <100% to leave room for remainder - if total_percentage >= 1.0 { - return Err(EncodingError::InvalidInput(format!( - "Total of non-remainder splits for token {:?} must be <100%, got {}%", - token, - total_percentage * 100.0 - ))); - } - } - - Ok(()) -} - -fn validate_token_path_connectivity( - swaps: &[Swap], - given_token: &Bytes, - checked_token: &Bytes, -) -> Result<(), EncodingError> { - // Build directed graph of token flows - let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); - for swap in swaps { - graph - .entry(&swap.token_in) - .or_default() - .insert(&swap.token_out); - } - - // BFS from given_token - let mut visited = HashSet::new(); - let mut queue = VecDeque::new(); - queue.push_back(given_token); - - while let Some(token) = queue.pop_front() { - if !visited.insert(token) { - continue; - } - - // Early success check - if token == checked_token && visited.len() == graph.len() + 1 { - return Ok(()); - } - - if let Some(next_tokens) = graph.get(token) { - for &next_token in next_tokens { - if !visited.contains(next_token) { - queue.push_back(next_token); - } - } - } - } - - // If we get here, either checked_token wasn't reached or not all tokens were visited - if !visited.contains(checked_token) { - Err(EncodingError::InvalidInput( - "Checked token is not reachable through swap path".to_string(), - )) - } else { - Err(EncodingError::InvalidInput( - "Some tokens are not connected to the main path".to_string(), - )) - } -} - /// This strategy encoder is used for solutions that are sent directly to the pool. /// Only 1 solution with 1 swap is supported. pub struct ExecutorStrategyEncoder {} @@ -780,8 +781,15 @@ mod tests { println!("{}", _hex_calldata); } + fn get_mock_split_swap_strategy_encoder() -> SplitSwapStrategyEncoder { + let private_key = + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); + SplitSwapStrategyEncoder::new(private_key, Chain::Ethereum).unwrap() + } + #[test] fn test_validate_token_path_connectivity_single_swap() { + let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let swaps = vec![Swap { @@ -794,12 +802,13 @@ mod tests { token_out: dai.clone(), split: 0f64, }]; - let result = validate_token_path_connectivity(&swaps, &weth, &dai); + let result = encoder.validate_token_path_connectivity(&swaps, &weth, &dai); assert_eq!(result, Ok(())); } #[test] fn test_validate_token_path_connectivity_multiple_swaps() { + let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -825,12 +834,13 @@ mod tests { split: 0f64, }, ]; - let result = validate_token_path_connectivity(&swaps, &weth, &usdc); + let result = encoder.validate_token_path_connectivity(&swaps, &weth, &usdc); assert_eq!(result, Ok(())); } #[test] fn test_validate_token_path_connectivity_multiple_swaps_failure() { + let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); @@ -860,7 +870,7 @@ mod tests { split: 0.0, }, ]; - let result = validate_token_path_connectivity(&disconnected_swaps, &weth, &usdc); + let result = encoder.validate_token_path_connectivity(&disconnected_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -877,7 +887,7 @@ mod tests { token_out: dai.clone(), split: 1.0, }]; - let result = validate_token_path_connectivity(&unreachable_swaps, &weth, &usdc); + let result = encoder.validate_token_path_connectivity(&unreachable_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -885,7 +895,7 @@ mod tests { // Test case 3: Empty swaps let empty_swaps: Vec = vec![]; - let result = validate_token_path_connectivity(&empty_swaps, &weth, &usdc); + let result = encoder.validate_token_path_connectivity(&empty_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -894,6 +904,7 @@ mod tests { #[test] fn test_validate_swaps_single_swap() { + let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let swaps = vec![Swap { @@ -906,7 +917,7 @@ mod tests { token_out: dai.clone(), split: 0f64, }]; - let result = validate_swaps(&swaps); + let result = encoder.validate_swaps(&swaps); assert_eq!(result, Ok(())); } @@ -948,7 +959,10 @@ mod tests { split: 0.0, // Remainder (20%) }, ]; - assert!(validate_swaps(&valid_swaps).is_ok()); + let encoder = get_mock_split_swap_strategy_encoder(); + assert!(encoder + .validate_swaps(&valid_swaps) + .is_ok()); } #[test] @@ -979,8 +993,9 @@ mod tests { split: 0.3, }, ]; + let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( - validate_swaps(&invalid_total_swaps), + encoder.validate_swaps(&invalid_total_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must have exactly one 0% split") )); @@ -1008,7 +1023,7 @@ mod tests { }, ]; assert!(matches!( - validate_swaps(&invalid_zero_position_swaps), + encoder.validate_swaps(&invalid_zero_position_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must be the last swap") )); @@ -1045,8 +1060,9 @@ mod tests { split: 0.0, }, ]; + let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( - validate_swaps(&invalid_overflow_swaps), + encoder.validate_swaps(&invalid_overflow_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must be <100%") )); } From b0dda205b09f8be0d97a1a68b4cd477a9ab5fc09 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 21:40:50 +0530 Subject: [PATCH 05/15] chore: break tests --- .../evm/strategy_encoder/strategy_encoders.rs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 070b166..65ea61c 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -69,6 +69,7 @@ impl SplitSwapStrategyEncoder { } fn validate_swaps(&self, swaps: &[Swap]) -> Result<(), EncodingError> { + let mut swaps_by_token: HashMap> = HashMap::new(); for swap in swaps { if swap.split >= 1.0 { return Err(EncodingError::InvalidInput(format!( @@ -76,11 +77,6 @@ impl SplitSwapStrategyEncoder { swap.split ))); } - } - - let mut swaps_by_token: HashMap> = HashMap::new(); - - for swap in swaps { swaps_by_token .entry(swap.token_in.clone()) .or_default() @@ -88,13 +84,6 @@ impl SplitSwapStrategyEncoder { } for (token, token_swaps) in swaps_by_token { - if token_swaps.is_empty() { - return Err(EncodingError::InvalidInput(format!( - "No swaps found for token {:?}", - token - ))); - } - // Single swaps don't need remainder handling if token_swaps.len() == 1 { if token_swaps[0].split != 0.0 { @@ -839,14 +828,13 @@ mod tests { } #[test] - fn test_validate_token_path_connectivity_multiple_swaps_failure() { + fn test_validate_token_path_connectivity_disconnected_path() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); - // Test case 1: Disconnected path let disconnected_swaps = vec![ Swap { component: ProtocolComponent { @@ -875,8 +863,15 @@ mod tests { result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") )); + } + + #[test] + fn test_validate_token_path_connectivity_unreachable_checked_token() { + let encoder = get_mock_split_swap_strategy_encoder(); + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - // Test case 2: Unreachable checked token let unreachable_swaps = vec![Swap { component: ProtocolComponent { id: "pool1".to_string(), @@ -892,8 +887,14 @@ mod tests { result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") )); + } + + #[test] + fn test_validate_token_path_connectivity_empty_swaps() { + let encoder = get_mock_split_swap_strategy_encoder(); + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); - // Test case 3: Empty swaps let empty_swaps: Vec = vec![]; let result = encoder.validate_token_path_connectivity(&empty_swaps, &weth, &usdc); assert!(matches!( @@ -966,11 +967,10 @@ mod tests { } #[test] - fn test_validate_swaps_multiple_swaps_failures() { + fn test_validate_swaps_no_remainder_split() { let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - // Test case 1: Invalid - splits sum to 100% with no remainder let invalid_total_swaps = vec![ Swap { component: ProtocolComponent { @@ -998,8 +998,13 @@ mod tests { encoder.validate_swaps(&invalid_total_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must have exactly one 0% split") )); + } + + #[test] + fn test_validate_swaps_zero_split_not_at_end() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - // Test case 2: Invalid - zero split not at end let invalid_zero_position_swaps = vec![ Swap { component: ProtocolComponent { @@ -1022,12 +1027,18 @@ mod tests { split: 0.5, }, ]; + let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( encoder.validate_swaps(&invalid_zero_position_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must be the last swap") )); + } + + #[test] + fn test_validate_swaps_splits_exceed_hundred_percent() { + let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); - // Test case 3: Invalid - splits exceed 100% let invalid_overflow_swaps = vec![ Swap { component: ProtocolComponent { From 4d97c3f16d263c975f1f42bebae9666af789eb10 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 22:18:03 +0530 Subject: [PATCH 06/15] feat: add tests for wrap unwrap case --- .../evm/strategy_encoder/strategy_encoders.rs | 67 +++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 65ea61c..ec19d1b 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -151,6 +151,19 @@ impl SplitSwapStrategyEncoder { given_token: &Bytes, checked_token: &Bytes, ) -> Result<(), EncodingError> { + // Special case: If given_token is ETH or checked_token is ETH, treat it as WETH for path + // validation + let eth_address = Bytes::from_str("0x0000000000000000000000000000000000000000") + .map_err(|_| EncodingError::FatalError("Invalid ETH address".to_string()))?; + let weth_address = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + .map_err(|_| EncodingError::FatalError("Invalid WETH address".to_string()))?; + + let validation_given = + if given_token == ð_address { &weth_address } else { given_token }; + + let validation_checked = + if checked_token == ð_address { &weth_address } else { checked_token }; + // Build directed graph of token flows let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); for swap in swaps { @@ -160,10 +173,10 @@ impl SplitSwapStrategyEncoder { .insert(&swap.token_out); } - // BFS from given_token + // BFS from validation_given let mut visited = HashSet::new(); let mut queue = VecDeque::new(); - queue.push_back(given_token); + queue.push_back(validation_given); while let Some(token) = queue.pop_front() { if !visited.insert(token) { @@ -171,7 +184,7 @@ impl SplitSwapStrategyEncoder { } // Early success check - if token == checked_token && visited.len() == graph.len() + 1 { + if token == validation_checked && visited.len() == graph.len() + 1 { return Ok(()); } @@ -185,7 +198,7 @@ impl SplitSwapStrategyEncoder { } // If we get here, either checked_token wasn't reached or not all tokens were visited - if !visited.contains(checked_token) { + if !visited.contains(validation_checked) { Err(EncodingError::InvalidInput( "Checked token is not reachable through swap path".to_string(), )) @@ -1077,4 +1090,50 @@ mod tests { Err(EncodingError::InvalidInput(msg)) if msg.contains("must be <100%") )); } + + #[test] + fn test_validate_token_path_connectivity_wrap_eth_i() { + let encoder = get_mock_split_swap_strategy_encoder(); + + let eth = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(); + + let swaps = vec![Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: weth.clone(), + token_out: usdc.clone(), + split: 0f64, + }]; + + let result = encoder.validate_token_path_connectivity(&swaps, ð, &usdc); + assert_eq!(result, Ok(())); + } + + #[test] + fn test_validate_token_path_connectivity_wrap_eth_checked_token() { + let encoder = get_mock_split_swap_strategy_encoder(); + + let eth = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(); + let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); + let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(); + + let swaps = vec![Swap { + component: ProtocolComponent { + id: "pool1".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: usdc.clone(), + token_out: weth.clone(), + split: 0f64, + }]; + + let result = encoder.validate_token_path_connectivity(&swaps, &usdc, ð); + assert_eq!(result, Ok(())); + } } From 4da50ddc9e7d7cb35ed37403b4a745ba9a87cc0b Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 22:32:05 +0530 Subject: [PATCH 07/15] chore: naming --- .../evm/strategy_encoder/strategy_encoders.rs | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index ec19d1b..c0f422a 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -13,7 +13,7 @@ use crate::encoding::{ errors::EncodingError, evm::{ approvals::permit2::Permit2, - constants::WETH_ADDRESS, + constants::{NATIVE_ADDRESS, WETH_ADDRESS}, swap_encoder::SWAP_ENCODER_REGISTRY, utils::{biguint_to_u256, bytes_to_address, encode_input, percentage_to_uint24}, }, @@ -68,7 +68,7 @@ impl SplitSwapStrategyEncoder { Ok(Self { permit2: Permit2::new(signer_pk, chain)?, selector }) } - fn validate_swaps(&self, swaps: &[Swap]) -> Result<(), EncodingError> { + fn validate_split_percentages(&self, swaps: &[Swap]) -> Result<(), EncodingError> { let mut swaps_by_token: HashMap> = HashMap::new(); for swap in swaps { if swap.split >= 1.0 { @@ -145,7 +145,7 @@ impl SplitSwapStrategyEncoder { Ok(()) } - fn validate_token_path_connectivity( + fn validate_swap_path( &self, swaps: &[Swap], given_token: &Bytes, @@ -153,16 +153,10 @@ impl SplitSwapStrategyEncoder { ) -> Result<(), EncodingError> { // Special case: If given_token is ETH or checked_token is ETH, treat it as WETH for path // validation - let eth_address = Bytes::from_str("0x0000000000000000000000000000000000000000") - .map_err(|_| EncodingError::FatalError("Invalid ETH address".to_string()))?; - let weth_address = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") - .map_err(|_| EncodingError::FatalError("Invalid WETH address".to_string()))?; + let given_token = if *given_token == *NATIVE_ADDRESS { &WETH_ADDRESS } else { given_token }; - let validation_given = - if given_token == ð_address { &weth_address } else { given_token }; - - let validation_checked = - if checked_token == ð_address { &weth_address } else { checked_token }; + let checked_token = + if *checked_token == *NATIVE_ADDRESS { &WETH_ADDRESS } else { checked_token }; // Build directed graph of token flows let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); @@ -176,7 +170,7 @@ impl SplitSwapStrategyEncoder { // BFS from validation_given let mut visited = HashSet::new(); let mut queue = VecDeque::new(); - queue.push_back(validation_given); + queue.push_back(given_token); while let Some(token) = queue.pop_front() { if !visited.insert(token) { @@ -184,7 +178,7 @@ impl SplitSwapStrategyEncoder { } // Early success check - if token == validation_checked && visited.len() == graph.len() + 1 { + if token == checked_token && visited.len() == graph.len() + 1 { return Ok(()); } @@ -198,7 +192,7 @@ impl SplitSwapStrategyEncoder { } // If we get here, either checked_token wasn't reached or not all tokens were visited - if !visited.contains(validation_checked) { + if !visited.contains(checked_token) { Err(EncodingError::InvalidInput( "Checked token is not reachable through swap path".to_string(), )) @@ -216,12 +210,8 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { solution: Solution, router_address: Bytes, ) -> Result<(Vec, Bytes), EncodingError> { - self.validate_swaps(&solution.swaps)?; - self.validate_token_path_connectivity( - &solution.swaps, - &solution.given_token, - &solution.checked_token, - )?; + self.validate_split_percentages(&solution.swaps)?; + self.validate_swap_path(&solution.swaps, &solution.given_token, &solution.checked_token)?; let (permit, signature) = self.permit2.get_permit( &router_address, &solution.sender, @@ -790,7 +780,7 @@ mod tests { } #[test] - fn test_validate_token_path_connectivity_single_swap() { + fn test_validate_path_single_swap() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -804,12 +794,12 @@ mod tests { token_out: dai.clone(), split: 0f64, }]; - let result = encoder.validate_token_path_connectivity(&swaps, &weth, &dai); + let result = encoder.validate_swap_path(&swaps, &weth, &dai); assert_eq!(result, Ok(())); } #[test] - fn test_validate_token_path_connectivity_multiple_swaps() { + fn test_validate_path_multiple_swaps() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -836,12 +826,12 @@ mod tests { split: 0f64, }, ]; - let result = encoder.validate_token_path_connectivity(&swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&swaps, &weth, &usdc); assert_eq!(result, Ok(())); } #[test] - fn test_validate_token_path_connectivity_disconnected_path() { + fn test_validate_path_disconnected() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -871,7 +861,7 @@ mod tests { split: 0.0, }, ]; - let result = encoder.validate_token_path_connectivity(&disconnected_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&disconnected_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -879,7 +869,7 @@ mod tests { } #[test] - fn test_validate_token_path_connectivity_unreachable_checked_token() { + fn test_validate_path_unreachable_checked_token() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -895,7 +885,7 @@ mod tests { token_out: dai.clone(), split: 1.0, }]; - let result = encoder.validate_token_path_connectivity(&unreachable_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&unreachable_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -903,13 +893,13 @@ mod tests { } #[test] - fn test_validate_token_path_connectivity_empty_swaps() { + fn test_validate_path_empty_swaps() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let empty_swaps: Vec = vec![]; - let result = encoder.validate_token_path_connectivity(&empty_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&empty_swaps, &weth, &usdc); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -931,7 +921,7 @@ mod tests { token_out: dai.clone(), split: 0f64, }]; - let result = encoder.validate_swaps(&swaps); + let result = encoder.validate_split_percentages(&swaps); assert_eq!(result, Ok(())); } @@ -975,7 +965,7 @@ mod tests { ]; let encoder = get_mock_split_swap_strategy_encoder(); assert!(encoder - .validate_swaps(&valid_swaps) + .validate_split_percentages(&valid_swaps) .is_ok()); } @@ -1008,7 +998,7 @@ mod tests { ]; let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( - encoder.validate_swaps(&invalid_total_swaps), + encoder.validate_split_percentages(&invalid_total_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must have exactly one 0% split") )); } @@ -1042,7 +1032,7 @@ mod tests { ]; let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( - encoder.validate_swaps(&invalid_zero_position_swaps), + encoder.validate_split_percentages(&invalid_zero_position_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must be the last swap") )); } @@ -1086,13 +1076,13 @@ mod tests { ]; let encoder = get_mock_split_swap_strategy_encoder(); assert!(matches!( - encoder.validate_swaps(&invalid_overflow_swaps), + encoder.validate_split_percentages(&invalid_overflow_swaps), Err(EncodingError::InvalidInput(msg)) if msg.contains("must be <100%") )); } #[test] - fn test_validate_token_path_connectivity_wrap_eth_i() { + fn test_validate_path_wrap_eth_given_token() { let encoder = get_mock_split_swap_strategy_encoder(); let eth = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(); @@ -1110,7 +1100,7 @@ mod tests { split: 0f64, }]; - let result = encoder.validate_token_path_connectivity(&swaps, ð, &usdc); + let result = encoder.validate_swap_path(&swaps, ð, &usdc); assert_eq!(result, Ok(())); } @@ -1133,7 +1123,7 @@ mod tests { split: 0f64, }]; - let result = encoder.validate_token_path_connectivity(&swaps, &usdc, ð); + let result = encoder.validate_swap_path(&swaps, &usdc, ð); assert_eq!(result, Ok(())); } } From 2f0013a9344e18d3107d8a9ac51b9576e4338979 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 22:33:34 +0530 Subject: [PATCH 08/15] chore: naming --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index c0f422a..620ecfa 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -907,7 +907,7 @@ mod tests { } #[test] - fn test_validate_swaps_single_swap() { + fn test_validate_swap_single() { let encoder = get_mock_split_swap_strategy_encoder(); let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); @@ -926,7 +926,7 @@ mod tests { } #[test] - fn test_validate_swaps_multiple_swaps() { + fn test_validate_swaps_multiple() { let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); From 576f89d24ca25ab37ae59b4db97cbff946d6da58 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 4 Feb 2025 23:30:23 +0530 Subject: [PATCH 09/15] fix: deprecated signature --- Cargo.lock | 174 ++++++++++++++++++-------- Cargo.toml | 2 +- src/encoding/evm/approvals/permit2.rs | 5 +- 3 files changed, 123 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e678f46..afd1688 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8ebf106e84a1c37f86244df7da0c7587e697b71a0d565cce079449b85ac6f8" +checksum = "bbcc41e8a11a4975b18ec6afba2cc48d591fa63336a4c526dacb50479a8d6b35" dependencies = [ "alloy-consensus", "alloy-core", @@ -82,20 +82,35 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed961a48297c732a5d97ee321aa8bb5009ecadbcb077d8bec90cb54e651629" +checksum = "f4138dc275554afa6f18c4217262ac9388790b2fc393c2dfe03c51d357abf013" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", + "alloy-trie", "auto_impl", "c-kzg", "derive_more", "serde", ] +[[package]] +name = "alloy-consensus-any" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa04e1882c31288ce1028fdf31b6ea94cfa9eafa2e497f903ded631c8c6a42c" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-core" version = "0.8.19" @@ -140,9 +155,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ffc577390ce50234e02d841214b3dc0bea6aaaae8e04bbf3cb82e9a45da9eb" +checksum = "cabf647eb4650c91a9d38cb6f972bb320009e7e9d61765fb688a86f1563b33e8" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -152,9 +167,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69e06cf9c37be824b9d26d6d101114fdde6af0c87de2828b414c05c4b3daa71" +checksum = "52dd5869ed09e399003e0e0ec6903d981b2a92e74c5d37e6b40890bad2517526" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -170,12 +185,14 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde15e14944a88bd6a57d325e9a49b75558746fe16aaccc79713ae50a6a9574c" +checksum = "e7d2a7fe5c1a9bd6793829ea21a636f30fc2b3f5d2e7418ba86d96e41dd1f460" dependencies = [ + "alloy-eips", "alloy-primitives", "alloy-serde", + "alloy-trie", "serde", ] @@ -193,29 +210,31 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5979e0d5a7bf9c7eb79749121e8256e59021af611322aee56e77e20776b4b3" +checksum = "2008bedb8159a255b46b7c8614516eda06679ea82f620913679afbd8031fea72" dependencies = [ "alloy-primitives", "alloy-sol-types", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.11", "tracing", ] [[package]] name = "alloy-network" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "204237129086ce5dc17a58025e93739b01b45313841f98fa339eb1d780511e57" +checksum = "4556f01fe41d0677495df10a648ddcf7ce118b0e8aa9642a0e2b6dd1fb7259de" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", "alloy-primitives", + "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -223,14 +242,16 @@ dependencies = [ "async-trait", "auto_impl", "futures-utils-wasm", - "thiserror 1.0.69", + "serde", + "serde_json", + "thiserror 2.0.11", ] [[package]] name = "alloy-network-primitives" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514f70ee2a953db21631cd817b13a1571474ec77ddc03d47616d5e8203489fde" +checksum = "f31c3c6b71340a1d076831823f09cb6e02de01de5c6630a9631bdb36f947ff80" dependencies = [ "alloy-consensus", "alloy-eips", @@ -268,9 +289,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4814d141ede360bb6cd1b4b064f1aab9de391e7c4d0d4d50ac89ea4bc1e25fbd" +checksum = "5a22c4441b3ebe2d77fa9cf629ba68c3f713eb91779cff84275393db97eddd82" dependencies = [ "alloy-chains", "alloy-consensus", @@ -296,7 +317,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -327,9 +348,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc2bd1e7403463a5f2c61e955bcc9d3072b63aa177442b0f9aa6a6d22a941e3" +checksum = "d06a292b37e182e514903ede6e623b9de96420e8109ce300da288a96d88b7e4b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -350,9 +371,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea9bf1abdd506f985a53533f5ac01296bcd6102c5e139bbc5d40bc468d2c916" +checksum = "9383845dd924939e7ab0298bbfe231505e20928907d7905aa3bf112287305e06" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -361,29 +382,41 @@ dependencies = [ ] [[package]] -name = "alloy-rpc-types-eth" -version = "0.5.4" +name = "alloy-rpc-types-any" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b034779a4850b4b03f5be5ea674a1cf7d746b2da762b34d1860ab45e48ca27" +checksum = "ca445cef0eb6c2cf51cfb4e214fbf1ebd00893ae2e6f3b944c8101b07990f988" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0938bc615c02421bd86c1733ca7205cc3d99a122d9f9bff05726bd604b76a5c2" dependencies = [ "alloy-consensus", + "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-sol-types", - "derive_more", "itertools 0.13.0", "serde", "serde_json", + "thiserror 2.0.11", ] [[package]] name = "alloy-serde" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028e72eaa9703e4882344983cfe7636ce06d8cce104a78ea62fd19b46659efc4" +checksum = "ae0465c71d4dced7525f408d84873aeebb71faf807d22d74c4a426430ccd9b55" dependencies = [ "alloy-primitives", "serde", @@ -392,9 +425,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592c185d7100258c041afac51877660c7bf6213447999787197db4842f0e938e" +checksum = "9bfa395ad5cc952c82358d31e4c68b27bf4a89a5456d9b27e226e77dac50e4ff" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -403,14 +436,14 @@ dependencies = [ "auto_impl", "elliptic-curve", "k256", - "thiserror 1.0.69", + "thiserror 2.0.11", ] [[package]] name = "alloy-signer-aws" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a406102908a4e51834f32c4e5c1b29aa2c407b3fd23a5cad129c28b56d85e1b8" +checksum = "0eb06810c34427d499863817eb506acf57cb9ded9224b374116cae4e22dbd4e9" dependencies = [ "alloy-consensus", "alloy-network", @@ -420,15 +453,15 @@ dependencies = [ "aws-sdk-kms", "k256", "spki", - "thiserror 1.0.69", + "thiserror 2.0.11", "tracing", ] [[package]] name = "alloy-signer-gcp" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d363e12280cb43747d3b62a1e6f00d595bc1a56464bb20200c6b6ca5d68185" +checksum = "d629e63fec8802ad53706d46e8eceeeae2b135c6648d0de41669a523bf17df4a" dependencies = [ "alloy-consensus", "alloy-network", @@ -438,15 +471,15 @@ dependencies = [ "gcloud-sdk", "k256", "spki", - "thiserror 1.0.69", + "thiserror 2.0.11", "tracing", ] [[package]] name = "alloy-signer-ledger" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a642c9f66ac73ae0d5398ce7ce3ce5bdfad5658d549abd48ea48962e585dca" +checksum = "b426789566a19252cb46b757d91543a6f8e70330c72f312b86c5878595d092ef" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -458,15 +491,15 @@ dependencies = [ "coins-ledger", "futures-util", "semver 1.0.24", - "thiserror 1.0.69", + "thiserror 2.0.11", "tracing", ] [[package]] name = "alloy-signer-local" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6614f02fc1d5b079b2a4a5320018317b506fd0a6d67c1fd5542a71201724986c" +checksum = "fbdc63ce9eda1283fcbaca66ba4a414b841c0e3edbeef9c86a71242fc9e84ccc" dependencies = [ "alloy-consensus", "alloy-network", @@ -475,7 +508,7 @@ dependencies = [ "async-trait", "k256", "rand", - "thiserror 1.0.69", + "thiserror 2.0.11", ] [[package]] @@ -550,9 +583,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be77579633ebbc1266ae6fd7694f75c408beb1aeb6865d0b18f22893c265a061" +checksum = "d17722a198f33bbd25337660787aea8b8f57814febb7c746bc30407bdfc39448" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -560,7 +593,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.11", "tokio", "tower 0.5.2", "tracing", @@ -570,9 +603,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.5.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd1a5d0827939847983b46f2f79510361f901dc82f8e3c38ac7397af142c6e" +checksum = "6e1509599021330a31c4a6816b655e34bf67acb1cc03c564e09fd8754ff6c5de" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -583,6 +616,22 @@ dependencies = [ "url", ] +[[package]] +name = "alloy-trie" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -733,6 +782,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "async-compression" @@ -2590,6 +2642,19 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "nybbles" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "object" version = "0.36.7" @@ -3644,6 +3709,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -4470,9 +4538,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.2.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ed9d8b15c7fb594d72bfb4b5a276f3d2029333cd93a932f376f5937f6f80ee" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" dependencies = [ "futures", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index d980b9f..53bda10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ thiserror = "1.0.69" tokio = { version = "1.38.0", features = ["full"] } chrono = "0.4.39" -alloy = { version = "0.5.4", features = ["providers", "rpc-types-eth", "eip712", "signer-local"], optional = true } +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.46.0" } diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 1423ce0..7460cae 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -7,9 +7,7 @@ use alloy::{ signers::{local::PrivateKeySigner, SignerSync}, transports::BoxTransport, }; -#[allow(deprecated)] -use alloy_primitives::Signature; -use alloy_primitives::B256; +use alloy_primitives::{PrimitiveSignature as Signature, B256}; use alloy_sol_types::{eip712_domain, sol, SolStruct, SolValue}; use chrono::Utc; use num_bigint::BigUint; @@ -117,7 +115,6 @@ impl Permit2 { } } /// Creates permit single and signature - #[allow(deprecated)] pub fn get_permit( &self, spender: &Bytes, From 25a26f21be8949a6f4ca548fd8c9d908ad5285f4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 4 Feb 2025 18:17:33 +0000 Subject: [PATCH 10/15] chore(release): 0.30.1 [skip ci] ## [0.30.1](https://github.com/propeller-heads/tycho-execution/compare/0.30.0...0.30.1) (2025-02-04) ### Bug Fixes * deprecated signature ([576f89d](https://github.com/propeller-heads/tycho-execution/commit/576f89d24ca25ab37ae59b4db97cbff946d6da58)) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d38cc..960b704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.30.1](https://github.com/propeller-heads/tycho-execution/compare/0.30.0...0.30.1) (2025-02-04) + + +### Bug Fixes + +* deprecated signature ([576f89d](https://github.com/propeller-heads/tycho-execution/commit/576f89d24ca25ab37ae59b4db97cbff946d6da58)) + ## [0.30.0](https://github.com/propeller-heads/tycho-execution/compare/0.29.1...0.30.0) (2025-02-04) diff --git a/Cargo.lock b/Cargo.lock index afd1688..e35c23e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4231,7 +4231,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.30.0" +version = "0.30.1" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 53bda10..1c7e8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.30.0" +version = "0.30.1" edition = "2021" [dependencies] From c787f5e722ad8a9f9a24e6ea09f59dfcf5f82239 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 5 Feb 2025 00:14:09 +0530 Subject: [PATCH 11/15] fix: use native action to validate path --- .../evm/strategy_encoder/strategy_encoders.rs | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 620ecfa..508f0c0 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -95,8 +95,8 @@ impl SplitSwapStrategyEncoder { continue; } - // Check if exactly one swap has 0% split and it's the last one let mut found_zero_split = false; + let mut total_percentage = 0.0; for (i, swap) in token_swaps.iter().enumerate() { match (swap.split == 0.0, i == token_swaps.len() - 1) { (true, false) => { @@ -106,7 +106,15 @@ impl SplitSwapStrategyEncoder { ))) } (true, true) => found_zero_split = true, - (false, _) => (), + (false, _) => { + if swap.split <= 0.0 { + return Err(EncodingError::InvalidInput(format!( + "Non-remainder splits must be >0% for token {:?}", + token + ))); + } + total_percentage += swap.split; + } } } @@ -117,21 +125,6 @@ impl SplitSwapStrategyEncoder { ))); } - // Sum non-zero splits and validate each is >0% and <100% - let mut total_percentage = 0.0; - for swap in token_swaps - .iter() - .take(token_swaps.len() - 1) - { - if swap.split <= 0.0 { - return Err(EncodingError::InvalidInput(format!( - "Non-remainder splits must be >0% for token {:?}", - token - ))); - } - total_percentage += swap.split; - } - // Total must be <100% to leave room for remainder if total_percentage >= 1.0 { return Err(EncodingError::InvalidInput(format!( @@ -150,13 +143,26 @@ impl SplitSwapStrategyEncoder { swaps: &[Swap], given_token: &Bytes, checked_token: &Bytes, + native_action: &Option, ) -> Result<(), EncodingError> { - // Special case: If given_token is ETH or checked_token is ETH, treat it as WETH for path - // validation - let given_token = if *given_token == *NATIVE_ADDRESS { &WETH_ADDRESS } else { given_token }; + // Convert ETH to WETH only if there's a corresponding wrap/unwrap action + let given_token = if *given_token == *NATIVE_ADDRESS { + match native_action { + Some(NativeAction::Wrap) => &WETH_ADDRESS, + _ => given_token, + } + } else { + given_token + }; - let checked_token = - if *checked_token == *NATIVE_ADDRESS { &WETH_ADDRESS } else { checked_token }; + let checked_token = if *checked_token == *NATIVE_ADDRESS { + match native_action { + Some(NativeAction::Unwrap) => &WETH_ADDRESS, + _ => checked_token, + } + } else { + checked_token + }; // Build directed graph of token flows let mut graph: HashMap<&Bytes, HashSet<&Bytes>> = HashMap::new(); @@ -211,7 +217,12 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { router_address: Bytes, ) -> Result<(Vec, Bytes), EncodingError> { self.validate_split_percentages(&solution.swaps)?; - self.validate_swap_path(&solution.swaps, &solution.given_token, &solution.checked_token)?; + self.validate_swap_path( + &solution.swaps, + &solution.given_token, + &solution.checked_token, + &solution.native_action, + )?; let (permit, signature) = self.permit2.get_permit( &router_address, &solution.sender, @@ -794,7 +805,7 @@ mod tests { token_out: dai.clone(), split: 0f64, }]; - let result = encoder.validate_swap_path(&swaps, &weth, &dai); + let result = encoder.validate_swap_path(&swaps, &weth, &dai, &None); assert_eq!(result, Ok(())); } @@ -826,7 +837,7 @@ mod tests { split: 0f64, }, ]; - let result = encoder.validate_swap_path(&swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&swaps, &weth, &usdc, &None); assert_eq!(result, Ok(())); } @@ -861,7 +872,7 @@ mod tests { split: 0.0, }, ]; - let result = encoder.validate_swap_path(&disconnected_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&disconnected_swaps, &weth, &usdc, &None); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -885,7 +896,7 @@ mod tests { token_out: dai.clone(), split: 1.0, }]; - let result = encoder.validate_swap_path(&unreachable_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&unreachable_swaps, &weth, &usdc, &None); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -899,7 +910,7 @@ mod tests { let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let empty_swaps: Vec = vec![]; - let result = encoder.validate_swap_path(&empty_swaps, &weth, &usdc); + let result = encoder.validate_swap_path(&empty_swaps, &weth, &usdc, &None); assert!(matches!( result, Err(EncodingError::InvalidInput(msg)) if msg.contains("not reachable through swap path") @@ -1100,7 +1111,7 @@ mod tests { split: 0f64, }]; - let result = encoder.validate_swap_path(&swaps, ð, &usdc); + let result = encoder.validate_swap_path(&swaps, ð, &usdc, &Some(NativeAction::Wrap)); assert_eq!(result, Ok(())); } @@ -1123,7 +1134,7 @@ mod tests { split: 0f64, }]; - let result = encoder.validate_swap_path(&swaps, &usdc, ð); + let result = encoder.validate_swap_path(&swaps, &usdc, ð, &Some(NativeAction::Unwrap)); assert_eq!(result, Ok(())); } } From 7b7226356d3bde61da987946dbc10ae3eec33722 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 5 Feb 2025 00:18:26 +0530 Subject: [PATCH 12/15] fix: get_mock_split_swap_strategy_encoder --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 5d43a96..f310a58 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -818,7 +818,8 @@ mod tests { fn get_mock_split_swap_strategy_encoder() -> SplitSwapStrategyEncoder { let private_key = "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - SplitSwapStrategyEncoder::new(private_key, Chain::Ethereum).unwrap() + let swap_encoder_registry = get_swap_encoder_registry(); + SplitSwapStrategyEncoder::new(private_key, Chain::Ethereum, swap_encoder_registry).unwrap() } #[test] From 3dcb8aee7b07d47b91a477dfa07f04dac605ece8 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Wed, 5 Feb 2025 10:10:43 +0000 Subject: [PATCH 13/15] chore: Use tycho_core::dto objects instead of tycho_core::models --- don't change below this line --- ENG-4169 Took 11 minutes --- examples/quickstart/main.rs | 5 ++++- src/encoding/evm/approvals/permit2.rs | 2 +- src/encoding/evm/models.rs | 2 +- .../evm/strategy_encoder/strategy_encoder_registry.rs | 2 +- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 2 +- src/encoding/evm/swap_encoder/swap_encoder_registry.rs | 2 +- src/encoding/evm/tycho_encoder.rs | 2 +- src/encoding/strategy_encoder.rs | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index ee89104..bef3e1a 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -1,7 +1,10 @@ use std::str::FromStr; use num_bigint::BigUint; -use tycho_core::{dto::ProtocolComponent, models::Chain, Bytes}; +use tycho_core::{ + dto::{Chain, ProtocolComponent}, + Bytes, +}; use tycho_execution::encoding::{ evm::{ strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry, diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 7460cae..b189d08 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -12,7 +12,7 @@ use alloy_sol_types::{eip712_domain, sol, SolStruct, SolValue}; use chrono::Utc; use num_bigint::BigUint; use tokio::runtime::Runtime; -use tycho_core::{models::Chain, Bytes}; +use tycho_core::{dto::Chain, Bytes}; use crate::encoding::{ errors::EncodingError, diff --git a/src/encoding/evm/models.rs b/src/encoding/evm/models.rs index 68121bf..706bc46 100644 --- a/src/encoding/evm/models.rs +++ b/src/encoding/evm/models.rs @@ -1,4 +1,4 @@ -use tycho_core::models::Chain; +use tycho_core::dto::Chain; pub struct ChainId(u64); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index 5554c9d..94a8253 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use tycho_core::models::Chain; +use tycho_core::dto::Chain; use crate::encoding::{ errors::EncodingError, diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 62a873e..df08515 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -3,7 +3,7 @@ use std::{cmp::max, collections::HashSet, str::FromStr}; use alloy_primitives::{aliases::U24, FixedBytes, U256, U8}; use alloy_sol_types::SolValue; use num_bigint::BigUint; -use tycho_core::{keccak256, models::Chain, Bytes}; +use tycho_core::{dto::Chain, keccak256, Bytes}; use crate::encoding::{ errors::EncodingError, diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index 639dfc0..b04e580 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fs}; -use tycho_core::models::Chain; +use tycho_core::dto::Chain; use crate::encoding::{ errors::EncodingError, evm::swap_encoder::builder::SwapEncoderBuilder, diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index 1a93400..23afbbf 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -104,7 +104,7 @@ impl TychoEncoder for EVMTychoEncoder { #[cfg(test)] mod tests { - use tycho_core::{dto::ProtocolComponent, models::Chain}; + use tycho_core::dto::{Chain, ProtocolComponent}; use super::*; use crate::encoding::{ diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 04d23e7..1b5013b 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,4 +1,4 @@ -use tycho_core::{models::Chain, Bytes}; +use tycho_core::{dto::Chain, Bytes}; use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::SwapEncoder}; From 9e61261a51506372786707cec4b035e84b665912 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Roy <42067944+royvardhan@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:23:10 +0530 Subject: [PATCH 14/15] Update strategy_encoders.rs Co-authored-by: Tamara --- src/encoding/evm/strategy_encoder/strategy_encoders.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 31343f9..7a33c85 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -113,9 +113,9 @@ impl SplitSwapStrategyEncoder { } (true, true) => found_zero_split = true, (false, _) => { - if swap.split <= 0.0 { + if swap.split < 0.0 { return Err(EncodingError::InvalidInput(format!( - "Non-remainder splits must be >0% for token {:?}", + "All splits must be >= 0% for token {:?}", token ))); } From cb01f95af9b55c0eeb694bf5fd015e5d3e82973f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 5 Feb 2025 15:03:14 +0000 Subject: [PATCH 15/15] chore(release): 0.31.0 [skip ci] ## [0.31.0](https://github.com/propeller-heads/tycho-execution/compare/0.30.1...0.31.0) (2025-02-05) ### Features * add tests for split swap validations ([b69aef9](https://github.com/propeller-heads/tycho-execution/commit/b69aef9b8f1d253bb465a39669bd18aa5f355aa5)) * add tests for wrap unwrap case ([4d97c3f](https://github.com/propeller-heads/tycho-execution/commit/4d97c3f16d263c975f1f42bebae9666af789eb10)) * add validation for split swap ([f80ffa9](https://github.com/propeller-heads/tycho-execution/commit/f80ffa924f1da626bef0751c92c09fb133d2ba85)) ### Bug Fixes * checks in validations ([95edd5b](https://github.com/propeller-heads/tycho-execution/commit/95edd5b1fe99fd96163dcf74c2a570a7c8a480a1)) * get_mock_split_swap_strategy_encoder ([7b72263](https://github.com/propeller-heads/tycho-execution/commit/7b7226356d3bde61da987946dbc10ae3eec33722)) * use native action to validate path ([c787f5e](https://github.com/propeller-heads/tycho-execution/commit/c787f5e722ad8a9f9a24e6ea09f59dfcf5f82239)) --- CHANGELOG.md | 16 ++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 960b704..98553bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [0.31.0](https://github.com/propeller-heads/tycho-execution/compare/0.30.1...0.31.0) (2025-02-05) + + +### Features + +* add tests for split swap validations ([b69aef9](https://github.com/propeller-heads/tycho-execution/commit/b69aef9b8f1d253bb465a39669bd18aa5f355aa5)) +* add tests for wrap unwrap case ([4d97c3f](https://github.com/propeller-heads/tycho-execution/commit/4d97c3f16d263c975f1f42bebae9666af789eb10)) +* add validation for split swap ([f80ffa9](https://github.com/propeller-heads/tycho-execution/commit/f80ffa924f1da626bef0751c92c09fb133d2ba85)) + + +### Bug Fixes + +* checks in validations ([95edd5b](https://github.com/propeller-heads/tycho-execution/commit/95edd5b1fe99fd96163dcf74c2a570a7c8a480a1)) +* get_mock_split_swap_strategy_encoder ([7b72263](https://github.com/propeller-heads/tycho-execution/commit/7b7226356d3bde61da987946dbc10ae3eec33722)) +* use native action to validate path ([c787f5e](https://github.com/propeller-heads/tycho-execution/commit/c787f5e722ad8a9f9a24e6ea09f59dfcf5f82239)) + ## [0.30.1](https://github.com/propeller-heads/tycho-execution/compare/0.30.0...0.30.1) (2025-02-04) diff --git a/Cargo.lock b/Cargo.lock index e35c23e..21b6199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4231,7 +4231,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.30.1" +version = "0.31.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 1c7e8f7..8a363ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.30.1" +version = "0.31.0" edition = "2021" [dependencies]