feat: early return in usv4 swap encoder for second swap, add utils

This commit is contained in:
royvardhan
2025-02-19 21:42:13 +05:30
parent 5e9b38876e
commit f7ddace559
3 changed files with 51 additions and 62 deletions

View File

@@ -197,7 +197,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
} }
function testMultipleSwapIntegration() public { function testMultipleSwapIntegration() public {
// USDE -> USDT -> WBTC -> // USDE -> USDT -> WBTC
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_combined // Generated by the Tycho swap encoder - test_encode_uniswap_v4_combined
bytes memory protocolData = bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000000000001015615deb798bb3e4dfa0139dfa1b3d433cc23b72f91dd7346dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c"; hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000000000001015615deb798bb3e4dfa0139dfa1b3d433cc23b72f91dd7346dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";

View File

@@ -7,7 +7,9 @@ use crate::encoding::{
errors::EncodingError, errors::EncodingError,
evm::{ evm::{
approvals::protocol_approvals_manager::ProtocolApprovalsManager, approvals::protocol_approvals_manager::ProtocolApprovalsManager,
utils::{bytes_to_address, encode_function_selector, pad_to_fixed_size}, utils::{
bytes_to_address, encode_function_selector, get_static_attribute, pad_to_fixed_size,
},
}, },
models::{EncodingContext, Swap}, models::{EncodingContext, Swap},
swap_encoder::SwapEncoder, swap_encoder::SwapEncoder,
@@ -105,16 +107,7 @@ impl SwapEncoder for UniswapV3SwapEncoder {
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address); let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address);
let component_id = Address::from_str(&swap.component.id) let component_id = Address::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?; .map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?;
let pool_fee_bytes = swap let pool_fee_bytes = get_static_attribute(&swap, "fee")?;
.component
.static_attributes
.get("fee")
.ok_or_else(|| {
EncodingError::FatalError(
"Pool fee not found in Uniswap v3 static attributes".to_string(),
)
})?
.to_vec();
let pool_fee_u24 = pad_to_fixed_size::<3>(&pool_fee_bytes) let pool_fee_u24 = pad_to_fixed_size::<3>(&pool_fee_bytes)
.map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?; .map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?;
@@ -183,10 +176,27 @@ impl SwapEncoder for UniswapV4SwapEncoder {
swap: Swap, swap: Swap,
encoding_context: EncodingContext, encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError> { ) -> Result<Vec<u8>, EncodingError> {
let mut first_swap = false; let fee = get_static_attribute(&swap, "fee")?;
if encoding_context.group_token_in == Some(swap.token_in.clone()) {
first_swap = true; let pool_fee_u24 = pad_to_fixed_size::<3>(&fee)
.map_err(|_| EncodingError::FatalError("Failed to pad fee bytes".to_string()))?;
let tick_spacing = get_static_attribute(&swap, "tickSpacing")?;
let pool_tick_spacing_u24 = pad_to_fixed_size::<3>(&tick_spacing).map_err(|_| {
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
})?;
// Early check if this is not the first swap
if encoding_context.group_token_in != Some(swap.token_in.clone()) {
return Ok(Self::encode_pool_params(
bytes_to_address(&swap.token_out)?,
pool_fee_u24,
pool_tick_spacing_u24,
));
} }
// This is the first swap, compute all necessary values
let token_in_address = bytes_to_address(&swap.token_in)?; let token_in_address = bytes_to_address(&swap.token_in)?;
let token_out_address = bytes_to_address(&swap.token_out)?; let token_out_address = bytes_to_address(&swap.token_out)?;
let group_token_in = if let Some(group_token_in) = encoding_context.group_token_in { let group_token_in = if let Some(group_token_in) = encoding_context.group_token_in {
@@ -208,46 +218,9 @@ impl SwapEncoder for UniswapV4SwapEncoder {
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address); 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)?; let callback_executor = bytes_to_address(&encoding_context.router_address)?;
let fee = swap
.component
.static_attributes
.get("fee")
.ok_or_else(|| {
EncodingError::FatalError(
"Pool fee not found in Uniswap v4 static attributes".to_string(),
)
})?
.to_vec();
let pool_fee_u24 = pad_to_fixed_size::<3>(&fee)
.map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?;
let tick_spacing = swap
.component
.static_attributes
.get("tickSpacing")
.ok_or_else(|| {
EncodingError::FatalError(
"Pool tick spacing not found in Uniswap v4 static attributes".to_string(),
)
})?
.to_vec();
let pool_tick_spacing_u24 = pad_to_fixed_size::<3>(&tick_spacing).map_err(|_| {
EncodingError::FatalError("Failed to extract tick spacing bytes".to_string())
})?;
let pool_params = let pool_params =
Self::encode_pool_params(token_out_address, pool_fee_u24, pool_tick_spacing_u24); Self::encode_pool_params(token_out_address, pool_fee_u24, pool_tick_spacing_u24);
if !first_swap {
return Ok(Self::encode_pool_params(
token_out_address,
pool_fee_u24,
pool_tick_spacing_u24,
));
}
let args = ( let args = (
group_token_in, group_token_in,
group_token_out, group_token_out,
@@ -527,9 +500,9 @@ mod tests {
assert_eq!( assert_eq!(
hex_swap, hex_swap,
String::from(concat!( String::from(concat!(
// token in // group token in
"4c9edd5852cd905f086c759e8383e09bff1e68b3", "4c9edd5852cd905f086c759e8383e09bff1e68b3",
// token out // group token out
"dac17f958d2ee523a2206206994597c13d831ec7", "dac17f958d2ee523a2206206994597c13d831ec7",
// amount out min (0 as u128) // amount out min (0 as u128)
"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001",
@@ -540,11 +513,11 @@ mod tests {
// callback selector for "unlockCallback(bytes)" // callback selector for "unlockCallback(bytes)"
"91dd7346", "91dd7346",
// pool params: // pool params:
// - intermediary token (20 bytes) // - intermediary token
"dac17f958d2ee523a2206206994597c13d831ec7", "dac17f958d2ee523a2206206994597c13d831ec7",
// - fee (3 bytes) // - fee
"000064", "000064",
// - tick spacing (3 bytes) // - tick spacing
"000001" "000001"
)) ))
); );
@@ -556,6 +529,7 @@ mod tests {
let tick_spacing = BigInt::from(60); let tick_spacing = BigInt::from(60);
let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be()); let encoded_pool_fee = Bytes::from(fee.to_signed_bytes_be());
let encoded_tick_spacing = Bytes::from(tick_spacing.to_signed_bytes_be()); let encoded_tick_spacing = Bytes::from(tick_spacing.to_signed_bytes_be());
let group_token_in = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3"); // USDE
let token_in = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT let token_in = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT
let token_out = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC let token_out = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC
@@ -580,9 +554,9 @@ mod tests {
receiver: Bytes::from("0x0000000000000000000000000000000000000001"), receiver: Bytes::from("0x0000000000000000000000000000000000000001"),
exact_out: false, exact_out: false,
router_address: Bytes::zero(20), router_address: Bytes::zero(20),
// Different from token_in and token_out group_token_in: Some(group_token_in),
group_token_in: Some(Bytes::zero(20)), // Token out is the same as the group token out
group_token_out: Some(Bytes::zero(20)), group_token_out: Some(token_out),
amount_out_min: Some(BigUint::from(1u128)), amount_out_min: Some(BigUint::from(1u128)),
}; };

View File

@@ -4,7 +4,10 @@ use alloy_primitives::{aliases::U24, keccak256, Address, FixedBytes, Keccak256,
use num_bigint::BigUint; use num_bigint::BigUint;
use tycho_core::Bytes; use tycho_core::Bytes;
use crate::encoding::{errors::EncodingError, models::Solution}; use crate::encoding::{
errors::EncodingError,
models::{Solution, Swap},
};
/// Safely converts a `Bytes` object to an `Address` object. /// Safely converts a `Bytes` object to an `Address` object.
/// ///
@@ -92,7 +95,7 @@ pub fn get_token_position(tokens: Vec<Bytes>, token: Bytes) -> Result<U8, Encodi
Ok(position) Ok(position)
} }
/// Pads a byte slice to a fixed size array with leading zeros. /// Pads a byte slice to a fixed size array of N bytes.
pub fn pad_to_fixed_size<const N: usize>(input: &[u8]) -> Result<[u8; N], EncodingError> { pub fn pad_to_fixed_size<const N: usize>(input: &[u8]) -> Result<[u8; N], EncodingError> {
let mut padded = [0u8; N]; let mut padded = [0u8; N];
let start = N - input.len(); let start = N - input.len();
@@ -105,3 +108,15 @@ pub fn encode_function_selector(selector: &str) -> FixedBytes<4> {
let hash = keccak256(selector.as_bytes()); let hash = keccak256(selector.as_bytes());
FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]]) FixedBytes::<4>::from([hash[0], hash[1], hash[2], hash[3]])
} }
/// Extracts a static attribute from a swap.
pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result<Vec<u8>, EncodingError> {
Ok(swap
.component
.static_attributes
.get(attribute_name)
.ok_or_else(|| {
EncodingError::FatalError(format!("Attribute {} not found", attribute_name))
})?
.to_vec())
}