diff --git a/src/encoding/errors.rs b/src/encoding/errors.rs index f4aa617..6581c6c 100644 --- a/src/encoding/errors.rs +++ b/src/encoding/errors.rs @@ -12,7 +12,7 @@ use thiserror::Error; /// - `RecoverableError`: Indicates that the encoding has failed with a recoverable error. Retrying /// at a later time may succeed. It may have failed due to a temporary issue, such as a network /// problem. -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] #[allow(dead_code)] pub enum EncodingError { #[error("Invalid input: {0}")] diff --git a/src/encoding/evm/mod.rs b/src/encoding/evm/mod.rs index 031dbc4..bffb498 100644 --- a/src/encoding/evm/mod.rs +++ b/src/encoding/evm/mod.rs @@ -1,6 +1,6 @@ pub mod approvals; mod models; -mod router_encoder; mod strategy_encoder; mod swap_encoder; +mod tycho_encoder; mod utils; diff --git a/src/encoding/evm/router_encoder.rs b/src/encoding/evm/router_encoder.rs deleted file mode 100644 index 896f707..0000000 --- a/src/encoding/evm/router_encoder.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::str::FromStr; - -use num_bigint::BigUint; -use tycho_core::{models::Chain, Bytes}; - -use crate::encoding::{ - errors::EncodingError, - models::{NativeAction, Solution, Transaction}, - router_encoder::RouterEncoder, - strategy_encoder::StrategySelector, -}; - -#[allow(dead_code)] -pub struct EVMRouterEncoder { - strategy_selector: S, - signer: Option, - chain: Chain, - router_address: Bytes, -} - -#[allow(dead_code)] -impl EVMRouterEncoder { - pub fn new( - strategy_selector: S, - router_address: String, - signer: Option, - chain: Chain, - ) -> Result { - let router_address = Bytes::from_str(&router_address) - .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; - Ok(EVMRouterEncoder { strategy_selector, signer, chain, router_address }) - } -} -impl RouterEncoder for EVMRouterEncoder { - fn encode_router_calldata( - &self, - solutions: Vec, - ) -> Result, EncodingError> { - let mut transactions: Vec = Vec::new(); - for solution in solutions.iter() { - if solution.exact_out { - return Err(EncodingError::FatalError( - "Currently only exact input solutions are supported".to_string(), - )); - } - - let router_address = solution - .router_address - .clone() - .unwrap_or(self.router_address.clone()); - let strategy = self.strategy_selector.select_strategy( - solution, - self.signer.clone(), - self.chain, - )?; - - let (contract_interaction, target_address) = - strategy.encode_strategy(solution.clone(), router_address)?; - - let value = match solution.native_action.as_ref() { - Some(NativeAction::Wrap) => solution.given_amount.clone(), - _ => BigUint::ZERO, - }; - - transactions.push(Transaction { - value, - data: contract_interaction, - to: target_address, - }); - } - Ok(transactions) - } -} diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs new file mode 100644 index 0000000..5be718d --- /dev/null +++ b/src/encoding/evm/tycho_encoder.rs @@ -0,0 +1,163 @@ +use std::str::FromStr; + +use num_bigint::BigUint; +use tycho_core::{models::Chain, Bytes}; + +use crate::encoding::{ + errors::EncodingError, + models::{NativeAction, Solution, Transaction}, + strategy_encoder::StrategySelector, + tycho_encoder::TychoEncoder, +}; + +#[allow(dead_code)] +pub struct EVMTychoEncoder { + strategy_selector: S, + signer: Option, + chain: Chain, + router_address: Bytes, +} + +#[allow(dead_code)] +impl EVMTychoEncoder { + pub fn new( + strategy_selector: S, + router_address: String, + signer: Option, + chain: Chain, + ) -> Result { + let router_address = Bytes::from_str(&router_address) + .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; + Ok(EVMTychoEncoder { strategy_selector, signer, chain, router_address }) + } +} +impl TychoEncoder for EVMTychoEncoder { + fn encode_router_calldata( + &self, + solutions: Vec, + ) -> Result, EncodingError> { + let mut transactions: Vec = Vec::new(); + for solution in solutions.iter() { + if solution.exact_out { + return Err(EncodingError::FatalError( + "Currently only exact input solutions are supported".to_string(), + )); + } + + let router_address = solution + .router_address + .clone() + .unwrap_or(self.router_address.clone()); + + let strategy = self.strategy_selector.select_strategy( + solution, + self.signer.clone(), + self.chain, + )?; + let (contract_interaction, target_address) = + strategy.encode_strategy(solution.clone(), router_address)?; + + let value = match solution.native_action.as_ref() { + Some(NativeAction::Wrap) => solution.given_amount.clone(), + _ => BigUint::ZERO, + }; + + transactions.push(Transaction { + value, + data: contract_interaction, + to: target_address, + }); + } + Ok(transactions) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::encoding::strategy_encoder::StrategyEncoder; + + struct MockStrategySelector; + + impl StrategySelector for MockStrategySelector { + fn select_strategy( + &self, + _solution: &Solution, + _signer: Option, + _chain: Chain, + ) -> Result, EncodingError> { + Ok(Box::new(MockStrategy)) + } + } + + struct MockStrategy; + + impl StrategyEncoder for MockStrategy { + fn encode_strategy( + &self, + _solution: Solution, + _router_address: Bytes, + ) -> Result<(Vec, Bytes), EncodingError> { + Ok(( + Bytes::from_str("0x1234") + .unwrap() + .to_vec(), + Bytes::from_str("0xabcd").unwrap(), + )) + } + } + + fn get_mocker_tycho_encoder() -> EVMTychoEncoder { + let strategy_selector = MockStrategySelector; + EVMTychoEncoder::new( + strategy_selector, + "0x1234567890abcdef1234567890abcdef12345678".to_string(), + Some("0xabcdef".to_string()), + Chain::Ethereum, + ) + .unwrap() + } + + #[test] + fn test_encode_router_calldata() { + let encoder = get_mocker_tycho_encoder(); + + let eth_amount_in = BigUint::from(1000u32); + let solution = Solution { + exact_out: false, + given_amount: eth_amount_in.clone(), + router_address: None, + native_action: Some(NativeAction::Wrap), + ..Default::default() + }; + + let transactions = encoder.encode_router_calldata(vec![solution]); + + assert!(transactions.is_ok()); + let transactions = transactions.unwrap(); + assert_eq!(transactions.len(), 1); + assert_eq!(transactions[0].value, eth_amount_in); + assert_eq!(transactions[0].data, Bytes::from_str("0x1234").unwrap()); + assert_eq!(transactions[0].to, Bytes::from_str("0xabcd").unwrap()); + } + + #[test] + fn test_encode_router_calldata_fails_for_exact_out() { + let encoder = get_mocker_tycho_encoder(); + + let solution = Solution { + exact_out: true, // This should cause an error + ..Default::default() + }; + + let result = encoder.encode_router_calldata(vec![solution]); + + assert!(result.is_err()); + assert_eq!( + result.err().unwrap(), + EncodingError::FatalError( + "Currently only exact input solutions are supported".to_string() + ) + ); + } +} diff --git a/src/encoding/mod.rs b/src/encoding/mod.rs index ef2afaa..6d169f5 100644 --- a/src/encoding/mod.rs +++ b/src/encoding/mod.rs @@ -2,6 +2,6 @@ mod errors; #[cfg(feature = "evm")] mod evm; mod models; -mod router_encoder; mod strategy_encoder; mod swap_encoder; +mod tycho_encoder; diff --git a/src/encoding/router_encoder.rs b/src/encoding/tycho_encoder.rs similarity index 85% rename from src/encoding/router_encoder.rs rename to src/encoding/tycho_encoder.rs index e3af9bb..bfec250 100644 --- a/src/encoding/router_encoder.rs +++ b/src/encoding/tycho_encoder.rs @@ -5,7 +5,7 @@ use crate::encoding::{ }; #[allow(dead_code)] -pub trait RouterEncoder { +pub trait TychoEncoder { fn encode_router_calldata( &self, solutions: Vec,