From 3116fef0d785ecae3cbd2b6c747036cd11ca331e Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 10:40:32 +0000 Subject: [PATCH 01/16] fix: Change version of serde to match tycho-simulation --- don't change below this line --- ENG-4088 Took 4 minutes Took 9 seconds Took 30 seconds Took 21 seconds --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a1911e2..47b55db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ lazy_static = "1.4.0" num-bigint = { version = "0.4.6", features = ["serde"] } hex = "0.4.3" num-traits = "0.2.19" -serde = { version = "1.0.217", features = ["derive"] } -serde_json = "1.0.135" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.105" thiserror = "1.0.69" tokio = { version = "1.38.0", features = ["full"] } chrono = "0.4.39" From 4680a4be2429ab90bcb440fb1f57105f4f244360 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 10:48:58 +0000 Subject: [PATCH 02/16] feat: Make executors_file_path optional and use a default value if None --- don't change below this line --- ENG-4088 Took 4 minutes Took 59 seconds --- examples/quickstart/main.rs | 3 +-- src/encoding/evm/constants.rs | 1 + .../evm/strategy_encoder/strategy_encoder_registry.rs | 8 ++++++-- src/encoding/evm/tycho_encoder.rs | 4 ++-- src/encoding/strategy_encoder.rs | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 src/encoding/evm/constants.rs diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index f63cd19..77a18c9 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -22,12 +22,11 @@ fn main() { Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()); let user_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .expect("Failed to create user address"); - let executors_file_path = "src/encoding/config/executor_addresses.json"; let eth_chain = Chain::from(TychoCoreChain::Ethereum); // Initialize the encoder let strategy_encoder_registry = - EVMStrategyEncoderRegistry::new(eth_chain.clone(), executors_file_path, signer_pk.clone()) + EVMStrategyEncoderRegistry::new(eth_chain.clone(), None, signer_pk.clone()) .expect("Failed to create strategy encoder registry"); let encoder = EVMTychoEncoder::new(strategy_encoder_registry, router_address, eth_chain) .expect("Failed to create encoder"); diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs new file mode 100644 index 0000000..177b7d9 --- /dev/null +++ b/src/encoding/evm/constants.rs @@ -0,0 +1 @@ +pub const DEFAULT_EXECUTORS_FILE_PATH: &str = "src/encoding/config/executor_addresses.json"; diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index 8ca10ce..ba4f99e 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::encoding::{ errors::EncodingError, evm::{ + constants::DEFAULT_EXECUTORS_FILE_PATH, strategy_encoder::strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder}, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, }, @@ -22,10 +23,13 @@ pub struct EVMStrategyEncoderRegistry { impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry { fn new( chain: Chain, - executors_file_path: &str, + executors_file_path: Option<&str>, signer_pk: Option, ) -> Result { - let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain.clone())?; + let swap_encoder_registry = SwapEncoderRegistry::new( + executors_file_path.unwrap_or(DEFAULT_EXECUTORS_FILE_PATH), + chain.clone(), + )?; let mut strategies: HashMap> = HashMap::new(); strategies.insert( diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index a022d18..db80d0a 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -169,7 +169,7 @@ mod tests { impl StrategyEncoderRegistry for MockStrategyRegistry { fn new( _chain: Chain, - _executors_file_path: &str, + _executors_file_path: Option<&str>, _signer_pk: Option, ) -> Result { Ok(Self { strategy: Box::new(MockStrategy) }) @@ -205,7 +205,7 @@ mod tests { } fn get_mocked_tycho_encoder() -> EVMTychoEncoder { - let strategy_registry = MockStrategyRegistry::new(eth_chain(), "", None).unwrap(); + let strategy_registry = MockStrategyRegistry::new(eth_chain(), None, None).unwrap(); EVMTychoEncoder::new( strategy_registry, "0x1234567890abcdef1234567890abcdef12345678".to_string(), diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index bb7ecf4..cedb96c 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -23,7 +23,7 @@ pub trait StrategyEncoder { pub trait StrategyEncoderRegistry { fn new( chain: Chain, - executors_file_path: &str, + executors_file_path: Option<&str>, signer_pk: Option, ) -> Result where From b333d60d69ffc37d45d065494902161462e52ada Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 11:49:11 +0000 Subject: [PATCH 03/16] feat: Add clone to EVMTychoEncoder --- don't change below this line --- ENG-4088 Took 4 minutes Took 8 seconds Took 48 seconds --- src/encoding/evm/approvals/permit2.rs | 5 +++-- src/encoding/evm/models.rs | 0 .../strategy_encoder/strategy_encoder_registry.rs | 12 ++++++++++++ .../evm/strategy_encoder/strategy_encoders.rs | 11 +++++++++++ src/encoding/evm/tycho_encoder.rs | 5 +++++ src/encoding/strategy_encoder.rs | 1 + 6 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/encoding/evm/models.rs diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index c118358..37686a2 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -25,10 +25,11 @@ use crate::encoding::{ /// Struct for managing Permit2 operations, including encoding approvals and fetching allowance /// data. +#[derive(Clone)] pub struct Permit2 { address: Address, client: Arc>, - runtime: Runtime, + runtime: Arc, signer: PrivateKeySigner, chain_id: ChainId, } @@ -73,7 +74,7 @@ impl Permit2 { address: Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3") .map_err(|_| EncodingError::FatalError("Permit2 address not valid".to_string()))?, client, - runtime, + runtime: Arc::new(runtime), signer, chain_id: chain.id, }) diff --git a/src/encoding/evm/models.rs b/src/encoding/evm/models.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index ba4f99e..0109aa1 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -58,3 +58,15 @@ impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry { } } } + +impl Clone for EVMStrategyEncoderRegistry { + fn clone(&self) -> Self { + Self { + strategies: self + .strategies + .iter() + .map(|(k, v)| (k.clone(), v.clone_box())) + .collect(), + } + } +} diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index af032a7..73e0f71 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -73,6 +73,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder { /// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary /// signatures and permit2 objects for calling the router /// * `selector`: String, the selector for the swap function in the router contract +#[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, permit2: Permit2, @@ -405,6 +406,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { self.swap_encoder_registry .get_encoder(protocol_system) } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } /// This strategy encoder is used for solutions that are sent directly to the executor, bypassing @@ -412,6 +417,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { /// /// # Fields /// * `swap_encoder_registry`: SwapEncoderRegistry, containing all possible swap encoders +#[derive(Clone)] pub struct ExecutorStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, } @@ -459,10 +465,15 @@ impl StrategyEncoder for ExecutorStrategyEncoder { .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; Ok((protocol_data, executor_address)) } + fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box> { self.swap_encoder_registry .get_encoder(protocol_system) } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } #[cfg(test)] diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index db80d0a..baa4f0f 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -19,6 +19,7 @@ use crate::encoding::{ /// * `router_address`: Bytes, the address of the router to use to execute the swaps. /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped native token +#[derive(Clone)] pub struct EVMTychoEncoder { strategy_registry: S, router_address: Bytes, @@ -183,6 +184,7 @@ mod tests { } } + #[derive(Clone)] struct MockStrategy; impl StrategyEncoder for MockStrategy { @@ -202,6 +204,9 @@ mod tests { fn get_swap_encoder(&self, _protocol_system: &str) -> Option<&Box> { None } + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } } fn get_mocked_tycho_encoder() -> EVMTychoEncoder { diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index cedb96c..101eaa4 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -16,6 +16,7 @@ pub trait StrategyEncoder { #[allow(clippy::borrowed_box)] fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box>; + fn clone_box(&self) -> Box; } /// Contains the supported strategies to encode a solution, and chooses the best strategy to encode From dee8542b4537715f2224f14b50179a756f7ccd50 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 12:13:38 +0000 Subject: [PATCH 04/16] chore: Improve error message when reading executors file --- don't change below this line --- ENG-4088 Took 1 minute Took 7 seconds --- src/encoding/evm/swap_encoder/swap_encoder_registry.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index a9bb22f..f0bd2a8 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -16,7 +16,13 @@ impl SwapEncoderRegistry { /// Populates the registry with the `SwapEncoders` for the given blockchain by parsing the /// executors' addresses in the file at the given path. pub fn new(executors_file_path: &str, blockchain: Chain) -> Result { - let config_str = fs::read_to_string(executors_file_path)?; + let config_str = fs::read_to_string(executors_file_path).map_err(|e| { + EncodingError::FatalError(format!( + "Error reading executors file from {}: {}", + executors_file_path, + e.to_string() + )) + })?; let config: HashMap> = serde_json::from_str(&config_str)?; let mut encoders = HashMap::new(); let executors = config From c791c93cb5ea0c39de46338c45f5575f30215189 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 12:38:35 +0000 Subject: [PATCH 05/16] feat: Add uniswap v3 to swap encoders list --- don't change below this line --- ENG-4088 Took 10 minutes --- src/encoding/config/executor_addresses.json | 1 + src/encoding/evm/swap_encoder/builder.rs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/encoding/config/executor_addresses.json b/src/encoding/config/executor_addresses.json index 6e27a2d..32c9e3e 100644 --- a/src/encoding/config/executor_addresses.json +++ b/src/encoding/config/executor_addresses.json @@ -1,6 +1,7 @@ { "ethereum": { "uniswap_v2": "0x5C2F5a71f67c01775180ADc06909288B4C329308", + "uniswap_v3": "0x5C2F5a71f67c01775180ADc06909288B4C329308", "vm:balancer_v2": "0x543778987b293C7E8Cf0722BB2e935ba6f4068D4" } } \ No newline at end of file diff --git a/src/encoding/evm/swap_encoder/builder.rs b/src/encoding/evm/swap_encoder/builder.rs index 4d99206..430dcb2 100644 --- a/src/encoding/evm/swap_encoder/builder.rs +++ b/src/encoding/evm/swap_encoder/builder.rs @@ -1,6 +1,8 @@ use crate::encoding::{ errors::EncodingError, - evm::swap_encoder::swap_encoders::{BalancerV2SwapEncoder, UniswapV2SwapEncoder}, + evm::swap_encoder::swap_encoders::{ + BalancerV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, + }, swap_encoder::SwapEncoder, }; @@ -22,6 +24,7 @@ impl SwapEncoderBuilder { match self.protocol_system.as_str() { "uniswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address))), "vm:balancer_v2" => Ok(Box::new(BalancerV2SwapEncoder::new(self.executor_address))), + "uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), _ => Err(EncodingError::FatalError(format!( "Unknown protocol system: {}", self.protocol_system From f5232f403ee8f09c3bf83be865e326540b781740 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 12:39:19 +0000 Subject: [PATCH 06/16] feat: Read default executors at compile time into a json This way we don't have issues with paths and trying to read files that are at other locations after compile time --- don't change below this line --- ENG-4088 Took 44 seconds Took 52 seconds --- src/encoding/evm/constants.rs | 3 ++- .../strategy_encoder_registry.rs | 6 +---- .../evm/strategy_encoder/strategy_encoders.rs | 2 +- .../evm/swap_encoder/swap_encoder_registry.rs | 25 ++++++++++++------- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index 177b7d9..1426dd4 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -1 +1,2 @@ -pub const DEFAULT_EXECUTORS_FILE_PATH: &str = "src/encoding/config/executor_addresses.json"; +pub const DEFAULT_EXECUTORS_JSON: &str = + include_str!("../../../src/encoding/config/executor_addresses.json"); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index 0109aa1..a245476 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use crate::encoding::{ errors::EncodingError, evm::{ - constants::DEFAULT_EXECUTORS_FILE_PATH, strategy_encoder::strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder}, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, }, @@ -26,10 +25,7 @@ impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry { executors_file_path: Option<&str>, signer_pk: Option, ) -> Result { - let swap_encoder_registry = SwapEncoderRegistry::new( - executors_file_path.unwrap_or(DEFAULT_EXECUTORS_FILE_PATH), - chain.clone(), - )?; + let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain.clone())?; let mut strategies: HashMap> = HashMap::new(); strategies.insert( diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 73e0f71..dd3abfc 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -506,7 +506,7 @@ mod tests { fn get_swap_encoder_registry() -> SwapEncoderRegistry { let eth_chain = eth_chain(); - SwapEncoderRegistry::new("src/encoding/config/executor_addresses.json", eth_chain).unwrap() + SwapEncoderRegistry::new(None, eth_chain).unwrap() } #[test] diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index f0bd2a8..401592d 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, fs}; use crate::encoding::{ - errors::EncodingError, evm::swap_encoder::builder::SwapEncoderBuilder, models::Chain, + errors::EncodingError, + evm::{constants::DEFAULT_EXECUTORS_JSON, swap_encoder::builder::SwapEncoderBuilder}, models::Chain, swap_encoder::SwapEncoder, }; @@ -15,14 +16,20 @@ pub struct SwapEncoderRegistry { impl SwapEncoderRegistry { /// Populates the registry with the `SwapEncoders` for the given blockchain by parsing the /// executors' addresses in the file at the given path. - pub fn new(executors_file_path: &str, blockchain: Chain) -> Result { - let config_str = fs::read_to_string(executors_file_path).map_err(|e| { - EncodingError::FatalError(format!( - "Error reading executors file from {}: {}", - executors_file_path, - e.to_string() - )) - })?; + pub fn new( + executors_file_path: Option<&str>, + blockchain: Chain, + ) -> Result { + let config_str = if let Some(path) = executors_file_path { + fs::read_to_string(path).map_err(|e| { + EncodingError::FatalError(format!( + "Error reading executors file from {:?}: {}", + executors_file_path, e + )) + })? + } else { + DEFAULT_EXECUTORS_JSON.to_string() + }; let config: HashMap> = serde_json::from_str(&config_str)?; let mut encoders = HashMap::new(); let executors = config From 12f85ccc0a4e5e30f06a4dd3db514c5cf5f91ba0 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 13:03:03 +0000 Subject: [PATCH 07/16] feat: Get current runtime if there is any instead of creating a new one every time --- don't change below this line --- ENG-4088 Took 18 minutes Took 6 seconds --- src/encoding/evm/approvals/permit2.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 37686a2..05ab7dc 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -61,8 +61,14 @@ sol! { impl Permit2 { pub fn new(signer_pk: String, chain: Chain) -> Result { - let runtime = Runtime::new() - .map_err(|_| EncodingError::FatalError("Failed to create runtime".to_string()))?; + let runtime = tokio::runtime::Handle::try_current() + .is_err() + .then(|| { + tokio::runtime::Runtime::new().map_err(|_| { + EncodingError::FatalError("Failed to create tokio runtime".to_string()) + }) + }) + .ok_or(EncodingError::FatalError("Failed to get tokio runtime".to_string()))??; let client = runtime.block_on(get_client())?; let pk = B256::from_str(&signer_pk).map_err(|_| { EncodingError::FatalError("Failed to convert signer private key to B256".to_string()) From d4af59d4dca83547d208e8d87ddc56b16153e64b Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 17:05:39 +0000 Subject: [PATCH 08/16] feat: Use block_in_place instead of block_on This is necessary when we run tycho-execution in an environment that already has a runtime. Make sure that if no runtime exists, like in the testing scenario inside tycho-execution, we save the new runtime in an attribute of permit2. If we don't do this, then the runtime just dies and our handle points to nothing at all and all tests fail --- don't change below this line --- ENG-4088 Took 17 minutes Took 5 seconds --- src/encoding/evm/approvals/permit2.rs | 62 ++++++++++++++++----------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/src/encoding/evm/approvals/permit2.rs b/src/encoding/evm/approvals/permit2.rs index 05ab7dc..2985a28 100644 --- a/src/encoding/evm/approvals/permit2.rs +++ b/src/encoding/evm/approvals/permit2.rs @@ -11,7 +11,10 @@ use alloy_primitives::{PrimitiveSignature as Signature, B256}; use alloy_sol_types::{eip712_domain, sol, SolStruct, SolValue}; use chrono::Utc; use num_bigint::BigUint; -use tokio::runtime::Runtime; +use tokio::{ + runtime::{Handle, Runtime}, + task::block_in_place, +}; use tycho_core::Bytes; use crate::encoding::{ @@ -29,9 +32,15 @@ use crate::encoding::{ pub struct Permit2 { address: Address, client: Arc>, - runtime: Arc, signer: PrivateKeySigner, chain_id: ChainId, + runtime_handle: Handle, + // Store the runtime to prevent it from being dropped before use. + // This is required since tycho-execution does not have a pre-existing runtime. + // However, if the library is used in a context where a runtime already exists, it is not + // necessary to store it. + #[allow(dead_code)] + runtime: Option>, } /// Type alias for representing allowance data as a tuple of (amount, expiration, nonce). Used for @@ -61,15 +70,16 @@ sol! { impl Permit2 { pub fn new(signer_pk: String, chain: Chain) -> Result { - let runtime = tokio::runtime::Handle::try_current() - .is_err() - .then(|| { - tokio::runtime::Runtime::new().map_err(|_| { - EncodingError::FatalError("Failed to create tokio runtime".to_string()) - }) - }) - .ok_or(EncodingError::FatalError("Failed to get tokio runtime".to_string()))??; - let client = runtime.block_on(get_client())?; + let (handle, runtime) = match Handle::try_current() { + Ok(h) => (h, None), + Err(_) => { + let rt = Arc::new(Runtime::new().map_err(|_| { + EncodingError::FatalError("Failed to create a new tokio runtime".to_string()) + })?); + (rt.handle().clone(), Some(rt)) + } + }; + let client = block_in_place(|| handle.block_on(get_client()))?; let pk = B256::from_str(&signer_pk).map_err(|_| { EncodingError::FatalError("Failed to convert signer private key to B256".to_string()) })?; @@ -80,9 +90,10 @@ impl Permit2 { address: Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3") .map_err(|_| EncodingError::FatalError("Permit2 address not valid".to_string()))?, client, - runtime: Arc::new(runtime), + runtime_handle: handle, signer, chain_id: chain.id, + runtime, }) } @@ -101,9 +112,10 @@ impl Permit2 { ..Default::default() }; - let output = self - .runtime - .block_on(async { self.client.call(&tx).await }); + let output = block_in_place(|| { + self.runtime_handle + .block_on(async { self.client.call(&tx).await }) + }); match output { Ok(response) => { let allowance: Allowance = @@ -290,14 +302,16 @@ mod tests { input: TransactionInput { input: Some(AlloyBytes::from(data)), data: None }, ..Default::default() }; - let receipt = permit2.runtime.block_on(async { - let pending_tx = permit2 - .client - .send_transaction(tx) - .await - .unwrap(); - // Wait for the transaction to be mined - pending_tx.get_receipt().await.unwrap() + let receipt = block_in_place(|| { + permit2.runtime_handle.block_on(async { + let pending_tx = permit2 + .client + .send_transaction(tx) + .await + .unwrap(); + // Wait for the transaction to be mined + pending_tx.get_receipt().await.unwrap() + }) }); assert!(receipt.status(), "Approve transaction failed"); @@ -321,7 +335,7 @@ mod tests { ..Default::default() }; - let result = permit2.runtime.block_on(async { + let result = permit2.runtime_handle.block_on(async { let pending_tx = permit2 .client .send_transaction(tx) From 6bbb6da1cdb24e8e1c280aa176941cb01c467219 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 18:03:03 +0000 Subject: [PATCH 09/16] feat: Increase tycho-core version --- don't change below this line --- ENG-4088 Took 8 minutes Took 11 seconds Took 22 seconds --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 949d8e0..d351255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4317,8 +4317,8 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-core" -version = "0.46.0" -source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.46.0#481b2f252eddb98442eab78069fa427068b5050d" +version = "0.55.2" +source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.55.2#dfa50d5e318253001938655a49aa3e05f958d89e" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 47b55db..3dcfb37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ clap = { version = "4.5.3", features = ["derive"] } alloy = { version = "0.9.2", features = ["providers", "rpc-types-eth", "eip712", "signer-local"], optional = true } alloy-sol-types = { version = "0.8.14", optional = true } alloy-primitives = { version = "0.8.9", optional = true } -tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.46.0" } +tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.55.2" } once_cell = "1.20.2" [dev-dependencies] From 164d062ad9ceb9ceb7ec26e0253d62972fc967cc Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 18:11:20 +0000 Subject: [PATCH 10/16] fix(univ3): The fee keyword is just "fee" and not "pool_fee" --- don't change below this line --- ENG-4088 Took 1 minute --- src/encoding/evm/swap_encoder/swap_encoders.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 34c1203..7d97002 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -107,7 +107,7 @@ impl SwapEncoder for UniswapV3SwapEncoder { let mut pool_fee_bytes = swap .component .static_attributes - .get("pool_fee") + .get("fee") .ok_or_else(|| { EncodingError::FatalError( "Pool fee not found in Uniswap v3 static attributes".to_string(), @@ -259,7 +259,7 @@ mod tests { fn test_encode_uniswap_v3() { let encoded_pool_fee: [u8; 4] = 500u32.to_le_bytes(); let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("pool_fee".into(), Bytes::from(encoded_pool_fee[..3].to_vec())); + static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee[..3].to_vec())); let usv3_pool = ProtocolComponent { id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), From bef4740a1d22312ed5745ce5e0199f919c784962 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 6 Feb 2025 18:23:17 +0000 Subject: [PATCH 11/16] fix: After rebase fixes --- don't change below this line --- ENG-4088 Took 2 minutes --- src/encoding/evm/mod.rs | 1 + src/encoding/evm/swap_encoder/swap_encoder_registry.rs | 3 ++- src/encoding/models.rs | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/encoding/evm/mod.rs b/src/encoding/evm/mod.rs index 9c736bb..55557f3 100644 --- a/src/encoding/evm/mod.rs +++ b/src/encoding/evm/mod.rs @@ -1,4 +1,5 @@ pub mod approvals; +mod constants; pub mod strategy_encoder; mod swap_encoder; pub mod tycho_encoder; diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index 401592d..006b28f 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -2,7 +2,8 @@ use std::{collections::HashMap, fs}; use crate::encoding::{ errors::EncodingError, - evm::{constants::DEFAULT_EXECUTORS_JSON, swap_encoder::builder::SwapEncoderBuilder}, models::Chain, + evm::{constants::DEFAULT_EXECUTORS_JSON, swap_encoder::builder::SwapEncoderBuilder}, + models::Chain, swap_encoder::SwapEncoder, }; diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 6a0e937..8ddcd27 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -137,6 +137,7 @@ impl From for ChainId { TychoCoreChain::ZkSync => ChainId(324), TychoCoreChain::Arbitrum => ChainId(42161), TychoCoreChain::Starknet => ChainId(0), + TychoCoreChain::Base => ChainId(8453), } } } From 0c9050cf79e78d26ca098d945f3380e73f689455 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 7 Feb 2025 11:02:59 +0000 Subject: [PATCH 12/16] fix: Uniswap v3 pool fee is big endian, not little endian --- don't change below this line --- ENG-4088 Took 18 minutes Took 17 seconds --- .../evm/swap_encoder/swap_encoders.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 7d97002..f3d0fb0 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -104,7 +104,7 @@ impl SwapEncoder for UniswapV3SwapEncoder { let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address); let component_id = Address::from_str(&swap.component.id) .map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?; - let mut pool_fee_bytes = swap + let pool_fee_bytes = swap .component .static_attributes .get("fee") @@ -113,19 +113,16 @@ impl SwapEncoder for UniswapV3SwapEncoder { "Pool fee not found in Uniswap v3 static attributes".to_string(), ) })? - .as_ref() .to_vec(); - // Reverse to get be bytes, since this is encoded as le bytes - pool_fee_bytes.reverse(); + // this is necessary to pad on the left with zeros if the fee is less than 3 bytes + let mut padded_fee_bytes = [0u8; 3]; + let start = 3 - pool_fee_bytes.len(); + padded_fee_bytes[start..].copy_from_slice(&pool_fee_bytes); - let pool_fee_u24: [u8; 3] = pool_fee_bytes[pool_fee_bytes.len() - 3..] + let pool_fee_u24: [u8; 3] = padded_fee_bytes[(padded_fee_bytes.len() - 3)..] .try_into() - .map_err(|_| { - EncodingError::FatalError( - "Pool fee static attribute must be at least 3 bytes".to_string(), - ) - })?; + .map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?; let args = ( token_in_address, @@ -214,6 +211,7 @@ mod tests { use std::collections::HashMap; use alloy::hex::encode; + use num_bigint::BigInt; use tycho_core::{dto::ProtocolComponent, Bytes}; use super::*; @@ -257,9 +255,10 @@ mod tests { } #[test] fn test_encode_uniswap_v3() { - let encoded_pool_fee: [u8; 4] = 500u32.to_le_bytes(); + let fee = BigInt::from(500); + let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee[..3].to_vec())); + static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee.to_vec())); let usv3_pool = ProtocolComponent { id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), From cad9f394cdbd22850417b09a1f590ee41245946a Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 7 Feb 2025 11:35:38 +0000 Subject: [PATCH 13/16] feat: The execution structs should receiver tycho_core Chain and convert it later to an execution model Chain --- don't change below this line --- ENG-4088 Took 8 minutes --- examples/quickstart/main.rs | 10 +++++----- .../strategy_encoder/strategy_encoder_registry.rs | 3 ++- src/encoding/evm/tycho_encoder.rs | 14 ++++++-------- src/encoding/strategy_encoder.rs | 8 ++------ 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index 77a18c9..e0db168 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -10,7 +10,7 @@ use tycho_execution::encoding::{ strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry, tycho_encoder::EVMTychoEncoder, }, - models::{Chain, Solution, Swap}, + models::{Solution, Swap}, strategy_encoder::StrategyEncoderRegistry, tycho_encoder::TychoEncoder, }; @@ -23,13 +23,13 @@ fn main() { let user_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") .expect("Failed to create user address"); - let eth_chain = Chain::from(TychoCoreChain::Ethereum); // Initialize the encoder let strategy_encoder_registry = - EVMStrategyEncoderRegistry::new(eth_chain.clone(), None, signer_pk.clone()) + EVMStrategyEncoderRegistry::new(TychoCoreChain::Ethereum, None, signer_pk.clone()) .expect("Failed to create strategy encoder registry"); - let encoder = EVMTychoEncoder::new(strategy_encoder_registry, router_address, eth_chain) - .expect("Failed to create encoder"); + let encoder = + EVMTychoEncoder::new(strategy_encoder_registry, router_address, TychoCoreChain::Ethereum) + .expect("Failed to create encoder"); // ------------------- Encode a simple swap ------------------- diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index a245476..ddcfcd9 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -21,10 +21,11 @@ pub struct EVMStrategyEncoderRegistry { impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry { fn new( - chain: Chain, + chain: tycho_core::dto::Chain, executors_file_path: Option<&str>, signer_pk: Option, ) -> Result { + let chain = Chain::from(chain); let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain.clone())?; let mut strategies: HashMap> = HashMap::new(); diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index baa4f0f..d9912ff 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -31,10 +31,11 @@ impl EVMTychoEncoder { pub fn new( strategy_registry: S, router_address: String, - chain: Chain, + chain: tycho_core::dto::Chain, ) -> Result { let router_address = Bytes::from_str(&router_address) .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; + let chain: Chain = Chain::from(chain); if chain.name != *"ethereum" { return Err(EncodingError::InvalidInput( "Currently only Ethereum is supported".to_string(), @@ -151,10 +152,6 @@ mod tests { strategy: Box, } - fn eth_chain() -> Chain { - TychoCoreChain::Ethereum.into() - } - fn dai() -> Bytes { Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap() } @@ -169,7 +166,7 @@ mod tests { impl StrategyEncoderRegistry for MockStrategyRegistry { fn new( - _chain: Chain, + _chain: tycho_core::dto::Chain, _executors_file_path: Option<&str>, _signer_pk: Option, ) -> Result { @@ -210,11 +207,12 @@ mod tests { } fn get_mocked_tycho_encoder() -> EVMTychoEncoder { - let strategy_registry = MockStrategyRegistry::new(eth_chain(), None, None).unwrap(); + let strategy_registry = + MockStrategyRegistry::new(TychoCoreChain::Ethereum, None, None).unwrap(); EVMTychoEncoder::new( strategy_registry, "0x1234567890abcdef1234567890abcdef12345678".to_string(), - eth_chain(), + TychoCoreChain::Ethereum, ) .unwrap() } diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 101eaa4..89a128f 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,10 +1,6 @@ -use tycho_core::Bytes; +use tycho_core::{dto::Chain, Bytes}; -use crate::encoding::{ - errors::EncodingError, - models::{Chain, Solution}, - swap_encoder::SwapEncoder, -}; +use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::SwapEncoder}; /// Encodes a solution using a specific strategy. pub trait StrategyEncoder { From d7f20aa74fdae67b096aebb376ca8d11cb72c930 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 7 Feb 2025 12:24:26 +0000 Subject: [PATCH 14/16] fix: Don't have a DEFAULT_CONFIG_PATH in bin This is unnecessary since we already have the executor address loaded at build time --- don't change below this line --- ENG-4088 Took 8 minutes Took 7 seconds --- src/bin/lib/cli.rs | 5 ++--- src/bin/tycho-encode.rs | 10 ++++------ .../evm/strategy_encoder/strategy_encoder_registry.rs | 2 +- src/encoding/evm/swap_encoder/swap_encoder_registry.rs | 4 ++-- src/encoding/evm/tycho_encoder.rs | 2 +- src/encoding/strategy_encoder.rs | 2 +- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/bin/lib/cli.rs b/src/bin/lib/cli.rs index 25e8786..6fd23fa 100644 --- a/src/bin/lib/cli.rs +++ b/src/bin/lib/cli.rs @@ -1,6 +1,5 @@ pub use clap::Parser; pub const DEFAULT_ROUTER_ADDRESS: &str = "0xaa820C29648D5EA543d712cC928377Bd7206a0E7"; -pub const DEFAULT_CONFIG_PATH: &str = "src/encoding/config/executor_addresses.json"; #[derive(Parser)] /// Encode swap transactions for the Tycho router @@ -45,6 +44,6 @@ pub struct Cli { pub private_key: Option, /// Path to the executor addresses configuration file - #[arg(short, default_value = DEFAULT_CONFIG_PATH)] - pub config_path: String, + #[arg(short)] + pub config_path: Option, } diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index 62bb74d..0727118 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -33,7 +33,7 @@ fn main() -> Result<(), Box> { } // Encode the solution - let encoded = encode_swaps(&buffer, &cli.router_address, &cli.config_path, cli.private_key)?; + let encoded = encode_swaps(&buffer, &cli.router_address, cli.config_path, cli.private_key)?; // Output the encoded result as JSON to stdout println!( @@ -48,16 +48,14 @@ fn main() -> Result<(), Box> { fn encode_swaps( input: &str, router_address: &str, - config_path: &str, + config_path: Option, private_key: Option, ) -> Result> { let solution: Solution = serde_json::from_str(input)?; let chain = Chain::Ethereum; - let strategy_selector = - EVMStrategyEncoderRegistry::new(chain.into(), config_path, private_key)?; - let encoder = - EVMTychoEncoder::new(strategy_selector, router_address.to_string(), chain.into())?; + let strategy_selector = EVMStrategyEncoderRegistry::new(chain, config_path, private_key)?; + let encoder = EVMTychoEncoder::new(strategy_selector, router_address.to_string(), chain)?; let transactions = encoder.encode_router_calldata(vec![solution])?; Ok(serde_json::json!({ diff --git a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs index ddcfcd9..71b266d 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoder_registry.rs @@ -22,7 +22,7 @@ pub struct EVMStrategyEncoderRegistry { impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry { fn new( chain: tycho_core::dto::Chain, - executors_file_path: Option<&str>, + executors_file_path: Option, signer_pk: Option, ) -> Result { let chain = Chain::from(chain); diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index 006b28f..30599d3 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -18,10 +18,10 @@ impl SwapEncoderRegistry { /// Populates the registry with the `SwapEncoders` for the given blockchain by parsing the /// executors' addresses in the file at the given path. pub fn new( - executors_file_path: Option<&str>, + executors_file_path: Option, blockchain: Chain, ) -> Result { - let config_str = if let Some(path) = executors_file_path { + let config_str = if let Some(ref path) = executors_file_path { fs::read_to_string(path).map_err(|e| { EncodingError::FatalError(format!( "Error reading executors file from {:?}: {}", diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index d9912ff..6d9ef6a 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -167,7 +167,7 @@ mod tests { impl StrategyEncoderRegistry for MockStrategyRegistry { fn new( _chain: tycho_core::dto::Chain, - _executors_file_path: Option<&str>, + _executors_file_path: Option, _signer_pk: Option, ) -> Result { Ok(Self { strategy: Box::new(MockStrategy) }) diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 89a128f..40d6471 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -20,7 +20,7 @@ pub trait StrategyEncoder { pub trait StrategyEncoderRegistry { fn new( chain: Chain, - executors_file_path: Option<&str>, + executors_file_path: Option, signer_pk: Option, ) -> Result where From a234ff701f8be8a3ad28630035cfe474a4702ad5 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 7 Feb 2025 14:58:09 +0000 Subject: [PATCH 15/16] feat: Remove router_address from TychoEncoder Make the router address mandatory for the Solution attribute instead --- don't change below this line --- ENG-4088 Took 30 minutes --- README.md | 18 +++---- examples/quickstart/main.rs | 9 ++-- src/bin/lib/cli.rs | 5 -- src/bin/tycho-encode.rs | 5 +- .../evm/strategy_encoder/strategy_encoders.rs | 48 +++++++------------ src/encoding/evm/tycho_encoder.rs | 40 ++++------------ src/encoding/models.rs | 4 +- src/encoding/strategy_encoder.rs | 6 +-- 8 files changed, 42 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 3910a29..c5f163e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ designed to be safe, straightforward, and quick to set up, so anyone can start t ### Installation First, build and install the binary: + ```bash # Build the project cargo build --release @@ -19,12 +20,11 @@ cargo build --release cargo install --path . ``` -After installation, the `tycho-encode` command will be available to use from any directory in your terminal. The command accepts the following options: +After installation, the `tycho-encode` command will be available to use from any directory in your terminal. The command +accepts the following options: - `-c`: Path to the executor addresses configuration file (defaults to `src/encoding/config/executor_addresses.json`) - `-p`: Private key for signing approvals (required when direct_execution is false) -- `ROUTER_ADDRESS`: Router contract address (defaults to `0xaa820C29648D5EA543d712cC928377Bd7206a0E7`) - ### Encoding Transactions @@ -36,9 +36,6 @@ echo '' | tycho-encode # Using custom config path echo '' | tycho-encode -c /path/to/your/config.json - -# Using custom router address and config path -echo '' | tycho-encode -c /path/to/your/config.json 0x1234...5678 ``` #### Example @@ -49,7 +46,6 @@ Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2: echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","check_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":1.0}],"direct_execution":true}' | tycho-encode ``` - #### JSON Payload Structure: Solution struct The `Solution` struct is composed of the following fields: @@ -64,10 +60,10 @@ The `Solution` struct is composed of the following fields: - `expected_amount`: The expected output amount - `check_amount`: The minimum acceptable output amount (accounting for slippage) - `swaps`: Array of swap steps, each containing: - - `component`: Details about the DEX/protocol being used - - `token_in`: Input token address for this step - - `token_out`: Output token address for this step - - `split`: Proportion of tokens to route through this step (1.0 = 100%) + - `component`: Details about the DEX/protocol being used + - `token_in`: Input token address for this step + - `token_out`: Output token address for this step + - `split`: Proportion of tokens to route through this step (1.0 = 100%) - `router_address`: The address of the protocol's router contract - `direct_execution`: Boolean indicating if the transaction should be executed directly diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index e0db168..f54f5e5 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -17,7 +17,8 @@ use tycho_execution::encoding::{ fn main() { // Setup variables - let router_address = "0x1234567890abcdef1234567890abcdef12345678".to_string(); + let router_address = Bytes::from_str("0x1234567890abcdef1234567890abcdef12345678") + .expect("Failed to create router address"); let signer_pk = Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()); let user_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") @@ -27,9 +28,8 @@ fn main() { let strategy_encoder_registry = EVMStrategyEncoderRegistry::new(TychoCoreChain::Ethereum, None, signer_pk.clone()) .expect("Failed to create strategy encoder registry"); - let encoder = - EVMTychoEncoder::new(strategy_encoder_registry, router_address, TychoCoreChain::Ethereum) - .expect("Failed to create encoder"); + let encoder = EVMTychoEncoder::new(strategy_encoder_registry, TychoCoreChain::Ethereum) + .expect("Failed to create encoder"); // ------------------- Encode a simple swap ------------------- @@ -64,6 +64,7 @@ fn main() { exact_out: false, // it's an exact in solution check_amount: None, // the amount out will not be checked in execution swaps: vec![simple_swap], + router_address, ..Default::default() }; diff --git a/src/bin/lib/cli.rs b/src/bin/lib/cli.rs index 6fd23fa..8027b2a 100644 --- a/src/bin/lib/cli.rs +++ b/src/bin/lib/cli.rs @@ -1,5 +1,4 @@ pub use clap::Parser; -pub const DEFAULT_ROUTER_ADDRESS: &str = "0xaa820C29648D5EA543d712cC928377Bd7206a0E7"; #[derive(Parser)] /// Encode swap transactions for the Tycho router @@ -35,10 +34,6 @@ pub const DEFAULT_ROUTER_ADDRESS: &str = "0xaa820C29648D5EA543d712cC928377Bd7206 /// } /// ``` pub struct Cli { - /// Router contract address to use for encoding transactions - #[arg(default_value = DEFAULT_ROUTER_ADDRESS)] - pub router_address: String, - /// Private key for signing approvals (required when direct_execution is false) #[arg(short)] pub private_key: Option, diff --git a/src/bin/tycho-encode.rs b/src/bin/tycho-encode.rs index 0727118..f8bd8b3 100644 --- a/src/bin/tycho-encode.rs +++ b/src/bin/tycho-encode.rs @@ -33,7 +33,7 @@ fn main() -> Result<(), Box> { } // Encode the solution - let encoded = encode_swaps(&buffer, &cli.router_address, cli.config_path, cli.private_key)?; + let encoded = encode_swaps(&buffer, cli.config_path, cli.private_key)?; // Output the encoded result as JSON to stdout println!( @@ -47,7 +47,6 @@ fn main() -> Result<(), Box> { fn encode_swaps( input: &str, - router_address: &str, config_path: Option, private_key: Option, ) -> Result> { @@ -55,7 +54,7 @@ fn encode_swaps( let chain = Chain::Ethereum; let strategy_selector = EVMStrategyEncoderRegistry::new(chain, config_path, private_key)?; - let encoder = EVMTychoEncoder::new(strategy_selector, router_address.to_string(), chain)?; + let encoder = EVMTychoEncoder::new(strategy_selector, chain)?; let transactions = encoder.encode_router_calldata(vec![solution])?; Ok(serde_json::json!({ diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index dd3abfc..fa2b211 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -261,11 +261,7 @@ impl SplitSwapStrategyEncoder { impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} impl StrategyEncoder for SplitSwapStrategyEncoder { - fn encode_strategy( - &self, - solution: Solution, - router_address: Bytes, - ) -> Result<(Vec, Bytes), EncodingError> { + fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { self.validate_split_percentages(&solution.swaps)?; self.validate_swap_path( &solution.swaps, @@ -274,7 +270,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { &solution.native_action, )?; let (permit, signature) = self.permit2.get_permit( - &router_address, + &solution.router_address, &solution.sender, &solution.given_token, &solution.given_amount, @@ -346,9 +342,9 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { })?; let encoding_context = EncodingContext { - receiver: router_address.clone(), + receiver: solution.router_address.clone(), exact_out: solution.exact_out, - router_address: router_address.clone(), + router_address: solution.router_address.clone(), }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; let swap_data = self.encode_swap_header( @@ -399,7 +395,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { .abi_encode(); let contract_interaction = encode_input(&self.selector, method_calldata); - Ok((contract_interaction, router_address)) + Ok((contract_interaction, solution.router_address)) } fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box> { @@ -429,17 +425,7 @@ impl ExecutorStrategyEncoder { } impl EVMStrategyEncoder for ExecutorStrategyEncoder {} impl StrategyEncoder for ExecutorStrategyEncoder { - fn encode_strategy( - &self, - solution: Solution, - _router_address: Bytes, - ) -> Result<(Vec, Bytes), EncodingError> { - let router_address = solution.router_address.ok_or_else(|| { - EncodingError::InvalidInput( - "Router address is required for straight-to-executor solutions".to_string(), - ) - })?; - + fn encode_strategy(&self, solution: Solution) -> Result<(Vec, Bytes), EncodingError> { let swap = solution .swaps .first() @@ -457,7 +443,7 @@ impl StrategyEncoder for ExecutorStrategyEncoder { let encoding_context = EncodingContext { receiver: solution.receiver, exact_out: solution.exact_out, - router_address, + router_address: solution.router_address, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; @@ -540,13 +526,13 @@ mod tests { receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), swaps: vec![swap], direct_execution: true, - router_address: Some(Bytes::zero(20)), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), slippage: None, native_action: None, }; let (protocol_data, executor_address) = encoder - .encode_strategy(solution, Bytes::zero(20)) + .encode_strategy(solution) .unwrap(); let hex_protocol_data = encode(&protocol_data); assert_eq!( @@ -631,13 +617,13 @@ mod tests { check_amount, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), swaps: vec![swap], ..Default::default() }; - let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(); let (calldata, _) = encoder - .encode_strategy(solution, router_address) + .encode_strategy(solution) .unwrap(); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ @@ -731,14 +717,14 @@ mod tests { check_amount: None, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), swaps: vec![swap], native_action: Some(NativeAction::Wrap), ..Default::default() }; - let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(); let (calldata, _) = encoder - .encode_strategy(solution, router_address) + .encode_strategy(solution) .unwrap(); let hex_calldata = encode(&calldata); @@ -779,14 +765,14 @@ mod tests { check_amount: None, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), swaps: vec![swap], native_action: Some(NativeAction::Unwrap), ..Default::default() }; - let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(); let (calldata, _) = encoder - .encode_strategy(solution, router_address) + .encode_strategy(solution) .unwrap(); let hex_calldata = encode(&calldata); @@ -868,13 +854,13 @@ mod tests { check_amount: None, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), + router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(), swaps: vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc], ..Default::default() }; - let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(); let (calldata, _) = encoder - .encode_strategy(solution, router_address) + .encode_strategy(solution) .unwrap(); let _hex_calldata = encode(&calldata); diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index 6d9ef6a..6e66755 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use num_bigint::BigUint; use tycho_core::Bytes; @@ -10,31 +8,22 @@ use crate::encoding::{ tycho_encoder::TychoEncoder, }; -/// Represents an encoder for a swap through the given router address using any strategy supported -/// by the strategy registry. +/// Represents an encoder for a swap using any strategy supported by the strategy registry. /// /// # Fields /// * `strategy_registry`: S, the strategy registry to use to select the best strategy to encode a /// solution, based on its supported strategies and the solution attributes. -/// * `router_address`: Bytes, the address of the router to use to execute the swaps. /// * `native_address`: Address of the chain's native token /// * `wrapped_address`: Address of the chain's wrapped native token #[derive(Clone)] pub struct EVMTychoEncoder { strategy_registry: S, - router_address: Bytes, native_address: Bytes, wrapped_address: Bytes, } impl EVMTychoEncoder { - pub fn new( - strategy_registry: S, - router_address: String, - chain: tycho_core::dto::Chain, - ) -> Result { - let router_address = Bytes::from_str(&router_address) - .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; + pub fn new(strategy_registry: S, chain: tycho_core::dto::Chain) -> Result { let chain: Chain = Chain::from(chain); if chain.name != *"ethereum" { return Err(EncodingError::InvalidInput( @@ -43,7 +32,6 @@ impl EVMTychoEncoder { } Ok(EVMTychoEncoder { strategy_registry, - router_address, native_address: chain.native_token()?, wrapped_address: chain.wrapped_token()?, }) @@ -113,16 +101,11 @@ impl TychoEncoder for EVMTychoEncoder { for solution in solutions.iter() { self.validate_solution(solution)?; - let router_address = solution - .router_address - .clone() - .unwrap_or(self.router_address.clone()); - let strategy = self .strategy_registry .get_encoder(solution)?; let (contract_interaction, target_address) = - strategy.encode_strategy(solution.clone(), router_address)?; + strategy.encode_strategy(solution.clone())?; let value = match solution.native_action.as_ref() { Some(NativeAction::Wrap) => solution.given_amount.clone(), @@ -141,6 +124,8 @@ impl TychoEncoder for EVMTychoEncoder { #[cfg(test)] mod tests { + use std::str::FromStr; + use tycho_core::dto::{Chain as TychoCoreChain, ProtocolComponent}; use super::*; @@ -185,11 +170,7 @@ mod tests { struct MockStrategy; impl StrategyEncoder for MockStrategy { - fn encode_strategy( - &self, - _solution: Solution, - _router_address: Bytes, - ) -> Result<(Vec, Bytes), EncodingError> { + fn encode_strategy(&self, _solution: Solution) -> Result<(Vec, Bytes), EncodingError> { Ok(( Bytes::from_str("0x1234") .unwrap() @@ -209,12 +190,7 @@ mod tests { fn get_mocked_tycho_encoder() -> EVMTychoEncoder { let strategy_registry = MockStrategyRegistry::new(TychoCoreChain::Ethereum, None, None).unwrap(); - EVMTychoEncoder::new( - strategy_registry, - "0x1234567890abcdef1234567890abcdef12345678".to_string(), - TychoCoreChain::Ethereum, - ) - .unwrap() + EVMTychoEncoder::new(strategy_registry, TychoCoreChain::Ethereum).unwrap() } #[test] @@ -236,7 +212,7 @@ mod tests { exact_out: false, given_amount: eth_amount_in.clone(), given_token: eth(), - router_address: None, + router_address: Bytes::from_str("0x1234567890abcdef1234567890abcdef12345678").unwrap(), swaps: vec![swap], native_action: Some(NativeAction::Wrap), ..Default::default() diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 8ddcd27..4853fcc 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -41,8 +41,8 @@ pub struct Solution { pub check_amount: Option, /// List of swaps to fulfill the solution. pub swaps: Vec, - /// If not set, then the Tycho Router will be used - pub router_address: Option, + /// Address of the router contract to be used for the swaps. + pub router_address: Bytes, /// If set, the corresponding native action will be executed. pub native_action: Option, /// If set to true, the solution will be encoded to be sent directly to the Executor and diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 40d6471..744df63 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -4,11 +4,7 @@ use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::Swa /// Encodes a solution using a specific strategy. pub trait StrategyEncoder { - fn encode_strategy( - &self, - to_encode: Solution, - router_address: Bytes, - ) -> Result<(Vec, Bytes), EncodingError>; + fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec, Bytes), EncodingError>; #[allow(clippy::borrowed_box)] fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box>; From c8a200dc55bf1c137d0412b0c83c1d7808ea6412 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 7 Feb 2025 16:16:55 +0000 Subject: [PATCH 16/16] docs: Add mention to the quickstart in the main README.md --- don't change below this line --- ENG-4088 Took 5 minutes --- README.md | 4 ++++ examples/quickstart/{Readme.md => README.md} | 0 2 files changed, 4 insertions(+) rename examples/quickstart/{Readme.md => README.md} (100%) diff --git a/README.md b/README.md index c5f163e..bc64ddd 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Tycho Execution makes it easy to trade on different DEXs by handling the complex custom code for each DEX, you get a simple, ready-to-use tool that generates the necessary data to execute trades. It's designed to be safe, straightforward, and quick to set up, so anyone can start trading without extra effort. +## Quickstart + +To get started, have a look at our [Quickstart example](examples/quickstart/README.md). + ## Bin Usage Guide ### Installation diff --git a/examples/quickstart/Readme.md b/examples/quickstart/README.md similarity index 100% rename from examples/quickstart/Readme.md rename to examples/quickstart/README.md