diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index ee89104..f01b8e9 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -25,7 +25,7 @@ fn main() { let strategy_encoder_registry = EVMStrategyEncoderRegistry::new(Chain::Ethereum, executors_file_path, signer_pk.clone()) .expect("Failed to create strategy encoder registry"); - let encoder = EVMTychoEncoder::new(strategy_encoder_registry, router_address) + let encoder = EVMTychoEncoder::new(strategy_encoder_registry, router_address, Chain::Ethereum) .expect("Failed to create encoder"); // ------------------- Encode a simple swap ------------------- diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index f0fa47d..af1c5d9 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -1,10 +1,18 @@ use alloy_primitives::hex; -use lazy_static::lazy_static; -use tycho_core::Bytes; +use tycho_core::{models::Chain, Bytes}; -lazy_static! { - pub static ref NATIVE_ADDRESS: Bytes = - Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec()); - pub static ref WETH_ADDRESS: Bytes = - Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()); +pub fn native_address(chain: Chain) -> Bytes { + match chain { + Chain::Ethereum => Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec()), + // Placeholder values for other chains; update with real addresses + _ => Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec()), + } +} + +pub fn wrapped_address(chain: Chain) -> Bytes { + match chain { + Chain::Ethereum => Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec()), + // Placeholder values for other chains; update with real addresses + _ => Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec()), + } } diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 62a873e..a6f37f6 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -9,7 +9,7 @@ use crate::encoding::{ errors::EncodingError, evm::{ approvals::permit2::Permit2, - constants::WETH_ADDRESS, + constants::wrapped_address, swap_encoder::swap_encoder_registry::SwapEncoderRegistry, utils::{biguint_to_u256, bytes_to_address, encode_input, percentage_to_uint24}, }, @@ -58,6 +58,7 @@ pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, permit2: Permit2, selector: String, + wrapped_address: Bytes, } impl SplitSwapStrategyEncoder { @@ -67,7 +68,13 @@ impl SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, ) -> Result { 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, swap_encoder_registry }) + let wrapped_address = wrapped_address(chain); + Ok(Self { + permit2: Permit2::new(signer_pk, chain)?, + selector, + swap_encoder_registry, + wrapped_address, + }) } } impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} @@ -126,14 +133,14 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len()); if wrap { - tokens.push(WETH_ADDRESS.clone()); + tokens.push(self.wrapped_address.clone()); } else { tokens.push(solution.given_token.clone()); } tokens.extend(intermediary_tokens); if unwrap { - tokens.push(WETH_ADDRESS.clone()); + tokens.push(self.wrapped_address.clone()); } else { tokens.push(solution.checked_token.clone()); } @@ -278,9 +285,16 @@ mod tests { use super::*; use crate::encoding::{ - evm::constants::{NATIVE_ADDRESS, WETH_ADDRESS}, + evm::constants::{native_address, wrapped_address}, models::Swap, }; + fn eth() -> Bytes { + native_address(Chain::Ethereum) + } + + fn weth() -> Bytes { + wrapped_address(Chain::Ethereum) + } fn get_swap_encoder_registry() -> SwapEncoderRegistry { SwapEncoderRegistry::new("src/encoding/config/executor_addresses.json", Chain::Ethereum) @@ -292,7 +306,7 @@ mod tests { let swap_encoder_registry = get_swap_encoder_registry(); let encoder = ExecutorStrategyEncoder::new(swap_encoder_registry); - let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_in = weth(); let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); let swap = Swap { @@ -494,7 +508,7 @@ mod tests { protocol_system: "uniswap_v2".to_string(), ..Default::default() }, - token_in: WETH_ADDRESS.clone(), + token_in: weth(), token_out: dai.clone(), split: 0f64, }; @@ -504,7 +518,7 @@ mod tests { .unwrap(); let solution = Solution { exact_out: false, - given_token: NATIVE_ADDRESS.clone(), + given_token: eth(), given_amount: BigUint::from_str("1_000000000000000000").unwrap(), checked_token: dai, expected_amount: Some(BigUint::from_str("3_000_000000000000000000").unwrap()), @@ -544,7 +558,7 @@ mod tests { ..Default::default() }, token_in: dai.clone(), - token_out: WETH_ADDRESS.clone(), + token_out: weth(), split: 0f64, }; let swap_encoder_registry = get_swap_encoder_registry(); @@ -555,7 +569,7 @@ mod tests { exact_out: false, given_token: dai, given_amount: BigUint::from_str("3_000_000000000000000000").unwrap(), - checked_token: NATIVE_ADDRESS.clone(), + checked_token: eth(), expected_amount: Some(BigUint::from_str("1_000000000000000000").unwrap()), check_amount: None, sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), @@ -590,7 +604,7 @@ mod tests { let private_key = "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string(); - let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap(); + let weth = weth(); let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap(); let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap(); let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); diff --git a/src/encoding/evm/tycho_encoder.rs b/src/encoding/evm/tycho_encoder.rs index 1a93400..e03576b 100644 --- a/src/encoding/evm/tycho_encoder.rs +++ b/src/encoding/evm/tycho_encoder.rs @@ -1,11 +1,11 @@ use std::str::FromStr; use num_bigint::BigUint; -use tycho_core::Bytes; +use tycho_core::{models::Chain, Bytes}; use crate::encoding::{ errors::EncodingError, - evm::constants::{NATIVE_ADDRESS, WETH_ADDRESS}, + evm::constants::{native_address, wrapped_address}, models::{NativeAction, Solution, Transaction}, strategy_encoder::StrategyEncoderRegistry, tycho_encoder::TychoEncoder, @@ -14,13 +14,26 @@ use crate::encoding::{ pub struct EVMTychoEncoder { strategy_selector: S, router_address: Bytes, + native_address: Bytes, + wrapped_address: Bytes, } impl EVMTychoEncoder { - pub fn new(strategy_selector: S, router_address: String) -> Result { + pub fn new( + strategy_selector: S, + router_address: String, + chain: Chain, + ) -> Result { let router_address = Bytes::from_str(&router_address) .map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?; - Ok(EVMTychoEncoder { strategy_selector, router_address }) + let native_address = native_address(chain); + let wrapped_address = wrapped_address(chain); + if chain != Chain::Ethereum { + return Err(EncodingError::InvalidInput( + "Currently only Ethereum is supported".to_string(), + )); + } + Ok(EVMTychoEncoder { strategy_selector, router_address, native_address, wrapped_address }) } } @@ -36,28 +49,30 @@ impl EVMTychoEncoder { } if let Some(native_action) = solution.clone().native_action { if native_action == NativeAction::Wrap { - if solution.given_token != *NATIVE_ADDRESS { + if solution.given_token != self.native_address { return Err(EncodingError::FatalError( - "ETH must be the input token in order to wrap".to_string(), + "Native token must be the input token in order to wrap".to_string(), )); } if let Some(first_swap) = solution.swaps.first() { - if first_swap.token_in != *WETH_ADDRESS { + if first_swap.token_in != self.wrapped_address { return Err(EncodingError::FatalError( - "WETH must be the first swap's input in order to wrap".to_string(), + "Wrapped token must be the first swap's input in order to wrap" + .to_string(), )); } } } else if native_action == NativeAction::Unwrap { - if solution.checked_token != *NATIVE_ADDRESS { + if solution.checked_token != self.native_address { return Err(EncodingError::FatalError( - "ETH must be the output token in order to unwrap".to_string(), + "Native token must be the output token in order to unwrap".to_string(), )); } if let Some(last_swap) = solution.swaps.last() { - if last_swap.token_out != *WETH_ADDRESS { + if last_swap.token_out != self.wrapped_address { return Err(EncodingError::FatalError( - "WETH must be the last swap's output in order to unwrap".to_string(), + "Wrapped token must be the last swap's output in order to unwrap" + .to_string(), )); } } @@ -119,6 +134,14 @@ mod tests { Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap() } + fn eth() -> Bytes { + native_address(Chain::Ethereum) + } + + fn weth() -> Bytes { + wrapped_address(Chain::Ethereum) + } + impl StrategyEncoderRegistry for MockStrategyRegistry { fn new( _chain: Chain, @@ -162,6 +185,7 @@ mod tests { EVMTychoEncoder::new( strategy_selector, "0x1234567890abcdef1234567890abcdef12345678".to_string(), + Chain::Ethereum, ) .unwrap() } @@ -176,7 +200,7 @@ mod tests { protocol_system: "uniswap_v2".to_string(), ..Default::default() }, - token_in: WETH_ADDRESS.clone(), + token_in: weth(), token_out: dai(), split: 0f64, }; @@ -184,7 +208,7 @@ mod tests { let solution = Solution { exact_out: false, given_amount: eth_amount_in.clone(), - given_token: NATIVE_ADDRESS.clone(), + given_token: eth(), router_address: None, swaps: vec![swap], native_action: Some(NativeAction::Wrap), @@ -228,14 +252,14 @@ mod tests { protocol_system: "uniswap_v2".to_string(), ..Default::default() }, - token_in: WETH_ADDRESS.clone(), + token_in: weth(), token_out: dai(), split: 0f64, }; let solution = Solution { exact_out: false, - given_token: NATIVE_ADDRESS.clone(), + given_token: eth(), checked_token: dai(), check_amount: None, swaps: vec![swap], @@ -257,14 +281,14 @@ mod tests { protocol_system: "uniswap_v2".to_string(), ..Default::default() }, - token_in: WETH_ADDRESS.clone(), + token_in: weth(), token_out: dai(), split: 0f64, }; let solution = Solution { exact_out: false, - given_token: WETH_ADDRESS.clone(), + given_token: weth(), swaps: vec![swap], native_action: Some(NativeAction::Wrap), ..Default::default() @@ -275,7 +299,9 @@ mod tests { assert!(result.is_err()); assert_eq!( result.err().unwrap(), - EncodingError::FatalError("ETH must be the input token in order to wrap".to_string()) + EncodingError::FatalError( + "Native token must be the input token in order to wrap".to_string() + ) ); } @@ -288,14 +314,14 @@ mod tests { protocol_system: "uniswap_v2".to_string(), ..Default::default() }, - token_in: NATIVE_ADDRESS.clone(), + token_in: eth(), token_out: dai(), split: 0f64, }; let solution = Solution { exact_out: false, - given_token: NATIVE_ADDRESS.clone(), + given_token: eth(), swaps: vec![swap], native_action: Some(NativeAction::Wrap), ..Default::default() @@ -307,7 +333,7 @@ mod tests { assert_eq!( result.err().unwrap(), EncodingError::FatalError( - "WETH must be the first swap's input in order to wrap".to_string() + "Wrapped token must be the first swap's input in order to wrap".to_string() ) ); } @@ -317,7 +343,7 @@ mod tests { let encoder = get_mocked_tycho_encoder(); let solution = Solution { exact_out: false, - given_token: NATIVE_ADDRESS.clone(), + given_token: eth(), swaps: vec![], native_action: Some(NativeAction::Wrap), ..Default::default() @@ -342,13 +368,13 @@ mod tests { ..Default::default() }, token_in: dai(), - token_out: WETH_ADDRESS.clone(), + token_out: weth(), split: 0f64, }; let solution = Solution { exact_out: false, - checked_token: NATIVE_ADDRESS.clone(), + checked_token: eth(), check_amount: None, swaps: vec![swap], native_action: Some(NativeAction::Unwrap), @@ -370,14 +396,14 @@ mod tests { ..Default::default() }, token_in: dai(), - token_out: WETH_ADDRESS.clone(), + token_out: weth(), split: 0f64, }; let solution = Solution { exact_out: false, given_token: dai(), - checked_token: WETH_ADDRESS.clone(), + checked_token: weth(), swaps: vec![swap], native_action: Some(NativeAction::Unwrap), ..Default::default() @@ -389,7 +415,7 @@ mod tests { assert_eq!( result.err().unwrap(), EncodingError::FatalError( - "ETH must be the output token in order to unwrap".to_string() + "Native token must be the output token in order to unwrap".to_string() ) ); } @@ -404,13 +430,13 @@ mod tests { ..Default::default() }, token_in: dai(), - token_out: NATIVE_ADDRESS.clone(), + token_out: eth(), split: 0f64, }; let solution = Solution { exact_out: false, - checked_token: NATIVE_ADDRESS.clone(), + checked_token: eth(), swaps: vec![swap], native_action: Some(NativeAction::Unwrap), ..Default::default() @@ -422,7 +448,7 @@ mod tests { assert_eq!( result.err().unwrap(), EncodingError::FatalError( - "WETH must be the last swap's output in order to unwrap".to_string() + "Wrapped token must be the last swap's output in order to unwrap".to_string() ) ); }