Merge branch 'refs/heads/main' into feat/bebop-rfq-encoder-and-executor

# Conflicts:
#	foundry/test/TychoRouterProtocolIntegration.t.sol
#	foundry/test/TychoRouterTestSetup.sol
#	foundry/test/assets/calldata.txt
#	foundry/test/protocols/BebopExecutor.t.sol
#	src/encoding/evm/tycho_encoders.rs

Took 4 minutes
This commit is contained in:
Diana Carvalho
2025-06-24 10:17:33 +01:00
40 changed files with 3218 additions and 991 deletions

View File

@@ -75,8 +75,8 @@ pub fn encode_tycho_router_call(
chain_id: u64,
encoded_solution: EncodedSolution,
solution: &Solution,
user_transfer_type: UserTransferType,
native_address: Bytes,
user_transfer_type: &UserTransferType,
native_address: &Bytes,
signer: Option<PrivateKeySigner>,
) -> Result<Transaction, EncodingError> {
let (mut unwrap, mut wrap) = (false, false);
@@ -137,7 +137,7 @@ pub fn encode_tycho_router_call(
wrap,
unwrap,
receiver,
user_transfer_type == UserTransferType::TransferFrom,
user_transfer_type == &UserTransferType::TransferFrom,
encoded_solution.swaps,
)
.abi_encode()
@@ -172,7 +172,7 @@ pub fn encode_tycho_router_call(
wrap,
unwrap,
receiver,
user_transfer_type == UserTransferType::TransferFrom,
user_transfer_type == &UserTransferType::TransferFrom,
encoded_solution.swaps,
)
.abi_encode()
@@ -209,7 +209,7 @@ pub fn encode_tycho_router_call(
unwrap,
n_tokens,
receiver,
user_transfer_type == UserTransferType::TransferFrom,
user_transfer_type == &UserTransferType::TransferFrom,
encoded_solution.swaps,
)
.abi_encode()
@@ -218,7 +218,7 @@ pub fn encode_tycho_router_call(
};
let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata);
let value = if solution.given_token == native_address {
let value = if solution.given_token == *native_address {
solution.given_amount.clone()
} else {
BigUint::ZERO

View File

@@ -24,7 +24,7 @@ pub struct SwapGroup {
///
/// An example where this applies is the case of USV4, which uses a PoolManager contract
/// to save token transfers on consecutive swaps.
pub fn group_swaps(swaps: Vec<Swap>) -> Vec<SwapGroup> {
pub fn group_swaps(swaps: &Vec<Swap>) -> Vec<SwapGroup> {
let mut grouped_swaps: Vec<SwapGroup> = Vec::new();
let mut current_group: Option<SwapGroup> = None;
let mut last_swap_protocol = "".to_string();
@@ -127,7 +127,7 @@ mod tests {
split: 0f64,
user_data: None,
};
let grouped_swaps = group_swaps(vec![
let grouped_swaps = group_swaps(&vec![
swap_weth_wbtc.clone(),
swap_wbtc_usdc.clone(),
swap_usdc_dai.clone(),
@@ -211,7 +211,7 @@ mod tests {
split: 0f64,
user_data: None,
};
let grouped_swaps = group_swaps(vec![
let grouped_swaps = group_swaps(&vec![
swap_wbtc_weth.clone(),
swap_weth_usdc.clone(),
swap_weth_dai.clone(),
@@ -303,7 +303,7 @@ mod tests {
user_data: None,
};
let grouped_swaps = group_swaps(vec![
let grouped_swaps = group_swaps(&vec![
swap_weth_wbtc.clone(),
swap_wbtc_usdc.clone(),
swap_weth_dai.clone(),

View File

@@ -71,12 +71,12 @@ impl SingleSwapStrategyEncoder {
}
impl StrategyEncoder for SingleSwapStrategyEncoder {
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
let grouped_swaps = group_swaps(solution.clone().swaps);
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
let grouped_swaps = group_swaps(&solution.swaps);
let number_of_groups = grouped_swaps.len();
if number_of_groups != 1 {
return Err(EncodingError::InvalidInput(format!(
"Executor strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
"Single strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
)))
}
@@ -91,15 +91,15 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
}
let (mut unwrap, mut wrap) = (false, false);
if let Some(action) = solution.native_action.clone() {
match action {
if let Some(action) = &solution.native_action {
match *action {
NativeAction::Wrap => wrap = true,
NativeAction::Unwrap => unwrap = true,
}
}
let protocol = grouped_swap.protocol_system.clone();
let protocol = &grouped_swap.protocol_system;
let swap_encoder = self
.get_swap_encoder(&protocol)
.get_swap_encoder(protocol)
.ok_or_else(|| {
EncodingError::InvalidInput(format!(
"Swap encoder not found for protocol: {protocol}"
@@ -111,9 +111,9 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
let transfer = self
.transfer_optimization
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
.get_transfers(grouped_swap, &solution.given_token, wrap, false);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
receiver: swap_receiver,
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
@@ -123,7 +123,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
let mut grouped_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
}
@@ -213,7 +213,7 @@ impl SequentialSwapStrategyEncoder {
}
impl StrategyEncoder for SequentialSwapStrategyEncoder {
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
self.sequential_swap_validator
.validate_swap_path(
&solution.swaps,
@@ -224,11 +224,11 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
&self.wrapped_address,
)?;
let grouped_swaps = group_swaps(solution.swaps);
let grouped_swaps = group_swaps(&solution.swaps);
let mut wrap = false;
if let Some(action) = solution.native_action.clone() {
if action == NativeAction::Wrap {
if let Some(action) = &solution.native_action {
if action == &NativeAction::Wrap {
wrap = true
}
}
@@ -236,9 +236,9 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
let mut swaps = vec![];
let mut next_in_between_swap_optimization_allowed = true;
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
let protocol = grouped_swap.protocol_system.clone();
let protocol = &grouped_swap.protocol_system;
let swap_encoder = self
.get_swap_encoder(&protocol)
.get_swap_encoder(protocol)
.ok_or_else(|| {
EncodingError::InvalidInput(format!(
"Swap encoder not found for protocol: {protocol}",
@@ -249,19 +249,19 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
let next_swap = grouped_swaps.get(i + 1);
let (swap_receiver, next_swap_optimization) = self
.transfer_optimization
.get_receiver(solution.receiver.clone(), next_swap)?;
.get_receiver(&solution.receiver, next_swap)?;
next_in_between_swap_optimization_allowed = next_swap_optimization;
let transfer = self
.transfer_optimization
.get_transfers(
grouped_swap.clone(),
solution.given_token.clone(),
grouped_swap,
&solution.given_token,
wrap,
in_between_swap_optimization_allowed,
);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
receiver: swap_receiver,
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
@@ -271,8 +271,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
let mut grouped_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data =
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
}
@@ -376,7 +375,7 @@ impl SplitSwapStrategyEncoder {
}
impl StrategyEncoder for SplitSwapStrategyEncoder {
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
self.split_swap_validator
.validate_split_percentages(&solution.swaps)?;
self.split_swap_validator
@@ -391,20 +390,17 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
// The tokens array is composed of the given token, the checked token and all the
// intermediary tokens in between. The contract expects the tokens to be in this order.
let solution_tokens: HashSet<Bytes> =
vec![solution.given_token.clone(), solution.checked_token.clone()]
.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.clone(), grouped_swap.token_out.clone()]
})
let solution_tokens: HashSet<&Bytes> = vec![&solution.given_token, &solution.checked_token]
.into_iter()
.collect();
let mut intermediary_tokens: Vec<Bytes> = intermediary_tokens
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();
@@ -413,8 +409,8 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
intermediary_tokens.sort();
let (mut unwrap, mut wrap) = (false, false);
if let Some(action) = solution.native_action.clone() {
match action {
if let Some(action) = &solution.native_action {
match *action {
NativeAction::Wrap => wrap = true,
NativeAction::Unwrap => unwrap = true,
}
@@ -422,23 +418,23 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
if wrap {
tokens.push(self.wrapped_address.clone());
tokens.push(&self.wrapped_address);
} else {
tokens.push(solution.given_token.clone());
tokens.push(&solution.given_token);
}
tokens.extend(intermediary_tokens);
if unwrap {
tokens.push(self.wrapped_address.clone());
tokens.push(&self.wrapped_address);
} else {
tokens.push(solution.checked_token.clone());
tokens.push(&solution.checked_token);
}
let mut swaps = vec![];
for grouped_swap in grouped_swaps.iter() {
let protocol = grouped_swap.protocol_system.clone();
let protocol = &grouped_swap.protocol_system;
let swap_encoder = self
.get_swap_encoder(&protocol)
.get_swap_encoder(protocol)
.ok_or_else(|| {
EncodingError::InvalidInput(format!(
"Swap encoder not found for protocol: {protocol}",
@@ -452,9 +448,9 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
};
let transfer = self
.transfer_optimization
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
.get_transfers(grouped_swap, &solution.given_token, wrap, false);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
receiver: swap_receiver,
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
@@ -464,14 +460,13 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
let mut grouped_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let protocol_data =
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
}
let swap_data = self.encode_swap_header(
get_token_position(tokens.clone(), grouped_swap.token_in.clone())?,
get_token_position(tokens.clone(), grouped_swap.token_out.clone())?,
get_token_position(&tokens, &grouped_swap.token_in)?,
get_token_position(&tokens, &grouped_swap.token_out)?,
percentage_to_uint24(grouped_swap.split),
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
EncodingError::FatalError("Invalid executor address".to_string())
@@ -581,7 +576,7 @@ mod tests {
};
let encoded_solution = encoder
.encode_strategy(solution.clone())
.encode_strategy(&solution)
.unwrap();
let expected_swap = String::from(concat!(
@@ -642,7 +637,7 @@ mod tests {
};
let encoded_solution = encoder
.encode_strategy(solution.clone())
.encode_strategy(&solution)
.unwrap();
let expected_input = [
@@ -724,7 +719,7 @@ mod tests {
};
let encoded_solution = encoder
.encode_strategy(solution.clone())
.encode_strategy(&solution)
.unwrap();
let hex_calldata = encode(&encoded_solution.swaps);
@@ -864,7 +859,7 @@ mod tests {
};
let encoded_solution = encoder
.encode_strategy(solution.clone())
.encode_strategy(&solution)
.unwrap();
let hex_calldata = hex::encode(&encoded_solution.swaps);
@@ -1014,7 +1009,7 @@ mod tests {
};
let encoded_solution = encoder
.encode_strategy(solution.clone())
.encode_strategy(&solution)
.unwrap();
let hex_calldata = hex::encode(&encoded_solution.swaps);

View File

@@ -118,7 +118,7 @@ impl SplitSwapValidator {
/// * The sum of all non-remainder splits for each token is < 1 (100%)
/// * There are no negative split amounts
pub fn validate_split_percentages(&self, swaps: &[Swap]) -> Result<(), EncodingError> {
let mut swaps_by_token: HashMap<Bytes, Vec<&Swap>> = HashMap::new();
let mut swaps_by_token: HashMap<&Bytes, Vec<&Swap>> = HashMap::new();
for swap in swaps {
if swap.split >= 1.0 {
return Err(EncodingError::InvalidInput(format!(
@@ -127,7 +127,7 @@ impl SplitSwapValidator {
)));
}
swaps_by_token
.entry(swap.token_in.clone())
.entry(&swap.token_in)
.or_default()
.push(swap);
}

View File

@@ -33,12 +33,12 @@ impl TransferOptimization {
/// Returns the transfer type that should be used for the current transfer.
pub fn get_transfers(
&self,
swap: SwapGroup,
given_token: Bytes,
swap: &SwapGroup,
given_token: &Bytes,
wrap: bool,
in_between_swap_optimization: bool,
) -> TransferType {
let is_first_swap = swap.token_in == given_token;
let is_first_swap = swap.token_in == *given_token;
let in_transfer_required: bool =
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
@@ -80,7 +80,7 @@ impl TransferOptimization {
// is necessary for the next swap transfer type decision).
pub fn get_receiver(
&self,
solution_receiver: Bytes,
solution_receiver: &Bytes,
next_swap: Option<&SwapGroup>,
) -> Result<(Bytes, bool), EncodingError> {
if let Some(next) = next_swap {
@@ -104,7 +104,7 @@ impl TransferOptimization {
}
} else {
// last swap - there is no next swap
Ok((solution_receiver, false))
Ok((solution_receiver.clone(), false))
}
}
}
@@ -189,12 +189,8 @@ mod tests {
};
let optimization =
TransferOptimization::new(eth(), weth(), user_transfer_type, router_address());
let transfer = optimization.get_transfers(
swap.clone(),
given_token,
wrap,
in_between_swap_optimization,
);
let transfer =
optimization.get_transfers(&swap, &given_token, wrap, in_between_swap_optimization);
assert_eq!(transfer, expected_transfer);
}
@@ -249,7 +245,7 @@ mod tests {
})
};
let result = optimization.get_receiver(receiver(), next_swap.as_ref());
let result = optimization.get_receiver(&receiver(), next_swap.as_ref());
assert!(result.is_ok());
let (actual_receiver, optimization_flag) = result.unwrap();

View File

@@ -47,8 +47,8 @@ impl SwapEncoder for UniswapV2SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_in_address = bytes_to_address(&swap.token_in)?;
let token_out_address = bytes_to_address(&swap.token_out)?;
@@ -105,8 +105,8 @@ impl SwapEncoder for UniswapV3SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_in_address = bytes_to_address(&swap.token_in)?;
let token_out_address = bytes_to_address(&swap.token_out)?;
@@ -114,7 +114,7 @@ impl SwapEncoder for UniswapV3SwapEncoder {
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address);
let component_id = Address::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?;
let pool_fee_bytes = get_static_attribute(&swap, "fee")?;
let pool_fee_bytes = get_static_attribute(swap, "fee")?;
let pool_fee_u24 = pad_to_fixed_size::<3>(&pool_fee_bytes)
.map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?;
@@ -166,15 +166,15 @@ impl SwapEncoder for UniswapV4SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let fee = get_static_attribute(&swap, "key_lp_fee")?;
let fee = get_static_attribute(swap, "key_lp_fee")?;
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, "tick_spacing")?;
let tick_spacing = get_static_attribute(swap, "tick_spacing")?;
let pool_tick_spacing_u24 = pad_to_fixed_size::<3>(&tick_spacing).map_err(|_| {
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
@@ -249,15 +249,15 @@ impl SwapEncoder for BalancerV2SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new()?;
let token = bytes_to_address(&swap.token_in)?;
let approval_needed: bool;
if let Some(router_address) = encoding_context.router_address {
let tycho_router_address = bytes_to_address(&router_address)?;
if let Some(router_address) = &encoding_context.router_address {
let tycho_router_address = bytes_to_address(router_address)?;
approval_needed = token_approvals_manager.approval_needed(
token,
tycho_router_address,
@@ -310,28 +310,28 @@ impl SwapEncoder for EkuboSwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
if encoding_context.exact_out {
return Err(EncodingError::InvalidInput("exact out swaps not implemented".to_string()));
}
let fee = u64::from_be_bytes(
get_static_attribute(&swap, "fee")?
get_static_attribute(swap, "fee")?
.try_into()
.map_err(|_| EncodingError::FatalError("fee should be an u64".to_string()))?,
);
let tick_spacing = u32::from_be_bytes(
get_static_attribute(&swap, "tick_spacing")?
get_static_attribute(swap, "tick_spacing")?
.try_into()
.map_err(|_| {
EncodingError::FatalError("tick_spacing should be an u32".to_string())
})?,
);
let extension: Address = get_static_attribute(&swap, "extension")?
let extension: Address = get_static_attribute(swap, "extension")?
.as_slice()
.try_into()
.map_err(|_| EncodingError::FatalError("extension should be an address".to_string()))?;
@@ -372,6 +372,7 @@ pub struct CurveSwapEncoder {
executor_address: String,
native_token_curve_address: String,
native_token_address: Bytes,
wrapped_native_token_address: Bytes,
}
impl CurveSwapEncoder {
@@ -407,14 +408,32 @@ impl CurveSwapEncoder {
}
}
// Some curve pools support both ETH and WETH as tokens.
// They do the wrapping/unwrapping inside the pool
fn normalize_token(&self, token: Address, coins: &[Address]) -> Result<Address, EncodingError> {
let native_token_address = bytes_to_address(&self.native_token_address)?;
let wrapped_native_token_address = bytes_to_address(&self.wrapped_native_token_address)?;
if token == native_token_address && !coins.contains(&token) {
Ok(wrapped_native_token_address)
} else if token == wrapped_native_token_address && !coins.contains(&token) {
Ok(native_token_address)
} else {
Ok(token)
}
}
fn get_coin_indexes(
&self,
swap: Swap,
swap: &Swap,
token_in: Address,
token_out: Address,
) -> Result<(U8, U8), EncodingError> {
let coins_bytes = get_static_attribute(&swap, "coins")?;
let coins_bytes = get_static_attribute(swap, "coins")?;
let coins: Vec<Address> = from_str(std::str::from_utf8(&coins_bytes)?)?;
let token_in = self.normalize_token(token_in, &coins)?;
let token_out = self.normalize_token(token_out, &coins)?;
let i = coins
.iter()
.position(|&addr| addr == token_in)
@@ -450,13 +469,14 @@ impl SwapEncoder for CurveSwapEncoder {
executor_address,
native_token_address: chain.native_token()?,
native_token_curve_address,
wrapped_native_token_address: chain.wrapped_token()?,
})
}
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new()?;
let native_token_curve_address = Address::from_str(&self.native_token_curve_address)
@@ -477,9 +497,9 @@ impl SwapEncoder for CurveSwapEncoder {
let component_address = Address::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?;
if let Some(router_address) = encoding_context.router_address {
if let Some(router_address) = &encoding_context.router_address {
if token_in != native_token_curve_address {
let tycho_router_address = bytes_to_address(&router_address)?;
let tycho_router_address = bytes_to_address(router_address)?;
approval_needed = token_approvals_manager.approval_needed(
token_in,
tycho_router_address,
@@ -492,7 +512,7 @@ impl SwapEncoder for CurveSwapEncoder {
approval_needed = true;
}
let factory_bytes = get_static_attribute(&swap, "factory")?.to_vec();
let factory_bytes = get_static_attribute(swap, "factory")?.to_vec();
// the conversion to Address is necessary to checksum the address
let factory_address =
Address::from_str(std::str::from_utf8(&factory_bytes).map_err(|_| {
@@ -552,8 +572,8 @@ impl SwapEncoder for MaverickV2SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let component_id = AlloyBytes::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?;
@@ -595,8 +615,8 @@ impl SwapEncoder for BalancerV3SwapEncoder {
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let pool = Address::from_str(&swap.component.id).map_err(|_| {
EncodingError::FatalError("Invalid pool address for Balancer v3".to_string())
@@ -1052,7 +1072,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(
@@ -1112,7 +1132,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(
@@ -1177,7 +1197,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1249,7 +1269,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1322,7 +1342,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1417,10 +1437,10 @@ mod tests {
)
.unwrap();
let initial_encoded_swap = encoder
.encode_swap(initial_swap, context.clone())
.encode_swap(&initial_swap, &context)
.unwrap();
let second_encoded_swap = encoder
.encode_swap(second_swap, context)
.encode_swap(&second_swap, &context)
.unwrap();
let combined_hex =
@@ -1501,7 +1521,7 @@ mod tests {
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1577,11 +1597,11 @@ mod tests {
};
let first_encoded_swap = encoder
.encode_swap(first_swap, encoding_context.clone())
.encode_swap(&first_swap, &encoding_context)
.unwrap();
let second_encoded_swap = encoder
.encode_swap(second_swap, encoding_context)
.encode_swap(&second_swap, &encoding_context)
.unwrap();
let combined_hex =
@@ -1703,7 +1723,7 @@ mod tests {
.unwrap();
let (i, j) = encoder
.get_coin_indexes(
swap,
&swap,
Address::from_str(token_in).unwrap(),
Address::from_str(token_out).unwrap(),
)
@@ -1755,7 +1775,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1827,7 +1847,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1909,7 +1929,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -1974,7 +1994,7 @@ mod tests {
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
@@ -2033,7 +2053,7 @@ mod tests {
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.encode_swap(&swap, &encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);

View File

@@ -89,33 +89,36 @@ impl TychoRouterEncoder {
fn encode_solution(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
self.validate_solution(solution)?;
let protocols: HashSet<String> = solution
.clone()
.swaps
.into_iter()
.map(|swap| swap.component.protocol_system)
.iter()
.map(|swap| swap.component.protocol_system.clone())
.collect();
let mut encoded_solution = if (solution.swaps.len() == 1) ||
(protocols.len() == 1 &&
((protocols.len() == 1 &&
protocols
.iter()
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str())))
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str()))) &&
solution
.swaps
.iter()
.all(|swap| swap.split == 0.0))
{
self.single_swap_strategy
.encode_strategy(solution.clone())?
.encode_strategy(solution)?
} else if solution
.swaps
.iter()
.all(|swap| swap.split == 0.0)
{
self.sequential_swap_strategy
.encode_strategy(solution.clone())?
.encode_strategy(solution)?
} else {
self.split_swap_strategy
.encode_strategy(solution.clone())?
.encode_strategy(solution)?
};
if let Some(permit2) = self.permit2.clone() {
if let Some(permit2) = &self.permit2 {
let permit = permit2.get_permit(
&self.router_address,
&solution.sender,
@@ -153,8 +156,8 @@ impl TychoEncoder for TychoRouterEncoder {
self.chain.id,
encoded_solution,
solution,
self.user_transfer_type.clone(),
self.chain.native_token()?.clone(),
&self.user_transfer_type,
&self.chain.native_token()?,
self.signer.clone(),
)?;
@@ -185,8 +188,8 @@ impl TychoEncoder for TychoRouterEncoder {
}
let native_address = self.chain.native_token()?;
let wrapped_address = self.chain.wrapped_token()?;
if let Some(native_action) = solution.clone().native_action {
if native_action == NativeAction::Wrap {
if let Some(native_action) = &solution.native_action {
if native_action == &NativeAction::Wrap {
if solution.given_token != native_address {
return Err(EncodingError::FatalError(
"Native token must be the input token in order to wrap".to_string(),
@@ -200,7 +203,7 @@ impl TychoEncoder for TychoRouterEncoder {
));
}
}
} else if native_action == NativeAction::Unwrap {
} else if native_action == &NativeAction::Unwrap {
if solution.checked_token != native_address {
return Err(EncodingError::FatalError(
"Native token must be the output token in order to unwrap".to_string(),
@@ -223,17 +226,17 @@ impl TychoEncoder for TychoRouterEncoder {
// so we don't count the split tokens more than once
if swap.split != 0.0 {
if !split_tokens_already_considered.contains(&swap.token_in) {
solution_tokens.push(swap.token_in.clone());
split_tokens_already_considered.insert(swap.token_in.clone());
solution_tokens.push(&swap.token_in);
split_tokens_already_considered.insert(&swap.token_in);
}
} else {
// it might be the last swap of the split or a regular swap
if !split_tokens_already_considered.contains(&swap.token_in) {
solution_tokens.push(swap.token_in.clone());
solution_tokens.push(&swap.token_in);
}
}
if i == solution.swaps.len() - 1 {
solution_tokens.push(swap.token_out.clone());
solution_tokens.push(&swap.token_out);
}
}
@@ -241,7 +244,7 @@ impl TychoEncoder for TychoRouterEncoder {
solution_tokens
.iter()
.cloned()
.collect::<HashSet<Bytes>>()
.collect::<HashSet<&Bytes>>()
.len()
{
if let Some(last_swap) = solution.swaps.last() {
@@ -252,7 +255,7 @@ impl TychoEncoder for TychoRouterEncoder {
} else {
// it is a valid cyclical swap
// we don't support any wrapping or unwrapping in this case
if let Some(_native_action) = solution.clone().native_action {
if let Some(_native_action) = &solution.native_action {
return Err(EncodingError::FatalError(
"Wrapping/Unwrapping is not available in cyclical swaps".to_string(),
));
@@ -283,9 +286,9 @@ impl TychoExecutorEncoder {
fn encode_executor_calldata(
&self,
solution: Solution,
solution: &Solution,
) -> Result<EncodedSolution, EncodingError> {
let grouped_swaps = group_swaps(solution.clone().swaps);
let grouped_swaps = group_swaps(&solution.swaps);
let number_of_groups = grouped_swaps.len();
if number_of_groups > 1 {
return Err(EncodingError::InvalidInput(format!(
@@ -297,8 +300,6 @@ impl TychoExecutorEncoder {
.first()
.ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
let receiver = solution.receiver;
let swap_encoder = self
.swap_encoder_registry
.get_encoder(&grouped_swap.protocol_system)
@@ -319,14 +320,14 @@ impl TychoExecutorEncoder {
TransferType::None
};
let encoding_context = EncodingContext {
receiver: receiver.clone(),
receiver: solution.receiver.clone(),
exact_out: solution.exact_out,
router_address: None,
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
};
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
grouped_protocol_data.extend(protocol_data);
}
@@ -353,7 +354,7 @@ impl TychoEncoder for TychoExecutorEncoder {
.ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
self.validate_solution(solution)?;
let encoded_solution = self.encode_executor_calldata(solution.clone())?;
let encoded_solution = self.encode_executor_calldata(solution)?;
Ok(vec![encoded_solution])
}
@@ -609,6 +610,32 @@ mod tests {
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
}
#[test]
fn test_encode_router_calldata_split_swap_group() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let mut swap_usdc_eth = swap_usdc_eth_univ4();
swap_usdc_eth.split = 0.5; // Set split to 50%
let solution = Solution {
exact_out: false,
given_token: usdc(),
given_amount: BigUint::from_str("1000_000000").unwrap(),
checked_token: eth(),
checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
swaps: vec![swap_usdc_eth, swap_usdc_eth_univ4()],
..Default::default()
};
let encoded_solution_res = encoder.encode_solution(&solution);
assert!(encoded_solution_res.is_ok());
let encoded_solution = encoded_solution_res.unwrap();
assert!(encoded_solution
.function_signature
.contains("splitSwap"));
}
#[test]
fn test_validate_fails_for_exact_out() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -1260,98 +1287,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::{
evm::utils::{biguint_to_u256, write_calldata_to_file},
models::BebopOrderType,
};
/// Builds Bebop user_data with support for single or multiple signatures
///
/// # Arguments
/// * `order_type` - The type of Bebop order (Single or Aggregate)
/// * `filled_taker_amount` - Amount to fill (0 means fill entire order)
/// * `quote_data` - The ABI-encoded order data
/// * `signatures` - Vector of (signature_bytes, signature_type) tuples
/// - For Single orders: expects exactly 1 signature
/// - For Aggregate orders: expects 1 or more signatures (one per maker)
fn build_bebop_user_data(
order_type: BebopOrderType,
filled_taker_amount: U256,
quote_data: &[u8],
signatures: Vec<(Vec<u8>, u8)>, // (signature, signature_type)
) -> Bytes {
// ABI encode MakerSignature[] array
// Format: offset_to_array | array_length | [offset_to_struct_i]... | [struct_i_data]...
let mut encoded_maker_sigs = Vec::new();
// Calculate total size needed
let array_offset = 32; // offset to array start
let array_length_size = 32;
let struct_offsets_size = 32 * signatures.len();
let _header_size = array_length_size + struct_offsets_size;
// Build each struct's data and calculate offsets
let mut struct_data = Vec::new();
let mut struct_offsets = Vec::new();
// Offsets are relative to the start of array data, not the absolute position
// Array data starts after array length, so first offset is after all offset values
let mut current_offset = struct_offsets_size; // Just the space for offsets, not including array length
for (signature, signature_type) in &signatures {
struct_offsets.push(current_offset);
// Each struct contains:
// - offset to signatureBytes (32 bytes) - always 0x40 (64)
// - flags (32 bytes)
// - signatureBytes length (32 bytes)
// - signatureBytes data (padded to 32 bytes)
let mut struct_bytes = Vec::new();
// Offset to signatureBytes within this struct
struct_bytes.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
// Flags (contains signature type) - AFTER the offset, not before!
let flags = U256::from(*signature_type);
struct_bytes.extend_from_slice(&flags.to_be_bytes::<32>());
// SignatureBytes length
struct_bytes.extend_from_slice(&U256::from(signature.len()).to_be_bytes::<32>());
// SignatureBytes data (padded to 32 byte boundary)
struct_bytes.extend_from_slice(signature);
let padding = (32 - (signature.len() % 32)) % 32;
struct_bytes.extend_from_slice(&vec![0u8; padding]);
current_offset += struct_bytes.len();
struct_data.push(struct_bytes);
}
// Build the complete ABI encoded array
// Offset to array (always 0x20 for a single parameter)
encoded_maker_sigs.extend_from_slice(&U256::from(array_offset).to_be_bytes::<32>());
// Array length
encoded_maker_sigs.extend_from_slice(&U256::from(signatures.len()).to_be_bytes::<32>());
// Struct offsets (relative to start of array data)
for offset in struct_offsets {
encoded_maker_sigs.extend_from_slice(&U256::from(offset).to_be_bytes::<32>());
}
// Struct data
for data in struct_data {
encoded_maker_sigs.extend_from_slice(&data);
}
// Build complete user_data
let mut user_data = Vec::new();
user_data.push(order_type as u8);
user_data.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
user_data.extend_from_slice(quote_data);
user_data.extend_from_slice(&encoded_maker_sigs);
Bytes::from(user_data)
}
use crate::encoding::evm::utils::{biguint_to_u256, write_calldata_to_file};
fn get_signer() -> PrivateKeySigner {
// Set up a mock private key for signing (Alice's pk in our contract tests)
@@ -2050,7 +1986,6 @@ mod tests {
mod optimized_transfers {
// In this module we test the ability to chain swaps or not. Different protocols are
// tested. The encoded data is used for solidity tests as well
use super::*;
#[test]
@@ -2403,140 +2338,6 @@ mod tests {
write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str());
}
#[test]
fn test_uniswap_v3_bebop() {
// Note: This test does not assert anything. It is only used to obtain
// integration test data for our router solidity test.
//
// Performs a sequential swap from WETH to ONDO through USDC using USV3 and
// Bebop RFQ
//
// WETH ───(USV3)──> USDC ───(Bebop RFQ)──> ONDO
let weth = weth();
let usdc = usdc();
let ondo = ondo();
// First swap: WETH -> USDC via UniswapV3
let swap_weth_usdc = Swap {
component: ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* WETH-USDC USV3 Pool 0.05% */
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()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
};
// Second swap: USDC -> ONDO via Bebop RFQ using real order data
// Using the same real order from the mainnet transaction at block 22667985
let expiry = 1749483840u64; // Real expiry from the order
let taker_address =
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Real taker
let maker_address =
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap(); // Real maker
let maker_nonce = 1749483765992417u64; // Real nonce
let taker_token = Address::from_str(&usdc.to_string()).unwrap();
let maker_token = Address::from_str(&ondo.to_string()).unwrap();
// Using the real order amounts
let taker_amount = U256::from_str("200000000").unwrap(); // 200 USDC (6 decimals)
let maker_amount = U256::from_str("237212396774431060000").unwrap(); // 237.21 ONDO (18 decimals)
let receiver =
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Real receiver
let packed_commands = U256::ZERO;
let flags = U256::from_str("51915842898789398998206002334703507894664330885127600393944965515693155942400").unwrap(); // Real flags
// Encode using standard ABI encoding (not packed)
let quote_data = (
expiry,
taker_address,
maker_address,
maker_nonce,
taker_token,
maker_token,
taker_amount,
maker_amount,
receiver,
packed_commands,
flags,
)
.abi_encode();
// Real signature from the order
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
// Build user_data with the quote and signature
let user_data = build_bebop_user_data(
BebopOrderType::Single,
U256::from(0), // 0 means fill entire order
&quote_data,
vec![(signature, 0)], // ETH_SIGN signature type (0)
);
let bebop_component = ProtocolComponent {
id: String::from("bebop-rfq"),
protocol_system: String::from("rfq:bebop"),
static_attributes: HashMap::new(), // No static attributes needed
..Default::default()
};
let swap_usdc_ondo = Swap {
component: bebop_component,
token_in: usdc.clone(),
token_out: ondo.clone(),
split: 0f64,
user_data: Some(user_data),
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let solution = Solution {
exact_out: false,
given_token: weth,
// Use ~0.099 WETH to get approximately 200 USDC from UniswapV3
// This should leave only dust amount in the router after Bebop consumes 200
// USDC
given_amount: BigUint::from_str("99000000000000000").unwrap(), // 0.099 WETH
checked_token: ondo,
checked_amount: BigUint::from_str("237212396774431060000").unwrap(), /* Expected ONDO from Bebop order */
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
.unwrap(),
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
.unwrap(), // Using the real order receiver
swaps: vec![swap_weth_usdc, swap_usdc_ondo],
..Default::default()
};
let encoded_solution = encoder
.encode_solutions(vec![solution.clone()])
.unwrap()[0]
.clone();
let calldata = encode_tycho_router_call(
eth_chain().id,
encoded_solution,
&solution,
UserTransferType::TransferFrom,
eth(),
None,
)
.unwrap()
.data;
let hex_calldata = encode(&calldata);
write_calldata_to_file("test_uniswap_v3_bebop", hex_calldata.as_str());
}
#[test]
fn test_multi_protocol() {
// Note: This test does not assert anything. It is only used to obtain
@@ -3827,252 +3628,6 @@ mod tests {
hex_calldata.as_str(),
);
}
#[test]
fn test_single_encoding_strategy_bebop() {
// Use the same mainnet data from Solidity tests
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
let token_in = usdc();
let token_out = ondo();
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
// Create the exact same order from mainnet
let expiry = 1749483840u64;
let taker_address =
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Order receiver from mainnet
let maker_address =
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap();
let maker_nonce = 1749483765992417u64;
let taker_token = Address::from_str(&token_in.to_string()).unwrap();
let maker_token = Address::from_str(&token_out.to_string()).unwrap();
let taker_amount = U256::from_str(&amount_in.to_string()).unwrap();
let maker_amount = U256::from_str(&amount_out.to_string()).unwrap();
let receiver = taker_address; // Same as taker_address in this order
let packed_commands = U256::ZERO;
let flags = U256::from_str(
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
)
.unwrap();
// Encode using standard ABI encoding (not packed)
let quote_data = (
expiry,
taker_address,
maker_address,
maker_nonce,
taker_token,
maker_token,
taker_amount,
maker_amount,
receiver,
packed_commands,
flags,
)
.abi_encode();
// Real signature from mainnet
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
// Build user_data with the quote and signature
let user_data = build_bebop_user_data(
BebopOrderType::Single,
U256::ZERO, // 0 means fill entire order
&quote_data,
vec![(signature, 0)], // ETH_SIGN signature type
);
let bebop_component = ProtocolComponent {
id: String::from("bebop-rfq"),
protocol_system: String::from("rfq:bebop"),
static_attributes: HashMap::new(), // No static attributes needed
..Default::default()
};
let swap = Swap {
component: bebop_component,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: Some(user_data),
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let taker_address_bytes = Bytes::from_str(&taker_address.to_string()).unwrap();
let solution = Solution {
exact_out: false,
given_token: token_in,
given_amount: amount_in,
checked_token: token_out,
checked_amount: amount_out, // Expected output amount
// Use the order's taker address as sender and receiver
sender: taker_address_bytes.clone(),
receiver: taker_address_bytes,
swaps: vec![swap],
..Default::default()
};
let encoded_solution = encoder
.encode_solutions(vec![solution.clone()])
.unwrap()[0]
.clone();
let calldata = encode_tycho_router_call(
eth_chain().id,
encoded_solution,
&solution,
UserTransferType::TransferFrom,
eth(),
None,
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata);
write_calldata_to_file(
"test_single_encoding_strategy_bebop",
hex_calldata.as_str(),
);
}
#[test]
fn test_single_encoding_strategy_bebop_aggregate() {
// Use real mainnet aggregate order data from CLAUDE.md
// Transaction: https://etherscan.io/tx/0xec88410136c287280da87d0a37c1cb745f320406ca3ae55c678dec11996c1b1c
// For testing, use WETH directly to avoid delegatecall + native ETH complexities
let token_in = eth();
let token_out = usdc();
let amount_in = BigUint::from_str("9850000000000000").unwrap(); // 0.00985 WETH
let amount_out = BigUint::from_str("17969561").unwrap(); // 17.969561 USDC
// Create the exact aggregate order from mainnet
let expiry = 1746367285u64;
let taker_address =
Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
let receiver =
Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
// Set up makers
let maker_addresses = vec![
Address::from_str("0x67336Cec42645F55059EfF241Cb02eA5cC52fF86").unwrap(),
Address::from_str("0xBF19CbF0256f19f39A016a86Ff3551ecC6f2aAFE").unwrap(),
];
let maker_nonces = vec![U256::from(1746367197308u64), U256::from(15460096u64)];
// 2D arrays for tokens
// We use WETH as a taker token even when handling native ETH
let taker_tokens =
vec![vec![Address::from_slice(&weth())], vec![Address::from_slice(&weth())]];
let maker_tokens = vec![
vec![Address::from_slice(&token_out)],
vec![Address::from_slice(&token_out)],
];
// 2D arrays for amounts
let taker_amounts = vec![
vec![U256::from_str("5812106401997138").unwrap()],
vec![U256::from_str("4037893598002862").unwrap()],
];
let maker_amounts = vec![
vec![U256::from_str("10607211").unwrap()],
vec![U256::from_str("7362350").unwrap()],
];
// Commands and flags from the real transaction
let commands = hex!("00040004").to_vec();
let flags = U256::from_str(
"95769172144825922628485191511070792431742484643425438763224908097896054784000",
)
.unwrap();
// Encode Aggregate order - must match IBebopSettlement.Aggregate struct exactly
let quote_data = (
U256::from(expiry), // expiry as U256
taker_address,
maker_addresses,
maker_nonces, // Array of maker nonces
taker_tokens, // 2D array
maker_tokens,
taker_amounts, // 2D array
maker_amounts,
receiver,
commands,
flags,
)
.abi_encode();
// Use real signatures from the mainnet transaction
let sig1 = hex::decode("d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f351ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc71c").unwrap();
let sig2 = hex::decode("f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f051e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c41b").unwrap();
// Build user_data with ETH_SIGN flag (0) for both signatures
let signatures = vec![
(sig1, 0u8), // ETH_SIGN for maker 1
(sig2, 0u8), // ETH_SIGN for maker 2
];
let user_data = build_bebop_user_data(
BebopOrderType::Aggregate,
U256::from(0), // 0 means fill entire aggregate order
&quote_data,
signatures,
);
let bebop_component = ProtocolComponent {
id: String::from("bebop-rfq"),
protocol_system: String::from("rfq:bebop"),
static_attributes: HashMap::new(),
..Default::default()
};
let swap = Swap {
component: bebop_component,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: Some(user_data),
};
// Use TransferFrom for WETH token transfer
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let solution = Solution {
exact_out: false,
given_token: token_in.clone(),
given_amount: amount_in,
checked_token: token_out,
checked_amount: amount_out,
// Use ALICE as sender but order receiver as receiver
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), /* ALICE */
receiver: Bytes::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6")
.unwrap(), /* Order receiver */
swaps: vec![swap],
..Default::default()
};
let encoded_solution = encoder
.encode_solutions(vec![solution.clone()])
.unwrap()[0]
.clone();
let calldata = encode_tycho_router_call(
eth_chain().id,
encoded_solution,
&solution,
UserTransferType::None,
eth(),
None,
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata);
write_calldata_to_file(
"test_single_encoding_strategy_bebop_aggregate",
hex_calldata.as_str(),
);
}
}
}
}

View File

@@ -48,7 +48,7 @@ pub fn percentage_to_uint24(decimal: f64) -> U24 {
}
/// Gets the position of a token in a list of tokens.
pub fn get_token_position(tokens: Vec<Bytes>, token: Bytes) -> Result<U8, EncodingError> {
pub fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result<U8, EncodingError> {
let position = U8::from(
tokens
.iter()

View File

@@ -203,7 +203,7 @@ pub struct EncodingContext {
/// * `Transfer`: Transfer the token from the router into the protocol.
/// * `None`: No transfer is needed. Tokens are already in the pool.
#[repr(u8)]
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TransferType {
TransferFrom = 0,
Transfer = 1,

View File

@@ -16,7 +16,7 @@ pub trait StrategyEncoder {
///
/// # Returns
/// * `Result<EncodedSwaps, EncodingError>`
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError>;
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError>;
/// Retrieves the swap encoder for a specific protocol system.
///

View File

@@ -34,8 +34,8 @@ pub trait SwapEncoder: Sync + Send {
/// The encoded swap data as bytes, directly executable on the executor contract
fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
swap: &Swap,
encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError>;
/// Returns the address of the protocol-specific executor contract.