diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index f28a768..2d776f4 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -1,9 +1,9 @@ -use std::cmp::min; +use std::{cmp::min, str::FromStr}; -use alloy_primitives::{aliases::U24, map::HashSet, U256, U8}; +use alloy_primitives::{aliases::U24, map::HashSet, FixedBytes, U256, U8}; use alloy_sol_types::SolValue; use num_bigint::BigUint; -use tycho_core::{models::Chain, Bytes}; +use tycho_core::{keccak256, models::Chain, Bytes}; use crate::encoding::{ errors::EncodingError, @@ -25,15 +25,23 @@ pub trait EVMStrategyEncoder: StrategyEncoder { token_in: U8, token_out: U8, split: U24, + executor_address: Bytes, + executor_selector: FixedBytes<4>, protocol_data: Vec, ) -> Vec { let mut encoded = Vec::new(); encoded.push(token_in.to_be_bytes_vec()[0]); encoded.push(token_out.to_be_bytes_vec()[0]); encoded.extend_from_slice(&split.to_be_bytes_vec()); + encoded.extend(executor_address.to_vec()); + encoded.extend(executor_selector); encoded.extend(protocol_data); encoded } + fn encode_executor_selector(&self, selector: &str) -> FixedBytes<4> { + let hash = keccak256(selector.as_bytes()); + FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]]) + } } pub struct SplitSwapStrategyEncoder { @@ -53,7 +61,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { &self, solution: Solution, router_address: Bytes, - ) -> Result, EncodingError> { + ) -> Result<(Vec, Bytes), EncodingError> { let (permit, signature) = self.permit2.get_permit( &router_address, &solution.sender, @@ -122,6 +130,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { })?, ), percentage_to_uint24(swap.split), + Bytes::from_str(swap_encoder.executor_address()).map_err(|_| { + EncodingError::FatalError("Invalid executor address".to_string()) + })?, + self.encode_executor_selector(swap_encoder.executor_selector()), protocol_data, ); swaps.push(swap_data); @@ -151,20 +163,20 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { .abi_encode(); let contract_interaction = encode_input(&self.selector, method_calldata); - Ok(contract_interaction) + Ok((contract_interaction, router_address)) } } /// This strategy encoder is used for solutions that are sent directly to the pool. /// Only 1 solution with 1 swap is supported. -pub struct StraightToPoolStrategyEncoder {} -impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {} -impl StrategyEncoder for StraightToPoolStrategyEncoder { +pub struct ExecutorStrategyEncoder {} +impl EVMStrategyEncoder for ExecutorStrategyEncoder {} +impl StrategyEncoder for ExecutorStrategyEncoder { fn encode_strategy( &self, solution: Solution, _router_address: Bytes, - ) -> Result, EncodingError> { + ) -> Result<(Vec, Bytes), EncodingError> { if solution.router_address.is_none() { return Err(EncodingError::InvalidInput( "Router address is required for straight to pool solutions".to_string(), @@ -193,8 +205,9 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder { }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; - // TODO: here we need to pass also the address of the executor to be used - Ok(protocol_data) + let executor_address = Bytes::from_str(swap_encoder.executor_address()) + .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; + Ok((protocol_data, executor_address)) } } @@ -203,11 +216,70 @@ mod tests { use std::str::FromStr; use alloy::hex::encode; - use tycho_core::dto::ProtocolComponent; + use num_bigint::BigUint; + use tycho_core::{dto::ProtocolComponent, Bytes}; use super::*; use crate::encoding::models::Swap; + #[test] + fn test_executor_strategy_encode() { + let encoder = ExecutorStrategyEncoder {}; + + let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); + let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"); + + let swap = Swap { + component: ProtocolComponent { + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + protocol_system: "uniswap_v2".to_string(), + ..Default::default() + }, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + + let solution = Solution { + exact_out: false, + given_token: token_in, + given_amount: BigUint::from(1000000000000000000u64), + expected_amount: BigUint::from(1000000000000000000u64), + checked_token: token_out, + check_amount: None, + sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(), + // The receiver was generated with `makeAddr("bob") using forge` + receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(), + swaps: vec![swap], + direct_execution: true, + router_address: Some(Bytes::zero(20)), + slippage: None, + native_action: None, + }; + + let (protocol_data, executor_address) = encoder + .encode_strategy(solution, Bytes::zero(20)) + .unwrap(); + let hex_protocol_data = encode(&protocol_data); + assert_eq!( + executor_address, + Bytes::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap() + ); + assert_eq!( + hex_protocol_data, + String::from(concat!( + // in token + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // component id + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", + // receiver + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", + // zero for one + "00", + )) + ); + } + #[test] fn test_split_swap_strategy_encoder() { // Set up a mock private key for signing @@ -243,7 +315,7 @@ mod tests { }; let router_address = Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(); - let calldata = encoder + let (calldata, _) = encoder .encode_strategy(solution, router_address) .unwrap(); @@ -279,9 +351,9 @@ mod tests { let expected_swaps = String::from(concat!( // length of ple encoded swaps without padding - "000000000000000000000000000000000000000000000000000000000000005d", + "000000000000000000000000000000000000000000000000000000000000005c", // ple encoded swaps - "005b", + "005a", // Swap header "01", // token in index "00", // token out index diff --git a/src/encoding/evm/strategy_encoder/strategy_selector.rs b/src/encoding/evm/strategy_encoder/strategy_selector.rs index 2451711..b45cebf 100644 --- a/src/encoding/evm/strategy_encoder/strategy_selector.rs +++ b/src/encoding/evm/strategy_encoder/strategy_selector.rs @@ -2,9 +2,7 @@ use tycho_core::models::Chain; use crate::encoding::{ errors::EncodingError, - evm::strategy_encoder::strategy_encoders::{ - SplitSwapStrategyEncoder, StraightToPoolStrategyEncoder, - }, + evm::strategy_encoder::strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder}, models::Solution, strategy_encoder::{StrategyEncoder, StrategySelector}, }; @@ -18,8 +16,8 @@ impl StrategySelector for EVMStrategySelector { signer: Option, chain: Chain, ) -> Result, EncodingError> { - if solution.straight_to_pool { - Ok(Box::new(StraightToPoolStrategyEncoder {})) + if solution.direct_execution { + Ok(Box::new(ExecutorStrategyEncoder {})) } else { let signer_pk = signer.ok_or_else(|| { EncodingError::FatalError( diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 661b309..6c0a894 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -1,8 +1,7 @@ use std::str::FromStr; -use alloy_primitives::{Address, Bytes as AlloyBytes, FixedBytes}; +use alloy_primitives::{Address, Bytes as AlloyBytes}; use alloy_sol_types::SolValue; -use tycho_core::keccak256; use crate::encoding::{ errors::EncodingError, @@ -13,18 +12,11 @@ use crate::encoding::{ swap_encoder::SwapEncoder, }; -pub trait EVMSwapEncoder: SwapEncoder { - fn executor_selector(&self) -> FixedBytes<4> { - let hash = keccak256("swap(uint256,bytes)".as_bytes()); - FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]]) - } -} - pub struct UniswapV2SwapEncoder { executor_address: String, + executor_selector: String, } -impl EVMSwapEncoder for UniswapV2SwapEncoder {} impl UniswapV2SwapEncoder { fn get_zero_to_one(sell_token_address: Address, buy_token_address: Address) -> bool { sell_token_address < buy_token_address @@ -33,7 +25,7 @@ impl UniswapV2SwapEncoder { impl SwapEncoder for UniswapV2SwapEncoder { fn new(executor_address: String) -> Self { - Self { executor_address } + Self { executor_address, executor_selector: "swap(uint256,bytes)".to_string() } } fn encode_swap( @@ -51,9 +43,6 @@ impl SwapEncoder for UniswapV2SwapEncoder { // Token in address is always needed to perform a manual transfer from the router, // since no optimizations are performed that send from one pool to the next let args = ( - Address::from_str(self.executor_address()) - .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?, - self.executor_selector(), token_in_address, component_id, bytes_to_address(&encoding_context.receiver)?, @@ -66,12 +55,16 @@ impl SwapEncoder for UniswapV2SwapEncoder { fn executor_address(&self) -> &str { &self.executor_address } + + fn executor_selector(&self) -> &str { + &self.executor_selector + } } pub struct UniswapV3SwapEncoder { executor_address: String, + executor_selector: String, } -impl EVMSwapEncoder for UniswapV3SwapEncoder {} impl UniswapV3SwapEncoder { fn get_zero_to_one(sell_token_address: Address, buy_token_address: Address) -> bool { @@ -81,7 +74,7 @@ impl UniswapV3SwapEncoder { impl SwapEncoder for UniswapV3SwapEncoder { fn new(executor_address: String) -> Self { - Self { executor_address } + Self { executor_address, executor_selector: "swap(uint256,bytes)".to_string() } } fn encode_swap( @@ -119,9 +112,6 @@ impl SwapEncoder for UniswapV3SwapEncoder { })?; let args = ( - Address::from_str(self.executor_address()) - .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?, - self.executor_selector(), token_in_address, token_out_address, pool_fee_u24, @@ -136,19 +126,22 @@ impl SwapEncoder for UniswapV3SwapEncoder { fn executor_address(&self) -> &str { &self.executor_address } + fn executor_selector(&self) -> &str { + &self.executor_selector + } } pub struct BalancerV2SwapEncoder { executor_address: String, + executor_selector: String, vault_address: String, } -impl EVMSwapEncoder for BalancerV2SwapEncoder {} - impl SwapEncoder for BalancerV2SwapEncoder { fn new(executor_address: String) -> Self { Self { executor_address, + executor_selector: "swap(uint256,bytes)".to_string(), vault_address: "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), } } @@ -171,9 +164,6 @@ impl SwapEncoder for BalancerV2SwapEncoder { .map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?; let args = ( - Address::from_str(self.executor_address()) - .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?, - self.executor_selector(), bytes_to_address(&swap.token_in)?, bytes_to_address(&swap.token_out)?, component_id, @@ -187,6 +177,9 @@ impl SwapEncoder for BalancerV2SwapEncoder { fn executor_address(&self) -> &str { &self.executor_address } + fn executor_selector(&self) -> &str { + &self.executor_selector + } } #[cfg(test)] @@ -224,10 +217,6 @@ mod tests { assert_eq!( hex_swap, String::from(concat!( - // executor address - "543778987b293c7e8cf0722bb2e935ba6f4068d4", - // executor selector - "bd0625ab", // in token "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // component id @@ -270,10 +259,6 @@ mod tests { assert_eq!( hex_swap, String::from(concat!( - // executor address - "543778987b293c7e8cf0722bb2e935ba6f4068d4", - // executor selector - "bd0625ab", // in token "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // out token @@ -318,10 +303,6 @@ mod tests { assert_eq!( hex_swap, String::from(concat!( - // executor address - "543778987b293c7e8cf0722bb2e935ba6f4068d4", - // executor selector - "bd0625ab", // token in "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index dcdcac0..99dfb36 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -4,8 +4,11 @@ use crate::encoding::{errors::EncodingError, models::Solution}; #[allow(dead_code)] pub trait StrategyEncoder { - fn encode_strategy(&self, to_encode: Solution,router_address: Bytes,) -> Result<(Vec, Bytes), EncodingError>; - fn selector(&self, exact_out: bool) -> &str; + fn encode_strategy( + &self, + to_encode: Solution, + router_address: Bytes, + ) -> Result<(Vec, Bytes), EncodingError>; } pub trait StrategySelector { diff --git a/src/encoding/swap_encoder.rs b/src/encoding/swap_encoder.rs index 1a1fc0c..14f49f2 100644 --- a/src/encoding/swap_encoder.rs +++ b/src/encoding/swap_encoder.rs @@ -14,4 +14,5 @@ pub trait SwapEncoder: Sync + Send { encoding_context: EncodingContext, ) -> Result, EncodingError>; fn executor_address(&self) -> &str; + fn executor_selector(&self) -> &str; }