diff --git a/Cargo.lock b/Cargo.lock index 2315882..378aa43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2941,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.96", @@ -3011,7 +3011,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index cdc0c89..9c7066f 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -80,7 +80,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { assertGe(amountOut, 0); } - function testSwap() public { + function testSwapUniswapV2() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; bool zeroForOne = false; @@ -93,4 +93,31 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants { uint256 finalBalance = DAI.balanceOf(BOB); assertGe(finalBalance, amountOut); } + + function testSwapExecutorEncoderData() public { + // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode + bytes memory protocolData = + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100"; + + (IERC20 tokenIn, address target, address receiver, bool zeroForOne) = + uniswapV2Exposed.decodeParams(protocolData); + + assertEq(address(tokenIn), WETH_ADDR); + assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); + assertEq(receiver, 0x0000000000000000000000000000000000000001); + assertEq(zeroForOne, false); + } + + function testSwapExecutorSwap() public { + // Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode + bytes memory protocolData = + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00"; + uint256 amountIn = 10 ** 18; + uint256 amountOut = 1847751195973566072891; + deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); + uniswapV2Exposed.swap(amountIn, protocolData); + + uint256 finalBalance = DAI.balanceOf(BOB); + assertGe(finalBalance, amountOut); + } } diff --git a/src/encoding/evm/router_encoder.rs b/src/encoding/evm/router_encoder.rs index 53ab437..14075c1 100644 --- a/src/encoding/evm/router_encoder.rs +++ b/src/encoding/evm/router_encoder.rs @@ -30,16 +30,17 @@ impl RouterEncoder for EVMRo &self, solutions: Vec, ) -> Result, EncodingError> { - let _approvals_calldata = self.handle_approvals(&solutions)?; // TODO: where should we append this? + let _approvals_calldata = self.handle_approvals(&solutions)?; let mut transactions: Vec = Vec::new(); for solution in solutions.iter() { let exact_out = solution.exact_out; - let straight_to_pool = solution.straight_to_pool; + let straight_to_pool = solution.direct_execution; let strategy = self .strategy_selector .select_strategy(solution); - let method_calldata = strategy.encode_strategy((*solution).clone())?; + let (method_calldata, target_address) = + strategy.encode_strategy((*solution).clone())?; let contract_interaction = if straight_to_pool { method_calldata @@ -52,7 +53,11 @@ impl RouterEncoder for EVMRo } else { BigUint::ZERO }; - transactions.push(Transaction { value, data: contract_interaction }); + transactions.push(Transaction { + value, + data: contract_interaction, + to: target_address, + }); } Ok(transactions) } diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index 7068608..09525a2 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -1,5 +1,8 @@ +use std::str::FromStr; + use alloy_primitives::Address; use alloy_sol_types::SolValue; +use tycho_core::Bytes; use crate::encoding::{ errors::EncodingError, @@ -27,7 +30,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder { pub struct SplitSwapStrategyEncoder {} impl EVMStrategyEncoder for SplitSwapStrategyEncoder {} impl StrategyEncoder for SplitSwapStrategyEncoder { - fn encode_strategy(&self, _solution: Solution) -> Result, EncodingError> { + fn encode_strategy(&self, _solution: Solution) -> Result<(Vec, Bytes), EncodingError> { todo!() } fn selector(&self, _exact_out: bool) -> &str { @@ -37,10 +40,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { /// 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 { - fn encode_strategy(&self, solution: Solution) -> Result, EncodingError> { +pub struct ExecutorStrategyEncoder {} +impl EVMStrategyEncoder for ExecutorStrategyEncoder {} +impl StrategyEncoder for ExecutorStrategyEncoder { + fn encode_strategy(&self, solution: Solution) -> 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(), @@ -68,10 +71,85 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder { router_address, }; 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)) } fn selector(&self, _exact_out: bool) -> &str { - unimplemented!(); + "swap(uint256, bytes)" + } +} + +#[cfg(test)] +mod tests { + use alloy::hex::encode; + 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) + .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_selector() { + let encoder = ExecutorStrategyEncoder {}; + assert_eq!(encoder.selector(false), "swap(uint256, bytes)"); } } diff --git a/src/encoding/evm/strategy_encoder/selector.rs b/src/encoding/evm/strategy_encoder/selector.rs index 37e397c..3ac619d 100644 --- a/src/encoding/evm/strategy_encoder/selector.rs +++ b/src/encoding/evm/strategy_encoder/selector.rs @@ -1,5 +1,5 @@ use crate::encoding::{ - evm::strategy_encoder::encoder::{SplitSwapStrategyEncoder, StraightToPoolStrategyEncoder}, + evm::strategy_encoder::encoder::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder}, models::Solution, strategy_encoder::{StrategyEncoder, StrategySelector}, }; @@ -8,8 +8,8 @@ pub struct EVMStrategySelector; impl StrategySelector for EVMStrategySelector { fn select_strategy(&self, solution: &Solution) -> Box { - if solution.straight_to_pool { - Box::new(StraightToPoolStrategyEncoder {}) + if solution.direct_execution { + Box::new(ExecutorStrategyEncoder {}) } else { Box::new(SplitSwapStrategyEncoder {}) } diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index f4fbf24..841089d 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -46,7 +46,6 @@ impl SwapEncoder for UniswapV2SwapEncoder { component_id, bytes_to_address(&encoding_context.receiver)?, zero_to_one, - encoding_context.exact_out, ); Ok(args.abi_encode_packed()) @@ -211,8 +210,6 @@ mod tests { "0000000000000000000000000000000000000001", // zero for one "00", - // exact out - "00", )) ); } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 4e2cbbd..a0daeb0 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -11,7 +11,7 @@ pub struct Solution { /// Amount of the given token. pub given_amount: BigUint, /// The token being bought (exact in) or sold (exact out). - checked_token: Bytes, + pub checked_token: Bytes, /// Expected amount of the bought token (exact in) or sold token (exact out). pub expected_amount: BigUint, /// Minimum amount to be checked for the solution to be valid. @@ -23,10 +23,10 @@ pub struct Solution { pub receiver: Bytes, /// List of swaps to fulfill the solution. pub swaps: Vec, - /// If set to true, the solution will be encoded to be sent directly to the SwapExecutor and + /// If set to true, the solution will be encoded to be sent directly to the Executor and /// skip the router. The user is responsible for managing necessary approvals and token /// transfers. - pub straight_to_pool: bool, + pub direct_execution: bool, // if not set, then the Propeller Router will be used pub router_address: Option, // if set, it will be applied to check_amount @@ -60,6 +60,8 @@ pub struct Transaction { pub data: Vec, // ETH value to be sent with the transaction. pub value: BigUint, + // Address of the contract to call with the calldata + pub to: Bytes, } #[allow(dead_code)] diff --git a/src/encoding/strategy_encoder.rs b/src/encoding/strategy_encoder.rs index 2b4e348..fd2e757 100644 --- a/src/encoding/strategy_encoder.rs +++ b/src/encoding/strategy_encoder.rs @@ -1,8 +1,10 @@ +use tycho_core::Bytes; + use crate::encoding::{errors::EncodingError, models::Solution}; #[allow(dead_code)] pub trait StrategyEncoder { - fn encode_strategy(&self, to_encode: Solution) -> Result, EncodingError>; + fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec, Bytes), EncodingError>; fn selector(&self, exact_out: bool) -> &str; }