From 73c1a2db6ac66d1cdad5726cfa998e272fa4cb14 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 27 May 2025 09:37:24 +0100 Subject: [PATCH 1/2] chore: Rename selector to function signature Took 8 minutes --- examples/encoding-example/main.rs | 16 ++++---- src/bin/tycho-encode.rs | 2 +- src/encoding/evm/encoding_utils.rs | 25 ++++++------ .../evm/strategy_encoder/strategy_encoders.rs | 40 +++++++++---------- .../evm/swap_encoder/swap_encoders.rs | 4 -- src/encoding/evm/tycho_encoders.rs | 2 +- src/encoding/models.rs | 5 +-- src/encoding/tycho_encoder.rs | 6 +-- 8 files changed, 48 insertions(+), 52 deletions(-) diff --git a/examples/encoding-example/main.rs b/examples/encoding-example/main.rs index 7c35f06..ff10123 100644 --- a/examples/encoding-example/main.rs +++ b/examples/encoding-example/main.rs @@ -64,10 +64,10 @@ fn main() { .clone(); println!(" ====== Simple swap WETH -> USDC ======"); println!( - "The simple swap encoded solution should be sent to address {:?} and selector {:?} and the \ - following encoded data: {:?}", + "The simple swap encoded solution should be sent to address {:?} with function signature {:?} and the \ + following encoded swaps: {:?}", encoded_solution.interacting_with, - encoded_solution.selector, + encoded_solution.function_signature, hex::encode(encoded_solution.swaps) ); @@ -138,10 +138,10 @@ fn main() { println!(" ====== Complex split swap WETH -> USDC ======"); println!( - "The complex swaps encoded solution should be sent to address {:?} and selector {:?} and the \ - following encoded data: {:?}", - complex_encoded_solution.interacting_with, - complex_encoded_solution.selector, - hex::encode(complex_encoded_solution.swaps) + "The complex swaps encoded solution should be sent to address {:?} with function signature {:?} and the \ + following encoded swaps: {:?}", + complex_encoded_solution.interacting_with, + complex_encoded_solution.function_signature, + hex::encode(complex_encoded_solution.swaps) ); } diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index 3af9033..b55c424 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -106,7 +106,7 @@ fn main() -> Result<(), Box> { let encoded = serde_json::json!({ "swaps": format!("0x{}", hex::encode(&encoded_solutions[0].swaps)), "interacting_with": format!("0x{}", hex::encode(&encoded_solutions[0].interacting_with)), - "selector": format!("{}",&encoded_solutions[0].selector), + "function_signature": format!("{}",&encoded_solutions[0].function_signature), "n_tokens": format!("{}", &encoded_solutions[0].n_tokens), "permit": match encoded_solutions[0].permit.as_ref() { Some(permit) => { diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index 438ebe0..f39a60b 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -34,7 +34,7 @@ use crate::encoding::{ /// - `splitSwapPermit2` /// /// The encoding includes handling of native asset wrapping/unwrapping, permit2 support, -/// and proper input argument formatting based on the selector string. +/// and proper input argument formatting based on the function signature string. /// /// # ⚠️ Important Responsibility Note /// @@ -60,7 +60,7 @@ use crate::encoding::{ /// funds. /// /// # Parameters -/// - `encoded_solution`: The solution already encoded by Tycho, including selector and swap path. +/// - `encoded_solution`: The solution already encoded by Tycho. /// - `solution`: The high-level solution including tokens, amounts, and receiver info. /// - `token_in_already_in_router`: Whether the input token is already present in the router. /// - `router_address`: The address of the Tycho Router contract. @@ -71,8 +71,8 @@ use crate::encoding::{ /// value, data), or an error if the inputs are invalid. /// /// # Errors -/// - Returns `EncodingError::FatalError` if the selector is unsupported or required fields (e.g., -/// permit or signature) are missing. +/// - Returns `EncodingError::FatalError` if the function signature is unsupported or required +/// fields (e.g., permit or signature) are missing. pub fn encode_tycho_router_call( chain_id: u64, encoded_solution: EncodedSolution, @@ -109,7 +109,7 @@ pub fn encode_tycho_router_call( }; let method_calldata = if encoded_solution - .selector + .function_signature .contains("singleSwapPermit2") { ( @@ -128,7 +128,7 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else if encoded_solution - .selector + .function_signature .contains("singleSwap") { ( @@ -144,7 +144,7 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else if encoded_solution - .selector + .function_signature .contains("sequentialSwapPermit2") { ( @@ -163,7 +163,7 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else if encoded_solution - .selector + .function_signature .contains("sequentialSwap") { ( @@ -179,7 +179,7 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else if encoded_solution - .selector + .function_signature .contains("splitSwapPermit2") { ( @@ -199,7 +199,7 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else if encoded_solution - .selector + .function_signature .contains("splitSwap") { ( @@ -216,10 +216,11 @@ pub fn encode_tycho_router_call( ) .abi_encode() } else { - Err(EncodingError::FatalError("Invalid selector for Tycho router".to_string()))? + Err(EncodingError::FatalError("Invalid function signature for Tycho router".to_string()))? }; - let contract_interaction = utils::encode_input(&encoded_solution.selector, method_calldata); + let contract_interaction = + utils::encode_input(&encoded_solution.function_signature, method_calldata); let value = if solution.given_token == native_address { solution.given_amount.clone() } else { diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index aa17095..9038fca 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -23,13 +23,13 @@ use crate::encoding::{ /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `selector`: String, the selector for the swap function in the router contract +/// * `function_signature`: String, the signature for the swap function in the router contract /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - selector: String, + function_signature: String, router_address: Bytes, transfer_optimization: TransferOptimization, } @@ -41,14 +41,14 @@ impl SingleSwapStrategyEncoder { user_transfer_type: UserTransferType, router_address: Bytes, ) -> Result { - let selector = if user_transfer_type == UserTransferType::TransferFromPermit2 { + let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" }.to_string(); Ok(Self { - selector, + function_signature, swap_encoder_registry, router_address: router_address.clone(), transfer_optimization: TransferOptimization::new( @@ -133,7 +133,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { grouped_protocol_data, ); Ok(EncodedSolution { - selector: self.selector.clone(), + function_signature: self.function_signature.clone(), interacting_with: self.router_address.clone(), swaps: swap_data, permit: None, @@ -155,7 +155,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `selector`: String, the selector for the swap function in the router contract +/// * `function_signature`: String, the signature for the swap function in the router contract /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped token /// * `router_address`: Address of the router to be used to execute swaps @@ -165,7 +165,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - selector: String, + function_signature: String, router_address: Bytes, native_address: Bytes, wrapped_address: Bytes, @@ -180,14 +180,14 @@ impl SequentialSwapStrategyEncoder { user_transfer_type: UserTransferType, router_address: Bytes, ) -> Result { - let selector = if user_transfer_type == UserTransferType::TransferFromPermit2 { + let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" }.to_string(); Ok(Self { - selector, + function_signature, swap_encoder_registry, router_address: router_address.clone(), native_address: chain.native_token()?, @@ -288,7 +288,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { let encoded_swaps = ple_encode(swaps); Ok(EncodedSolution { interacting_with: self.router_address.clone(), - selector: self.selector.clone(), + function_signature: self.function_signature.clone(), swaps: encoded_swaps, permit: None, n_tokens: 0, @@ -309,7 +309,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders -/// * `selector`: String, the selector for the swap function in the router contract +/// * `function_signature`: String, the signature for the swap function in the router contract /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped token /// * `split_swap_validator`: SplitSwapValidator, responsible for checking validity of split swap @@ -319,7 +319,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, - selector: String, + function_signature: String, native_address: Bytes, wrapped_address: Bytes, split_swap_validator: SplitSwapValidator, @@ -334,13 +334,13 @@ impl SplitSwapStrategyEncoder { user_transfer_type: UserTransferType, router_address: Bytes, ) -> Result { - let selector = if user_transfer_type == UserTransferType::TransferFromPermit2 { + let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 { "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" } else { "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)" }.to_string(); Ok(Self { - selector, + function_signature, swap_encoder_registry, native_address: chain.native_token()?, wrapped_address: chain.wrapped_token()?, @@ -489,7 +489,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { }; Ok(EncodedSolution { interacting_with: self.router_address.clone(), - selector: self.selector.clone(), + function_signature: self.function_signature.clone(), swaps: encoded_swaps, permit: None, n_tokens: tokens_len, @@ -596,7 +596,7 @@ mod tests { let hex_calldata = encode(&encoded_solution.swaps); assert_eq!(hex_calldata, expected_swap); - assert_eq!(encoded_solution.selector, "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()); + assert_eq!(encoded_solution.function_signature, "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string()); assert_eq!(encoded_solution.interacting_with, router_address()); } @@ -659,7 +659,7 @@ mod tests { assert_eq!(hex_calldata, expected_input); assert_eq!( - encoded_solution.selector, + encoded_solution.function_signature, "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" .to_string() ); @@ -747,7 +747,7 @@ mod tests { assert_eq!(hex_calldata, expected); assert_eq!( - encoded_solution.selector, + encoded_solution.function_signature, "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" .to_string() ); @@ -902,7 +902,7 @@ mod tests { .join(""); assert_eq!(hex_calldata, expected_swaps); assert_eq!( - encoded_solution.selector, + encoded_solution.function_signature, "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)" .to_string() ); @@ -1050,7 +1050,7 @@ mod tests { assert_eq!(hex_calldata, expected_swaps); assert_eq!( - encoded_solution.selector, + encoded_solution.function_signature, "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)" .to_string() ); diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 2c123ef..11cd019 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -19,7 +19,6 @@ use crate::encoding::{ /// /// # Fields /// * `executor_address` - The address of the executor contract that will perform the swap. -/// * `swap_selector` - The selector of the swap function in the executor contract. #[derive(Clone)] pub struct UniswapV2SwapEncoder { executor_address: String, @@ -78,7 +77,6 @@ impl SwapEncoder for UniswapV2SwapEncoder { /// /// # Fields /// * `executor_address` - The address of the executor contract that will perform the swap. -/// * `swap_selector` - The selector of the swap function in the executor contract. #[derive(Clone)] pub struct UniswapV3SwapEncoder { executor_address: String, @@ -140,8 +138,6 @@ impl SwapEncoder for UniswapV3SwapEncoder { /// /// # Fields /// * `executor_address` - The address of the executor contract that will perform the swap. -/// * `swap_selector` - The selector of the swap function in the executor contract. -/// * `callback_selector` - The selector of the callback function in the executor contract. #[derive(Clone)] pub struct UniswapV4SwapEncoder { executor_address: String, diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 5b96f9b..14f76bd 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -337,7 +337,7 @@ impl TychoExecutorEncoder { swaps: grouped_protocol_data, interacting_with: executor_address, permit: None, - selector: "".to_string(), + function_signature: "".to_string(), n_tokens: 0, }) } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 527c853..b8e03f4 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -105,7 +105,6 @@ impl Swap { /// * `to`: Address of the contract to call with the calldata /// * `value`: Native token value to be sent with the transaction. /// * `data`: Encoded calldata for the transaction. -/// * `selector`: Only relevant for direct executions. The selector of the function to be called. #[derive(Clone, Debug)] pub struct Transaction { pub to: Bytes, @@ -118,14 +117,14 @@ pub struct Transaction { /// # Fields /// * `swaps`: Encoded swaps to be executed. /// * `interacting_with`: Address of the contract to be called. -/// * `selector`: The selector of the function to be called. +/// * `function_signature`: The signature of the function to be called. /// * `n_tokens`: Number of tokens in the swap. /// * `permit`: Optional permit for the swap (if permit2 is enabled). #[derive(Clone, Debug)] pub struct EncodedSolution { pub swaps: Vec, pub interacting_with: Bytes, - pub selector: String, + pub function_signature: String, pub n_tokens: usize, pub permit: Option, } diff --git a/src/encoding/tycho_encoder.rs b/src/encoding/tycho_encoder.rs index 99b0216..4c5c230 100644 --- a/src/encoding/tycho_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -26,8 +26,8 @@ use crate::encoding::{ /// outer function call arguments themselves** and verify that they enforce correct and secure /// behavior. pub trait TychoEncoder { - /// Encodes a list of [`Solution`]s into [`EncodedSolution`]s, which include the selector and - /// internal swap call data. + /// Encodes a list of [`Solution`]s into [`EncodedSolution`]s, which include the function + /// signature and internal swap call data. /// /// This method gives users maximum flexibility and control. It **does not** produce full /// transaction objects. Users are responsible for: @@ -36,7 +36,7 @@ pub trait TychoEncoder { /// /// # Returns /// A vector of encoded solutions, each containing: - /// - The Tycho method selector + /// - The Tycho method function signature /// - The encoded swap path /// - Additional metadata (e.g., permit2 information) /// From 92d36b9f48d30aad2d1227c21f490ac9f47daa7b Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 27 May 2025 12:12:47 +0100 Subject: [PATCH 2/2] fix: Move encode_input back into encoding_utils.rs We don't want the user to be able to use it outside the crate Took 16 minutes --- src/encoding/evm/approvals/permit2.rs | 5 +++- .../approvals/protocol_approvals_manager.rs | 5 +++- src/encoding/evm/encoding_utils.rs | 28 ++++++++++++++++--- src/encoding/evm/utils.rs | 24 +--------------- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index ee4d572..6c58707 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -17,7 +17,10 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, - evm::utils::{biguint_to_u256, bytes_to_address, encode_input, get_client, get_runtime}, + evm::{ + encoding_utils::encode_input, + utils::{biguint_to_u256, bytes_to_address, get_client, get_runtime}, + }, models, }; diff --git a/src/encoding/evm/approvals/protocol_approvals_manager.rs b/src/encoding/evm/approvals/protocol_approvals_manager.rs index 364a4ce..8b5769b 100644 --- a/src/encoding/evm/approvals/protocol_approvals_manager.rs +++ b/src/encoding/evm/approvals/protocol_approvals_manager.rs @@ -14,7 +14,10 @@ use tokio::{ use crate::encoding::{ errors::EncodingError, - evm::utils::{encode_input, get_client, get_runtime}, + evm::{ + encoding_utils::encode_input, + utils::{get_client, get_runtime}, + }, }; /// A manager for checking if an approval is needed for interacting with a certain spender. diff --git a/src/encoding/evm/encoding_utils.rs b/src/encoding/evm/encoding_utils.rs index f39a60b..3295280 100644 --- a/src/encoding/evm/encoding_utils.rs +++ b/src/encoding/evm/encoding_utils.rs @@ -4,7 +4,7 @@ use alloy::{ primitives::U256, signers::{local::PrivateKeySigner, Signature, SignerSync}, }; -use alloy_primitives::Address; +use alloy_primitives::{Address, Keccak256}; use alloy_sol_types::{eip712_domain, SolStruct, SolValue}; use num_bigint::BigUint; use tycho_common::Bytes; @@ -13,7 +13,6 @@ use crate::encoding::{ errors::EncodingError, evm::{ approvals::permit2::PermitSingle, - utils, utils::{biguint_to_u256, bytes_to_address}, }, models, @@ -219,8 +218,7 @@ pub fn encode_tycho_router_call( Err(EncodingError::FatalError("Invalid function signature for Tycho router".to_string()))? }; - let contract_interaction = - utils::encode_input(&encoded_solution.function_signature, method_calldata); + let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata); let value = if solution.given_token == native_address { solution.given_amount.clone() } else { @@ -258,3 +256,25 @@ pub fn sign_permit( EncodingError::FatalError(format!("Failed to sign permit2 approval with error: {e}")) }) } + +/// Encodes the input data for a function call to the given function selector. +pub fn encode_input(selector: &str, mut encoded_args: Vec) -> Vec { + let mut hasher = Keccak256::new(); + hasher.update(selector.as_bytes()); + let selector_bytes = &hasher.finalize()[..4]; + let mut call_data = selector_bytes.to_vec(); + // Remove extra prefix if present (32 bytes for dynamic data) + // Alloy encoding is including a prefix for dynamic data indicating the offset or length + // but at this point we don't want that + if encoded_args.len() > 32 && + encoded_args[..32] == + [0u8; 31] + .into_iter() + .chain([32].to_vec()) + .collect::>() + { + encoded_args = encoded_args[32..].to_vec(); + } + call_data.extend(encoded_args); + call_data +} diff --git a/src/encoding/evm/utils.rs b/src/encoding/evm/utils.rs index bad8f5f..df61ef4 100644 --- a/src/encoding/evm/utils.rs +++ b/src/encoding/evm/utils.rs @@ -9,7 +9,7 @@ use alloy::{ providers::{ProviderBuilder, RootProvider}, transports::BoxTransport, }; -use alloy_primitives::{aliases::U24, Address, Keccak256, U256, U8}; +use alloy_primitives::{aliases::U24, Address, U256, U8}; use alloy_sol_types::SolValue; use num_bigint::BigUint; use once_cell::sync::Lazy; @@ -159,25 +159,3 @@ pub fn write_calldata_to_file(test_identifier: &str, hex_calldata: &str) { writeln!(file, "{line}").expect("Failed to write calldata"); } } - -/// Encodes the input data for a function call to the given function selector. -pub fn encode_input(selector: &str, mut encoded_args: Vec) -> Vec { - let mut hasher = Keccak256::new(); - hasher.update(selector.as_bytes()); - let selector_bytes = &hasher.finalize()[..4]; - let mut call_data = selector_bytes.to_vec(); - // Remove extra prefix if present (32 bytes for dynamic data) - // Alloy encoding is including a prefix for dynamic data indicating the offset or length - // but at this point we don't want that - if encoded_args.len() > 32 && - encoded_args[..32] == - [0u8; 31] - .into_iter() - .chain([32].to_vec()) - .collect::>() - { - encoded_args = encoded_args[32..].to_vec(); - } - call_data.extend(encoded_args); - call_data -}