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:
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
"e_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
|
||||
"e_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
|
||||
"e_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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user