use std::collections::HashSet;
use alloy::primitives::{aliases::U24, U8};
use tycho_common::Bytes;
use crate::encoding::{
errors::EncodingError,
evm::{
constants::NON_PLE_ENCODED_PROTOCOLS,
gas_estimator::estimate_gas_usage,
group_swaps::{group_swaps, SwapGroup},
strategy_encoder::strategy_validators::{
SequentialSwapValidator, SingleSwapValidator, SplitSwapValidator, SwapValidator,
},
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
utils::{get_token_position, map_on_threads, percentage_to_uint24, ple_encode},
},
models::{EncodedSolution, EncodingContext, Solution, Strategy, UserTransferType},
};
struct EncodedSwapGroup {
executor_address: Bytes,
protocol_data: Vec<u8>,
}
fn encode_swap_group(
swap_encoder_registry: &SwapEncoderRegistry,
grouped_swap: &SwapGroup,
router_address: &Bytes,
) -> Result<EncodedSwapGroup, EncodingError> {
let protocol = &grouped_swap.protocol_system;
let swap_encoder = swap_encoder_registry
.get_encoder(protocol)
.ok_or_else(|| {
EncodingError::InvalidInput(format!("Swap encoder not found for protocol: {protocol}"))
})?;
let encoding_context = EncodingContext {
router_address: Some(router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
if encoding_context.group_token_in == *swap.token_in().address {
initial_protocol_data = protocol_data;
} else {
grouped_protocol_data.push(protocol_data);
}
}
if !grouped_protocol_data.is_empty() {
if NON_PLE_ENCODED_PROTOCOLS.contains(protocol.as_str()) {
for protocol_data in grouped_protocol_data {
initial_protocol_data.extend(protocol_data);
}
} else {
initial_protocol_data.extend(ple_encode(grouped_protocol_data));
}
}
Ok(EncodedSwapGroup {
executor_address: swap_encoder.executor_address().clone(),
protocol_data: initial_protocol_data,
})
}
fn encode_swap_groups(
swap_encoder_registry: &SwapEncoderRegistry,
grouped_swaps: &[SwapGroup],
router_address: &Bytes,
) -> Result<Vec<EncodedSwapGroup>, EncodingError> {
let any_group_blocks = grouped_swaps.iter().any(|group| {
swap_encoder_registry
.get_encoder(&group.protocol_system)
.is_some_and(|encoder| encoder.blocks_on_quote())
});
if !any_group_blocks {
let mut encoded_groups = Vec::with_capacity(grouped_swaps.len());
for grouped_swap in grouped_swaps {
encoded_groups.push(encode_swap_group(
swap_encoder_registry,
grouped_swap,
router_address,
)?);
}
return Ok(encoded_groups);
}
map_on_threads(grouped_swaps, |grouped_swap| {
encode_swap_group(swap_encoder_registry, grouped_swap, router_address)
})
}
#[derive(Clone)]
pub(crate) struct SingleSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
router_address: Bytes,
single_swap_validator: SingleSwapValidator,
}
impl SingleSwapStrategyEncoder {
pub(crate) fn new(
swap_encoder_registry: SwapEncoderRegistry,
router_address: Bytes,
) -> Result<Self, EncodingError> {
Ok(Self {
swap_encoder_registry,
router_address: router_address.clone(),
single_swap_validator: SingleSwapValidator,
})
}
fn encode_swap_header(&self, executor_address: Bytes, protocol_data: Vec<u8>) -> Vec<u8> {
let mut encoded = Vec::new();
encoded.extend(executor_address.to_vec());
encoded.extend(protocol_data);
encoded
}
}
impl SingleSwapStrategyEncoder {
pub(crate) fn encode_strategy(
&self,
solution: &Solution,
) -> Result<EncodedSolution, EncodingError> {
let function_signature = match solution.user_transfer_type() {
UserTransferType::TransferFromPermit2 => {
"singleSwapPermit2(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
}
UserTransferType::TransferFrom => {
"singleSwap(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
UserTransferType::UseVaultsFunds => {
"singleSwapUsingVault(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
}
.to_string();
self.single_swap_validator
.validate_swap_path(solution.swaps(), solution.token_in(), solution.token_out())?;
let grouped_swaps = group_swaps(solution.swaps());
let number_of_groups = grouped_swaps.len();
if number_of_groups != 1 {
return Err(EncodingError::InvalidInput(format!(
"Single strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
)));
}
let grouped_swap = grouped_swaps
.first()
.ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
if grouped_swap.split != 0f64 {
return Err(EncodingError::InvalidInput(
"Splits not supported for single swaps.".to_string(),
));
}
let encoded_group =
encode_swap_group(&self.swap_encoder_registry, grouped_swap, &self.router_address)?;
let swap_data =
self.encode_swap_header(encoded_group.executor_address, encoded_group.protocol_data);
let gas_usage = estimate_gas_usage(solution, Strategy::Single);
Ok(EncodedSolution::new(
swap_data,
self.router_address.clone(),
function_signature,
0,
gas_usage,
))
}
}
#[derive(Clone)]
pub(crate) struct SequentialSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
router_address: Bytes,
sequential_swap_validator: SequentialSwapValidator,
}
impl SequentialSwapStrategyEncoder {
pub(crate) fn new(
swap_encoder_registry: SwapEncoderRegistry,
router_address: Bytes,
) -> Result<Self, EncodingError> {
Ok(Self {
swap_encoder_registry,
router_address: router_address.clone(),
sequential_swap_validator: SequentialSwapValidator,
})
}
fn encode_swap_header(&self, executor_address: Bytes, protocol_data: Vec<u8>) -> Vec<u8> {
let mut encoded = Vec::new();
encoded.extend(executor_address.to_vec());
encoded.extend(protocol_data);
encoded
}
}
impl SequentialSwapStrategyEncoder {
pub(crate) fn encode_strategy(
&self,
solution: &Solution,
) -> Result<EncodedSolution, EncodingError> {
let function_signature = match solution.user_transfer_type() {
UserTransferType::TransferFromPermit2 => {
"sequentialSwapPermit2(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
}
UserTransferType::TransferFrom => {
"sequentialSwap(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
UserTransferType::UseVaultsFunds => {
"sequentialSwapUsingVault(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
}
.to_string();
self.sequential_swap_validator
.validate_swap_path(solution.swaps(), solution.token_in(), solution.token_out())?;
let grouped_swaps = group_swaps(solution.swaps());
let encoded_groups =
encode_swap_groups(&self.swap_encoder_registry, &grouped_swaps, &self.router_address)?;
let mut swaps = vec![];
for encoded_group in encoded_groups {
let swap_data = self
.encode_swap_header(encoded_group.executor_address, encoded_group.protocol_data);
swaps.push(swap_data);
}
let encoded_swaps = ple_encode(swaps);
let gas_usage = estimate_gas_usage(solution, Strategy::Sequential);
Ok(EncodedSolution::new(
encoded_swaps,
self.router_address.clone(),
function_signature,
0,
gas_usage,
))
}
}
#[derive(Clone)]
pub(crate) struct SplitSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
split_swap_validator: SplitSwapValidator,
router_address: Bytes,
}
impl SplitSwapStrategyEncoder {
pub(crate) fn new(
swap_encoder_registry: SwapEncoderRegistry,
router_address: Bytes,
) -> Result<Self, EncodingError> {
Ok(Self {
swap_encoder_registry,
split_swap_validator: SplitSwapValidator,
router_address: router_address.clone(),
})
}
fn encode_swap_header(
&self,
token_in: U8,
token_out: U8,
split: U24,
executor_address: Bytes,
protocol_data: Vec<u8>,
) -> Vec<u8> {
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(protocol_data);
encoded
}
}
impl SplitSwapStrategyEncoder {
pub(crate) fn encode_strategy(
&self,
solution: &Solution,
) -> Result<EncodedSolution, EncodingError> {
let function_signature = match solution.user_transfer_type() {
UserTransferType::TransferFromPermit2 => {
"splitSwapPermit2(uint256,address,address,uint256,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
}
UserTransferType::TransferFrom => {
"splitSwap(uint256,address,address,uint256,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
UserTransferType::UseVaultsFunds => {
"splitSwapUsingVault(uint256,address,address,uint256,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
}
}
.to_string();
self.split_swap_validator
.validate_split_percentages(solution.swaps())?;
self.split_swap_validator
.validate_swap_path(solution.swaps(), solution.token_in(), solution.token_out())?;
let solution_tokens: HashSet<&Bytes> = vec![solution.token_in(), solution.token_out()]
.into_iter()
.collect();
let grouped_swaps = group_swaps(solution.swaps());
let intermediary_tokens: HashSet<&Bytes> = grouped_swaps
.iter()
.flat_map(|grouped_swap| vec![&grouped_swap.token_in, &grouped_swap.token_out])
.collect();
let mut intermediary_tokens: Vec<&Bytes> = intermediary_tokens
.difference(&solution_tokens)
.cloned()
.collect();
intermediary_tokens.sort();
let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
tokens.push(solution.token_in());
tokens.extend(intermediary_tokens);
tokens.push(solution.token_out());
let mut token_indices = Vec::with_capacity(grouped_swaps.len());
for grouped_swap in &grouped_swaps {
token_indices.push((
get_token_position(&tokens, &grouped_swap.token_in)?,
get_token_position(&tokens, &grouped_swap.token_out)?,
));
}
let encoded_groups =
encode_swap_groups(&self.swap_encoder_registry, &grouped_swaps, &self.router_address)?;
let mut swaps = Vec::with_capacity(grouped_swaps.len());
for (index, encoded_group) in encoded_groups.into_iter().enumerate() {
let (token_in_index, token_out_index) = token_indices[index];
let swap_data = self.encode_swap_header(
token_in_index,
token_out_index,
percentage_to_uint24(grouped_swaps[index].split),
encoded_group.executor_address,
encoded_group.protocol_data,
);
swaps.push(swap_data);
}
let encoded_swaps = ple_encode(swaps);
let tokens_len = if solution.token_in() == solution.token_out() {
tokens.len() - 1
} else {
tokens.len()
};
let gas_usage = estimate_gas_usage(solution, Strategy::Split);
Ok(EncodedSolution::new(
encoded_swaps,
self.router_address.clone(),
function_signature,
tokens_len,
gas_usage,
))
}
}
#[cfg(test)]
mod tests {
use std::{
collections::HashMap,
fs,
str::FromStr,
time::{Duration, Instant},
};
use alloy::{hex::encode, primitives::hex};
use num_bigint::{BigInt, BigUint};
use tycho_common::{
models::{protocol::ProtocolComponent, Chain},
Bytes,
};
use super::*;
fn eth_chain() -> Chain {
Chain::Ethereum
}
fn weth() -> Bytes {
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
}
fn get_swap_encoder_registry() -> SwapEncoderRegistry {
let executors_addresses =
fs::read_to_string("config/test_executor_addresses.json").unwrap();
let eth_chain = eth_chain();
let registry = SwapEncoderRegistry::new(eth_chain);
registry
.add_default_encoders(Some(executors_addresses))
.unwrap()
}
fn router_address() -> Bytes {
Bytes::from_str("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2").unwrap()
}
mod single {
use super::*;
use crate::encoding::models::{default_token, Swap};
#[test]
fn test_single_swap_strategy_encoder() {
let expected_amount_out = BigUint::from_str("2018817438608734439720").unwrap();
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap = Swap::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
default_token(weth.clone()),
default_token(dai.clone()),
BigUint::ZERO,
);
let swap_encoder_registry = get_swap_encoder_registry();
let encoder =
SingleSwapStrategyEncoder::new(swap_encoder_registry, router_address()).unwrap();
let solution = Solution::new(
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
Bytes::default(),
weth,
dai,
BigUint::from_str("1_000000000000000000").unwrap(),
expected_amount_out.clone(),
&expected_amount_out * BigUint::from(9800u64) / BigUint::from(10_000u64),
vec![swap],
)
.with_user_transfer_type(UserTransferType::TransferFromPermit2);
let encoded_solution = encoder
.encode_strategy(&solution)
.unwrap();
let expected_swap = String::from(concat!(
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", "a478c2975ab1ea89e8196811f51a7b7ade33eb11", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "6b175474e89094c44da98b954eedeac495271d0f", ));
let hex_calldata = encode(encoded_solution.swaps());
assert_eq!(hex_calldata, expected_swap);
assert_eq!(
encoded_solution.function_signature(),
"singleSwapPermit2(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
);
assert_eq!(encoded_solution.interacting_with(), &router_address());
}
}
mod sequential {
use super::*;
use crate::encoding::{
evm::testing_utils::delayed_bebop_swap,
models::{default_token, Swap},
};
#[test]
fn test_sequential_swap_strategy_encoder_no_permit2() {
let weth = weth();
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_weth_wbtc = Swap::new(
ProtocolComponent {
id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
default_token(weth.clone()),
default_token(wbtc.clone()),
BigUint::ZERO,
);
let swap_wbtc_usdc = Swap::new(
ProtocolComponent {
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
default_token(wbtc.clone()),
default_token(usdc.clone()),
BigUint::ZERO,
);
let swap_encoder_registry = get_swap_encoder_registry();
let encoder =
SequentialSwapStrategyEncoder::new(swap_encoder_registry, router_address())
.unwrap();
let solution = Solution::new(
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
Bytes::default(),
weth,
usdc,
BigUint::from_str("1_000000000000000000").unwrap(),
BigUint::from_str("26173932").unwrap(),
BigUint::from_str("25650453").unwrap(),
vec![swap_weth_wbtc, swap_wbtc_usdc],
);
let encoded_solution = encoder
.encode_strategy(&solution)
.unwrap();
let hex_calldata = encode(encoded_solution.swaps());
let expected = String::from(concat!(
"0050", "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", "bb2b8038a1640196fbe3e38816f3e67cba72d940", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "2260fac5e5542a773aa44fbcfedf7c193bc2c599", "0050", "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", "004375dff511095cc5a197a54140a24efef3a416", "2260fac5e5542a773aa44fbcfedf7c193bc2c599", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", ));
assert_eq!(hex_calldata, expected);
assert_eq!(
encoded_solution.function_signature(),
"sequentialSwap(uint256,address,address,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
);
assert_eq!(encoded_solution.interacting_with(), &router_address());
}
#[test]
fn test_sequential_swap_encodes_rfq_hops_in_parallel() {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let weth = weth();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let delay = Duration::from_millis(300);
let solution = Solution::new(
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
Bytes::default(),
usdc.clone(),
dai.clone(),
BigUint::from(1_000u64),
BigUint::from(1_000u64),
BigUint::from(900u64),
vec![
delayed_bebop_swap(usdc.clone(), weth.clone(), delay),
delayed_bebop_swap(weth.clone(), dai.clone(), delay),
],
);
let encoder =
SequentialSwapStrategyEncoder::new(get_swap_encoder_registry(), router_address())
.unwrap();
let start = Instant::now();
let encoded_solution = encoder
.encode_strategy(&solution)
.unwrap();
let elapsed = start.elapsed();
assert!(elapsed < Duration::from_millis(500), "encoding took {elapsed:?}");
let hex_calldata = encode(encoded_solution.swaps());
let first_hop = hex_calldata
.find(&encode(&usdc)[..])
.unwrap();
let second_hop = hex_calldata
.find(&encode(&dai)[..])
.unwrap();
assert!(first_hop < second_hop, "hops are out of route order");
}
}
mod split {
use super::*;
use crate::encoding::models::{default_token, Swap};
#[test]
fn test_split_input_cyclic_swap() {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_usdc_weth_pool1 = Swap::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(),
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(500).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(usdc.clone()),
default_token(weth.clone()),
BigUint::ZERO,
)
.with_split(0.6f64);
let swap_usdc_weth_pool2 = Swap::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(),
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(usdc.clone()),
default_token(weth.clone()),
BigUint::ZERO,
);
let swap_weth_usdc_pool2 = Swap::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
protocol_system: "uniswap_v2".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(weth.clone()),
default_token(usdc.clone()),
BigUint::ZERO,
);
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(
swap_encoder_registry,
Bytes::from("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2"),
)
.unwrap();
let solution = Solution::new(
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
Bytes::default(),
usdc.clone(),
usdc.clone(),
BigUint::from_str("100000000").unwrap(),
BigUint::from_str("99574171").unwrap(),
BigUint::from_str("97582687").unwrap(),
vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2],
)
.with_user_transfer_type(UserTransferType::TransferFromPermit2);
let encoded_solution = encoder
.encode_strategy(&solution)
.unwrap();
let hex_calldata = hex::encode(encoded_solution.swaps());
let expected_swaps = [
"0059", "00", "01", "999999", "2e234dae75c793f67a35089c9d99245e1c58470b", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "0001f4", "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", "01", "0059", "00", "01", "000000", "2e234dae75c793f67a35089c9d99245e1c58470b", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "000bb8", "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", "01", "0055", "01", "00", "000000", "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", ]
.join("");
assert_eq!(hex_calldata, expected_swaps);
assert_eq!(
encoded_solution.function_signature(),
"splitSwapPermit2(uint256,address,address,uint256,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
);
assert_eq!(encoded_solution.interacting_with(), &router_address());
}
#[test]
fn test_split_output_cyclic_swap() {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_usdc_weth_v2 = Swap::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), protocol_system: "uniswap_v2".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(500).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(usdc.clone()),
default_token(weth.clone()),
BigUint::ZERO,
);
let swap_weth_usdc_v3_pool1 = Swap::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(),
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(500).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(weth.clone()),
default_token(usdc.clone()),
BigUint::ZERO,
)
.with_split(0.6f64);
let swap_weth_usdc_v3_pool2 = Swap::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(),
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
attrs.insert(
"fee".to_string(),
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
);
attrs
},
..Default::default()
},
default_token(weth.clone()),
default_token(usdc.clone()),
BigUint::ZERO,
);
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(
swap_encoder_registry,
Bytes::from("0xcd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2"),
)
.unwrap();
let solution = Solution::new(
Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
Bytes::default(),
usdc.clone(),
usdc.clone(),
BigUint::from_str("100000000").unwrap(),
BigUint::from_str("99025908").unwrap(),
BigUint::from_str("97045389").unwrap(),
vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2],
);
let encoded_solution = encoder
.encode_strategy(&solution)
.unwrap();
let hex_calldata = hex::encode(encoded_solution.swaps());
let expected_swaps = [
"0055", "00", "01", "000000", "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "0059", "01", "00", "999999", "2e234dae75c793f67a35089c9d99245e1c58470b", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0001f4", "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", "00", "0059", "01", "00", "000000", "2e234dae75c793f67a35089c9d99245e1c58470b", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "000bb8", "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", "00", ]
.join("");
assert_eq!(hex_calldata, expected_swaps);
assert_eq!(
encoded_solution.function_signature(),
"splitSwap(uint256,address,address,uint256,uint256,uint256,address,\
(uint32,address,uint256,uint256,bytes),bytes)"
);
assert_eq!(encoded_solution.interacting_with(), &router_address());
}
}
}