From 1bfe656e6b1279482116be5ffddd81f83c0c381e Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 19 Feb 2025 08:39:21 +0530 Subject: [PATCH] feat: add new attributes in encoding context, update usv4 swap encoder and tests --- .../test/executors/UniswapV4Executor.t.sol | 2 +- .../evm/strategy_encoder/strategy_encoders.rs | 8 ++ .../evm/swap_encoder/swap_encoders.rs | 102 ++++++++++++++++-- src/encoding/models.rs | 6 ++ 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 61a10ce..b1c8169 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -135,7 +135,7 @@ contract UniswapV4ExecutorTest is Test, Constants { function testSingleSwapIntegration() public { // USDE -> USDT - // Generated by the Tycho swap encoder - test_encode_uniswap_v4 + // Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap bytes memory protocolData = hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000000000001015615deb798bb3e4dfa0139dfa1b3d433cc23b72f91dd7346dac17f958d2ee523a2206206994597c13d831ec7000064000001"; diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index efb79e4..de59a54 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -2,6 +2,8 @@ use std::{collections::HashSet, str::FromStr}; use alloy_primitives::{aliases::U24, FixedBytes, U256, U8}; use alloy_sol_types::SolValue; +use num_bigint::BigUint; +use num_traits::Zero; use tycho_core::{keccak256, Bytes}; use crate::encoding::{ @@ -257,6 +259,9 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { receiver: solution.router_address.clone(), exact_out: solution.exact_out, router_address: solution.router_address.clone(), + group_token_in: Some(tokens.first().unwrap().clone()), + group_token_out: Some(tokens.last().unwrap().clone()), + amount_out_min: Some(BigUint::zero()), }; let mut grouped_protocol_data: Vec> = vec![]; for swap in grouped_swap.swaps.iter() { @@ -347,6 +352,9 @@ impl StrategyEncoder for ExecutorStrategyEncoder { receiver: solution.receiver, exact_out: solution.exact_out, router_address: solution.router_address, + group_token_in: Some(solution.given_token.clone()), + group_token_out: Some(solution.checked_token.clone()), + amount_out_min: Some(BigUint::zero()), }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?; diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 8754a11..d207804 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -202,10 +202,21 @@ impl SwapEncoder for UniswapV4SwapEncoder { swap: Swap, encoding_context: EncodingContext, ) -> Result, EncodingError> { + let mut first_swap = false; + if encoding_context.group_token_in == Some(swap.token_in.clone()) && + encoding_context.group_token_out == Some(swap.token_out.clone()) + { + first_swap = true; + } let token_in_address = bytes_to_address(&swap.token_in)?; let token_out_address = bytes_to_address(&swap.token_out)?; - let mut amount_out_min = [0u8; 32]; - amount_out_min[31] = 1; + let mut amount_out_min = vec![0u8; 32]; // Create a zero-filled buffer of 32 bytes + let min_value = encoding_context + .amount_out_min + .unwrap_or_default() + .to_bytes_be(); + // Copy the actual value to the end of the buffer, maintaining leading zeros + amount_out_min[(32 - min_value.len())..].copy_from_slice(&min_value); let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address); let callback_executor = bytes_to_address(&encoding_context.router_address)?; @@ -255,6 +266,14 @@ impl SwapEncoder for UniswapV4SwapEncoder { let pool_params = Self::encode_pool_params(token_out_address, pool_fee_u24, pool_tick_spacing_u24); + if !first_swap { + return Ok(Self::encode_pool_params( + token_in_address, + pool_fee_u24, + pool_tick_spacing_u24, + )); + } + let args = ( token_in_address, token_out_address, @@ -345,7 +364,7 @@ mod tests { use std::collections::HashMap; use alloy::hex::encode; - use num_bigint::BigInt; + use num_bigint::{BigInt, BigUint}; use tycho_core::{dto::ProtocolComponent, Bytes}; use super::*; @@ -366,6 +385,9 @@ mod tests { receiver: Bytes::from("0x0000000000000000000000000000000000000001"), exact_out: false, router_address: Bytes::zero(20), + group_token_in: None, + group_token_out: None, + amount_out_min: None, }; let encoder = UniswapV2SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); @@ -409,6 +431,9 @@ mod tests { receiver: Bytes::from("0x0000000000000000000000000000000000000001"), exact_out: false, router_address: Bytes::zero(20), + group_token_in: None, + group_token_out: None, + amount_out_min: None, }; let encoder = UniswapV3SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); @@ -453,6 +478,9 @@ mod tests { receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"), exact_out: false, router_address: Bytes::zero(20), + group_token_in: None, + group_token_out: None, + amount_out_min: None, }; let encoder = BalancerV2SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); @@ -480,11 +508,13 @@ mod tests { } #[test] - fn test_encode_uniswap_v4() { + fn test_encode_uniswap_v4_simple_swap() { let fee = BigInt::from(100); let tick_spacing = BigInt::from(1); let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); let encoded_tick_spacing = Bytes::from(tick_spacing.to_signed_bytes_be()); + let token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE + let token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT let mut static_attributes: HashMap = HashMap::new(); static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee.to_vec())); @@ -498,8 +528,8 @@ mod tests { }; let swap = Swap { component: usv4_pool, - token_in: Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"), // USDE - token_out: Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"), // USDT + token_in: token_in.clone(), + token_out: token_out.clone(), split: 0f64, }; let encoding_context = EncodingContext { @@ -509,6 +539,9 @@ mod tests { exact_out: false, // Same as the executor address router_address: Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f"), + group_token_in: Some(token_in), + group_token_out: Some(token_out), + amount_out_min: Some(BigUint::from(1u128)), }; let encoder = UniswapV4SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); @@ -542,4 +575,61 @@ mod tests { )) ); } + + #[test] + fn test_encode_uniswap_v4_grouped() { + let fee = BigInt::from(500); + let tick_spacing = BigInt::from(10); + let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); + let encoded_tick_spacing = Bytes::from(tick_spacing.to_signed_bytes_be()); + let token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE + let token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT + + let mut static_attributes: HashMap = HashMap::new(); + static_attributes.insert("fee".into(), Bytes::from(encoded_pool_fee.to_vec())); + static_attributes.insert("tickSpacing".into(), Bytes::from(encoded_tick_spacing.to_vec())); + + let usv4_pool = ProtocolComponent { + id: String::from("0x000000000004444c5dc75cB358380D2e3dE08A90"), + static_attributes, + ..Default::default() + }; + + let swap = Swap { + component: usv4_pool, + token_in: token_in.clone(), + token_out: token_out.clone(), + split: 0f64, + }; + + let encoding_context = EncodingContext { + receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + exact_out: false, + router_address: Bytes::zero(20), + // Different from token_in and token_out + group_token_in: Some(Bytes::zero(20)), + group_token_out: Some(Bytes::zero(20)), + amount_out_min: Some(BigUint::from(1u128)), + }; + + let encoder = + UniswapV4SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); + let encoded_swap = encoder + .encode_swap(swap, encoding_context) + .unwrap(); + let hex_swap = encode(&encoded_swap); + + assert_eq!( + hex_swap, + String::from(concat!( + // pool params: + // - intermediary token (20 bytes) + "4c9edd5852cd905f086c759e8383e09bff1e68b3", + // - fee (3 bytes) + "0001f4", + // - tick spacing (3 bytes) + "00000a" + )) + ); + } } diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 78a4a3f..543b77a 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -111,11 +111,17 @@ pub struct Transaction { /// * `receiver`: Address of the receiver of the out token after the swaps are completed. /// * `exact_out`: true if the solution is a buy order, false if it is a sell order. /// * `router_address`: Address of the router contract to be used for the swaps. +/// * `group_token_in`: Token to be used as the input for the group swap. +/// * `group_token_out`: Token to be used as the output for the group swap. +/// * `amount_out_min`: Minimum amount of the output token to be received. #[derive(Clone, Debug)] pub struct EncodingContext { pub receiver: Bytes, pub exact_out: bool, pub router_address: Bytes, + pub group_token_in: Option, + pub group_token_out: Option, + pub amount_out_min: Option, } #[derive(Clone, PartialEq, Eq, Hash)]