diff --git a/config/protocol_specific_addresses.json b/config/protocol_specific_addresses.json new file mode 100644 index 0000000..c503d02 --- /dev/null +++ b/config/protocol_specific_addresses.json @@ -0,0 +1,11 @@ +{ + "ethereum": { + "vm:balancer_v2": { + "vault_address": "0xba12222222228d8ba445958a75a0704d566bf2c8" + }, + "vm:curve": { + "native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "meta_registry_address": "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC" + } + } +} \ No newline at end of file diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index b3d33fc..feba1cc 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -2,6 +2,8 @@ use std::{collections::HashSet, sync::LazyLock}; pub const DEFAULT_EXECUTORS_JSON: &str = include_str!("../../../config/executor_addresses.json"); pub const DEFAULT_ROUTERS_JSON: &str = include_str!("../../../config/router_addresses.json"); +pub const PROTOCOL_SPECIFIC_CONFIG: &str = + include_str!("../../../config/protocol_specific_addresses.json"); /// These protocols support the optimization of grouping swaps. /// diff --git a/src/encoding/evm/swap_encoder/builder.rs b/src/encoding/evm/swap_encoder/builder.rs index e1e585f..bbe2c2d 100644 --- a/src/encoding/evm/swap_encoder/builder.rs +++ b/src/encoding/evm/swap_encoder/builder.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::encoding::{ errors::EncodingError, evm::swap_encoder::swap_encoders::{ @@ -13,42 +15,67 @@ pub struct SwapEncoderBuilder { protocol_system: String, executor_address: String, chain: Chain, + config: Option>, } impl SwapEncoderBuilder { - pub fn new(protocol_system: &str, executor_address: &str, chain: Chain) -> Self { + pub fn new( + protocol_system: &str, + executor_address: &str, + chain: Chain, + config: Option>, + ) -> Self { SwapEncoderBuilder { protocol_system: protocol_system.to_string(), executor_address: executor_address.to_string(), chain, + config, } } pub fn build(self) -> Result, EncodingError> { match self.protocol_system.as_str() { - "uniswap_v2" => { - Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?)) + "uniswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "sushiswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "pancakeswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "vm:balancer_v2" => Ok(Box::new(BalancerV2SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "pancakeswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "uniswap_v4" => Ok(Box::new(UniswapV4SwapEncoder::new( + self.executor_address, + self.chain, + self.config, + )?)), + "ekubo_v2" => { + Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain, self.config)?)) } - "sushiswap_v2" => { - Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?)) + "vm:curve" => { + Ok(Box::new(CurveSwapEncoder::new(self.executor_address, self.chain, self.config)?)) } - "pancakeswap_v2" => { - Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?)) - } - "vm:balancer_v2" => { - Ok(Box::new(BalancerV2SwapEncoder::new(self.executor_address, self.chain)?)) - } - "uniswap_v3" => { - Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address, self.chain)?)) - } - "pancakeswap_v3" => { - Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address, self.chain)?)) - } - "uniswap_v4" => { - Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address, self.chain)?)) - } - "ekubo_v2" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain)?)), - "vm:curve" => Ok(Box::new(CurveSwapEncoder::new(self.executor_address, self.chain)?)), _ => Err(EncodingError::FatalError(format!( "Unknown protocol system: {}", self.protocol_system diff --git a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs index b90a2cf..130e4cd 100644 --- a/src/encoding/evm/swap_encoder/swap_encoder_registry.rs +++ b/src/encoding/evm/swap_encoder/swap_encoder_registry.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, fs}; use crate::encoding::{ errors::EncodingError, - evm::{constants::DEFAULT_EXECUTORS_JSON, swap_encoder::builder::SwapEncoderBuilder}, + evm::{ + constants::{DEFAULT_EXECUTORS_JSON, PROTOCOL_SPECIFIC_CONFIG}, + swap_encoder::builder::SwapEncoderBuilder, + }, models::Chain, swap_encoder::SwapEncoder, }; @@ -33,12 +36,27 @@ impl SwapEncoderRegistry { DEFAULT_EXECUTORS_JSON.to_string() }; let config: HashMap> = serde_json::from_str(&config_str)?; - let mut encoders = HashMap::new(); let executors = config .get(&chain.name) .ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?; + + let protocol_specific_config: HashMap>> = + serde_json::from_str(PROTOCOL_SPECIFIC_CONFIG)?; + let protocol_specific_config = protocol_specific_config + .get(&chain.name) + .ok_or(EncodingError::FatalError( + "No protocol specific config found for chain".to_string(), + ))?; + let mut encoders = HashMap::new(); for (protocol, executor_address) in executors { - let builder = SwapEncoderBuilder::new(protocol, executor_address, chain.clone()); + let builder = SwapEncoderBuilder::new( + protocol, + executor_address, + chain.clone(), + protocol_specific_config + .get(protocol) + .cloned(), + ); let encoder = builder.build()?; encoders.insert(protocol.to_string(), encoder); } diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 4480663..eff2e0d 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use alloy::{ providers::Provider, @@ -39,7 +39,11 @@ impl UniswapV2SwapEncoder { } impl SwapEncoder for UniswapV2SwapEncoder { - fn new(executor_address: String, _chain: Chain) -> Result { + fn new( + executor_address: String, + _chain: Chain, + _config: Option>, + ) -> Result { Ok(Self { executor_address }) } @@ -93,7 +97,11 @@ impl UniswapV3SwapEncoder { } impl SwapEncoder for UniswapV3SwapEncoder { - fn new(executor_address: String, _chain: Chain) -> Result { + fn new( + executor_address: String, + _chain: Chain, + _config: Option>, + ) -> Result { Ok(Self { executor_address }) } @@ -151,7 +159,11 @@ impl UniswapV4SwapEncoder { } impl SwapEncoder for UniswapV4SwapEncoder { - fn new(executor_address: String, _chain: Chain) -> Result { + fn new( + executor_address: String, + _chain: Chain, + _config: Option>, + ) -> Result { Ok(Self { executor_address }) } @@ -224,12 +236,23 @@ pub struct BalancerV2SwapEncoder { } impl SwapEncoder for BalancerV2SwapEncoder { - fn new(executor_address: String, _chain: Chain) -> Result { - Ok(Self { - executor_address, - vault_address: "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), - }) + fn new( + executor_address: String, + _chain: Chain, + config: Option>, + ) -> Result { + let config = config.ok_or(EncodingError::FatalError( + "Missing balancer specific addresses in config".to_string(), + ))?; + let vault_address = config + .get("vault_address") + .ok_or(EncodingError::FatalError( + "Missing balancer vault address in config".to_string(), + ))? + .to_string(); + Ok(Self { executor_address, vault_address }) } + fn encode_swap( &self, swap: Swap, @@ -282,7 +305,11 @@ pub struct EkuboSwapEncoder { } impl SwapEncoder for EkuboSwapEncoder { - fn new(executor_address: String, _chain: Chain) -> Result { + fn new( + executor_address: String, + _chain: Chain, + _config: Option>, + ) -> Result { Ok(Self { executor_address }) } @@ -348,7 +375,7 @@ impl SwapEncoder for EkuboSwapEncoder { pub struct CurveSwapEncoder { executor_address: String, meta_registry_address: String, - native_token_curve_address: Address, + native_token_curve_address: String, native_token_address: Bytes, } @@ -438,31 +465,51 @@ impl CurveSwapEncoder { } impl SwapEncoder for CurveSwapEncoder { - fn new(executor_address: String, chain: Chain) -> Result { - let native_token_curve_address = - Address::from_str("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE").map_err(|_| { - EncodingError::FatalError("Invalid Curve native token address".to_string()) - })?; + fn new( + executor_address: String, + chain: Chain, + config: Option>, + ) -> Result { + let config = config.ok_or(EncodingError::FatalError( + "Missing curve specific addresses in config".to_string(), + ))?; + let native_token_curve_address = config + .get("native_token_address") + .ok_or(EncodingError::FatalError( + "Missing native token curve address in config".to_string(), + ))? + .to_string(); + let meta_registry_address = config + .get("meta_registry_address") + .ok_or(EncodingError::FatalError( + "Missing meta registry address in config".to_string(), + ))? + .to_string(); Ok(Self { executor_address, - meta_registry_address: "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC".to_string(), + meta_registry_address, native_token_address: chain.native_token()?, native_token_curve_address, }) } + fn encode_swap( &self, swap: Swap, encoding_context: EncodingContext, ) -> Result, EncodingError> { let token_approvals_manager = ProtocolApprovalsManager::new()?; + let native_token_curve_address = Address::from_str(&self.native_token_curve_address) + .map_err(|_| { + EncodingError::FatalError("Invalid Curve native token curve address".to_string()) + })?; let token_in = if swap.token_in == self.native_token_address { - self.native_token_curve_address + native_token_curve_address } else { bytes_to_address(&swap.token_in)? }; let token_out = if swap.token_out == self.native_token_address { - self.native_token_curve_address + native_token_curve_address } else { bytes_to_address(&swap.token_out)? }; @@ -471,7 +518,7 @@ impl SwapEncoder for CurveSwapEncoder { let component_address = Address::from_str(&swap.component.id) .map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?; if let Some(router_address) = encoding_context.router_address { - if token_in != self.native_token_curve_address { + if token_in != native_token_curve_address { let tycho_router_address = bytes_to_address(&router_address)?; approval_needed = token_approvals_manager.approval_needed( token_in, @@ -555,6 +602,7 @@ mod tests { let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), TychoCoreChain::Ethereum.into(), + None, ) .unwrap(); let encoded_swap = encoder @@ -605,6 +653,7 @@ mod tests { let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), TychoCoreChain::Ethereum.into(), + None, ) .unwrap(); let encoded_swap = encoder @@ -656,6 +705,10 @@ mod tests { let encoder = BalancerV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), TychoCoreChain::Ethereum.into(), + Some(HashMap::from([( + "vault_address".to_string(), + "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), + )])), ) .unwrap(); let encoded_swap = encoder @@ -718,6 +771,7 @@ mod tests { let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), TychoCoreChain::Ethereum.into(), + None, ) .unwrap(); let encoded_swap = encoder @@ -786,6 +840,7 @@ mod tests { let encoder = UniswapV4SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), TychoCoreChain::Ethereum.into(), + None, ) .unwrap(); let encoded_swap = encoder @@ -877,6 +932,7 @@ mod tests { let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), TychoCoreChain::Ethereum.into(), + None, ) .unwrap(); let initial_encoded_swap = encoder @@ -954,7 +1010,8 @@ mod tests { }; let encoder = - EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap(); + EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into(), None) + .unwrap(); let encoded_swap = encoder .encode_swap(swap, encoding_context) @@ -983,7 +1040,8 @@ mod tests { let intermediary_token = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC let encoder = - EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap(); + EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into(), None) + .unwrap(); let encoding_context = EncodingContext { receiver: RECEIVER.into(), @@ -1062,6 +1120,19 @@ mod tests { use super::*; + fn curve_config() -> Option> { + Some(HashMap::from([ + ( + "native_token_address".to_string(), + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE".to_string(), + ), + ( + "meta_registry_address".to_string(), + "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC".to_string(), + ), + ])) + } + #[rstest] #[case( "0x5500307Bcf134E5851FB4D7D8D1Dc556dCdB84B4", @@ -1105,8 +1176,12 @@ mod tests { #[case] expected_i: u64, #[case] expected_j: u64, ) { - let encoder = - CurveSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap(); + let encoder = CurveSwapEncoder::new( + String::default(), + TychoCoreChain::Ethereum.into(), + curve_config(), + ) + .unwrap(); let (i, j) = encoder .get_coin_indexes( Address::from_str(pool).unwrap(), @@ -1117,178 +1192,192 @@ mod tests { assert_eq!(i, U8::from(expected_i)); assert_eq!(j, U8::from(expected_j)); } - } - #[test] - fn test_curve_encode_tripool() { - let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("factory".into(), Bytes::from(vec![])); - let curve_tri_pool = ProtocolComponent { - id: String::from("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"), - protocol_system: String::from("vm:curve"), - static_attributes, - ..Default::default() - }; - let token_in = Bytes::from("0x6B175474E89094C44Da98b954EedeAC495271d0F"); - let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); - let swap = Swap { - component: curve_tri_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - // The receiver was generated with `makeAddr("bob") using forge` - receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), - exact_out: false, - router_address: None, - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - }; - let encoder = CurveSwapEncoder::new( - String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), - TychoCoreChain::Ethereum.into(), - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + #[test] + fn test_curve_encode_tripool() { + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("factory".into(), Bytes::from(vec![])); + let curve_tri_pool = ProtocolComponent { + id: String::from("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"), + protocol_system: String::from("vm:curve"), + static_attributes, + ..Default::default() + }; + let token_in = Bytes::from("0x6B175474E89094C44Da98b954EedeAC495271d0F"); + let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); + let swap = Swap { + component: curve_tri_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + // The receiver was generated with `makeAddr("bob") using forge` + receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), + exact_out: false, + router_address: None, + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + }; + let encoder = CurveSwapEncoder::new( + String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), + TychoCoreChain::Ethereum.into(), + curve_config(), + ) .unwrap(); - let hex_swap = encode(&encoded_swap); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // token in - "6b175474e89094c44da98b954eedeac495271d0f", - // token out - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - // pool address - "bebc44782c7db0a1a60cb6fe97d0b483032ff1c7", - // pool type 1 - "01", - // i index - "00", - // j index - "01", - // approval needed - "01", - )) - ); - } + assert_eq!( + hex_swap, + String::from(concat!( + // token in + "6b175474e89094c44da98b954eedeac495271d0f", + // token out + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // pool address + "bebc44782c7db0a1a60cb6fe97d0b483032ff1c7", + // pool type 1 + "01", + // i index + "00", + // j index + "01", + // approval needed + "01", + )) + ); + } - #[test] - fn test_curve_encode_factory() { - let mut static_attributes: HashMap = HashMap::new(); - static_attributes - .insert("factory".into(), Bytes::from("0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf")); - let curve_pool = ProtocolComponent { - id: String::from("0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72"), - protocol_system: String::from("vm:curve"), - static_attributes, - ..Default::default() - }; - let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); - let token_out = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); - let swap = Swap { - component: curve_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - // The receiver was generated with `makeAddr("bob") using forge` - receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), - exact_out: false, - router_address: None, - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - }; - let encoder = CurveSwapEncoder::new( - String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), - TychoCoreChain::Ethereum.into(), - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + #[test] + fn test_curve_encode_factory() { + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert( + "factory".into(), + Bytes::from("0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf"), + ); + let curve_pool = ProtocolComponent { + id: String::from("0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72"), + protocol_system: String::from("vm:curve"), + static_attributes, + ..Default::default() + }; + let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); + let token_out = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); + let swap = Swap { + component: curve_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + // The receiver was generated with `makeAddr("bob") using forge` + receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), + exact_out: false, + router_address: None, + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + }; + let encoder = CurveSwapEncoder::new( + String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), + TychoCoreChain::Ethereum.into(), + curve_config(), + ) .unwrap(); - let hex_swap = encode(&encoded_swap); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // token in - "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - // token out - "4c9edd5852cd905f086c759e8383e09bff1e68b3", - // pool address - "02950460e2b9529d0e00284a5fa2d7bdf3fa4d72", - // pool type 1 - "01", - // i index - "01", - // j index - "00", - // approval needed - "01", - )) - ); - } - #[test] - fn test_curve_encode_st_eth() { - // This test is for the stETH pool, which is a special case in Curve - // where the token in is ETH but not as the zero address. - let mut static_attributes: HashMap = HashMap::new(); - static_attributes.insert("factory".into(), Bytes::from(vec![])); - let curve_pool = ProtocolComponent { - id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), - protocol_system: String::from("vm:curve"), - static_attributes, - ..Default::default() - }; - let token_in = Bytes::from("0x0000000000000000000000000000000000000000"); - let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); - let swap = Swap { - component: curve_pool, - token_in: token_in.clone(), - token_out: token_out.clone(), - split: 0f64, - }; - let encoding_context = EncodingContext { - // The receiver was generated with `makeAddr("bob") using forge` - receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), - exact_out: false, - router_address: None, - group_token_in: token_in.clone(), - group_token_out: token_out.clone(), - }; - let encoder = CurveSwapEncoder::new( - String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), - TychoCoreChain::Ethereum.into(), - ) - .unwrap(); - let encoded_swap = encoder - .encode_swap(swap, encoding_context) + assert_eq!( + hex_swap, + String::from(concat!( + // token in + "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + // token out + "4c9edd5852cd905f086c759e8383e09bff1e68b3", + // pool address + "02950460e2b9529d0e00284a5fa2d7bdf3fa4d72", + // pool type 1 + "01", + // i index + "01", + // j index + "00", + // approval needed + "01", + )) + ); + } + #[test] + fn test_curve_encode_st_eth() { + // This test is for the stETH pool, which is a special case in Curve + // where the token in is ETH but not as the zero address. + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("factory".into(), Bytes::from(vec![])); + let curve_pool = ProtocolComponent { + id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), + protocol_system: String::from("vm:curve"), + static_attributes, + ..Default::default() + }; + let token_in = Bytes::from("0x0000000000000000000000000000000000000000"); + let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); + let swap = Swap { + component: curve_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + let encoding_context = EncodingContext { + // The receiver was generated with `makeAddr("bob") using forge` + receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), + exact_out: false, + router_address: None, + group_token_in: token_in.clone(), + group_token_out: token_out.clone(), + }; + let encoder = CurveSwapEncoder::new( + String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"), + TychoCoreChain::Ethereum.into(), + Some(HashMap::from([ + ( + "native_token_address".to_string(), + "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE".to_string(), + ), + ( + "meta_registry_address".to_string(), + "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC".to_string(), + ), + ])), + ) .unwrap(); - let hex_swap = encode(&encoded_swap); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); - assert_eq!( - hex_swap, - String::from(concat!( - // token in - "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - // token out - "ae7ab96520de3a18e5e111b5eaab095312d7fe84", - // pool address - "dc24316b9ae028f1497c275eb9192a3ea0f67022", - // pool type 1 - "01", - // i index - "00", - // j index - "01", - // approval needed - "01", - )) - ); + assert_eq!( + hex_swap, + String::from(concat!( + // token in + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + // token out + "ae7ab96520de3a18e5e111b5eaab095312d7fe84", + // pool address + "dc24316b9ae028f1497c275eb9192a3ea0f67022", + // pool type 1 + "01", + // i index + "00", + // j index + "01", + // approval needed + "01", + )) + ); + } } } diff --git a/src/encoding/swap_encoder.rs b/src/encoding/swap_encoder.rs index 3051523..9d23aad 100644 --- a/src/encoding/swap_encoder.rs +++ b/src/encoding/swap_encoder.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::encoding::{ errors::EncodingError, models::{Chain, EncodingContext, Swap}, @@ -11,7 +13,13 @@ pub trait SwapEncoder: Sync + Send { /// # Arguments /// * `executor_address` - The address of the contract that will execute the swap /// * `chain` - The chain on which the swap will be executed - fn new(executor_address: String, chain: Chain) -> Result + /// * `config` - Additional configuration parameters for the encoder, like vault or registry + /// address + fn new( + executor_address: String, + chain: Chain, + config: Option>, + ) -> Result where Self: Sized;