From 7b4bf0205d52354ffde4a88bd344a6df7d92cca5 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 21 Jan 2025 14:45:42 -0500 Subject: [PATCH 1/3] feat: UniswapV2 Swap Encoder --- src/encoding/evm/swap_encoder/encoders.rs | 87 +++++++++++++++++++++-- src/encoding/models.rs | 1 + 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index da1b3aa..8eecc5f 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -1,7 +1,9 @@ use std::str::FromStr; +use alloy::hex::decode; use alloy_primitives::Address; use alloy_sol_types::SolValue; +use tycho_core::Bytes; use crate::encoding::{ errors::EncodingError, @@ -16,17 +18,51 @@ pub struct UniswapV2SwapEncoder { executor_address: String, } -impl UniswapV2SwapEncoder {} +impl UniswapV2SwapEncoder { + fn get_zero_to_one(sell_token_address: Address, buy_token_address: Address) -> bool { + sell_token_address < buy_token_address + } +} + impl SwapEncoder for UniswapV2SwapEncoder { fn new(executor_address: String) -> Self { Self { executor_address } } + fn encode_swap( &self, - _swap: Swap, - _encoding_context: EncodingContext, + swap: Swap, + encoding_context: EncodingContext, ) -> Result, EncodingError> { - todo!() + let token_in_address = bytes_to_address(&swap.token_in)?; + let token_out_address = bytes_to_address(&swap.token_out)?; + + let zero_for_one = Self::get_zero_to_one(token_in_address, token_out_address); + let protocol_id = Bytes::from( + decode( + swap.component + .id + .trim_start_matches("0x"), + ) + .map_err(|_| { + EncodingError::FatalError(format!( + "Failed to parse component id: {}", + swap.component.id + )) + })?, + ); + + // Sell token address is always needed to perform manual transfer from router into the pool, + // since no optimizations are performed that send from one pool to the next + let args = ( + token_in_address, + bytes_to_address(&protocol_id)?, + bytes_to_address(&encoding_context.receiver)?, + zero_for_one, + encoding_context.exact_out, + ); + + Ok(args.abi_encode_packed()) } fn executor_address(&self) -> &str { @@ -77,8 +113,47 @@ impl SwapEncoder for BalancerV2SwapEncoder { #[cfg(test)] mod tests { + use alloy::hex::encode; + use tycho_core::{dto::ProtocolComponent, Bytes}; + + use super::*; + #[tokio::test] - async fn test_encode_swap() { - // Dummy test to make CI pass. Please implement me. + async fn test_encode_uniswap_v2() { + let usv2_pool = ProtocolComponent { + id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), + ..Default::default() + }; + let swap = Swap { + component: usv2_pool, + token_in: Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), + token_out: Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f"), + split: 0f64, + }; + let encoding_context = EncodingContext { + receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + exact_out: false, + address_for_approvals: Bytes::zero(20), + }; + let encoder = super::UniswapV2SwapEncoder::new(String::from("0x")); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + assert_eq!( + hex_swap, + String::from(concat!( + // in token + "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + // component id + "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + // receiver + "0000000000000000000000000000000000000001", + // zero for one + "00", + // exact out + "00", + )) + ); } } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 0b81dbe..cda2eee 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -76,5 +76,6 @@ pub struct Transaction { pub struct EncodingContext { pub receiver: Bytes, pub exact_out: bool, + // TODO I don't like this - what does this mean? pub address_for_approvals: Bytes, } From cc979880e2d790fe6ddccd9378c4745a351e4dde Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Tue, 21 Jan 2025 16:31:18 -0500 Subject: [PATCH 2/3] chore: Rename address_for_approvals -> router_address When will this not be the router address that we use for approvals? It will always be the router address. address_for_approvals sounds to me very vague (which approvals?). --- src/encoding/evm/strategy_encoder/encoder.rs | 2 +- src/encoding/evm/swap_encoder/encoders.rs | 4 ++-- src/encoding/models.rs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/encoding/evm/strategy_encoder/encoder.rs b/src/encoding/evm/strategy_encoder/encoder.rs index e1660f6..7068608 100644 --- a/src/encoding/evm/strategy_encoder/encoder.rs +++ b/src/encoding/evm/strategy_encoder/encoder.rs @@ -65,7 +65,7 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder { let encoding_context = EncodingContext { receiver: solution.receiver, exact_out: solution.exact_out, - address_for_approvals: router_address, + 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 diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index 8eecc5f..14421e7 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -90,7 +90,7 @@ impl SwapEncoder for BalancerV2SwapEncoder { ) -> Result, EncodingError> { let token_approvals_manager = ProtocolApprovalsManager::new(); let token = bytes_to_address(&swap.token_in)?; - let router_address = bytes_to_address(&encoding_context.address_for_approvals)?; + let router_address = bytes_to_address(&encoding_context.router_address)?; let approval_needed = token_approvals_manager.approval_needed(token, router_address, self.vault_address)?; // should we return gas estimation here too?? if there is an approval needed, gas will be @@ -133,7 +133,7 @@ mod tests { let encoding_context = EncodingContext { receiver: Bytes::from("0x0000000000000000000000000000000000000001"), exact_out: false, - address_for_approvals: Bytes::zero(20), + router_address: Bytes::zero(20), }; let encoder = super::UniswapV2SwapEncoder::new(String::from("0x")); let encoded_swap = encoder diff --git a/src/encoding/models.rs b/src/encoding/models.rs index cda2eee..c5f0907 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -76,6 +76,5 @@ pub struct Transaction { pub struct EncodingContext { pub receiver: Bytes, pub exact_out: bool, - // TODO I don't like this - what does this mean? - pub address_for_approvals: Bytes, + pub router_address: Bytes, } From 748a3f06c35fc22ca80727a98deb6a7118598f22 Mon Sep 17 00:00:00 2001 From: TAMARA LIPOWSKI Date: Wed, 22 Jan 2025 09:33:08 -0500 Subject: [PATCH 3/3] chore: Renamings for clarity --- src/encoding/evm/swap_encoder/encoders.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/encoding/evm/swap_encoder/encoders.rs b/src/encoding/evm/swap_encoder/encoders.rs index 14421e7..fd9d376 100644 --- a/src/encoding/evm/swap_encoder/encoders.rs +++ b/src/encoding/evm/swap_encoder/encoders.rs @@ -38,7 +38,7 @@ impl SwapEncoder for UniswapV2SwapEncoder { let token_out_address = bytes_to_address(&swap.token_out)?; let zero_for_one = Self::get_zero_to_one(token_in_address, token_out_address); - let protocol_id = Bytes::from( + let component_id = Bytes::from( decode( swap.component .id @@ -46,17 +46,17 @@ impl SwapEncoder for UniswapV2SwapEncoder { ) .map_err(|_| { EncodingError::FatalError(format!( - "Failed to parse component id: {}", + "Failed to parse component id for Uniswap v2: {}", swap.component.id )) })?, ); - // Sell token address is always needed to perform manual transfer from router into the pool, + // 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 = ( token_in_address, - bytes_to_address(&protocol_id)?, + bytes_to_address(&component_id)?, bytes_to_address(&encoding_context.receiver)?, zero_for_one, encoding_context.exact_out, @@ -135,7 +135,7 @@ mod tests { exact_out: false, router_address: Bytes::zero(20), }; - let encoder = super::UniswapV2SwapEncoder::new(String::from("0x")); + let encoder = UniswapV2SwapEncoder::new(String::from("0x")); let encoded_swap = encoder .encode_swap(swap, encoding_context) .unwrap();