feat: Add interacting_with to EncodedSolution

- Remove encode_full_calldata from the TychoEncoder trait
- Make the TychoExecutorEncoder use encode_solutions instead of encode_full_calldata
- Update tycho-encode.rs accordingly

Took 1 hour 3 minutes


Took 12 seconds
This commit is contained in:
Diana Carvalho
2025-05-21 18:00:19 +01:00
parent 08056c4a6c
commit facdf716bd
8 changed files with 174 additions and 299 deletions

View File

@@ -61,17 +61,17 @@ fn main() {
}; };
// Encode the solution // Encode the solution
let tx = encoder let encoded_solution = encoder
.encode_calldata(vec![solution.clone()]) .encode_solutions(vec![solution.clone()])
.expect("Failed to encode router calldata")[0] .expect("Failed to encode router calldata")[0]
.clone(); .clone();
println!(" ====== Simple swap WETH -> USDC ======"); println!(" ====== Simple swap WETH -> USDC ======");
println!( println!(
"The simple swap encoded transaction should be sent to address {:?} with the value of {:?} and the \ "The simple swap encoded solution should be sent to address {:?} and selector {:?} and the \
following encoded data: {:?}", following encoded data: {:?}",
tx.to, encoded_solution.interacting_with,
tx.value, encoded_solution.selector,
hex::encode(tx.data) hex::encode(encoded_solution.swaps)
); );
// ------------------- Encode a swap with multiple splits ------------------- // ------------------- Encode a swap with multiple splits -------------------
@@ -134,17 +134,17 @@ fn main() {
complex_solution.swaps = vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc]; complex_solution.swaps = vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc];
// Encode the solution // Encode the solution
let complex_tx = encoder let complex_encoded_solution = encoder
.encode_calldata(vec![complex_solution]) .encode_solutions(vec![complex_solution])
.expect("Failed to encode router calldata")[0] .expect("Failed to encode router calldata")[0]
.clone(); .clone();
println!(" ====== Complex split swap WETH -> USDC ======"); println!(" ====== Complex split swap WETH -> USDC ======");
println!( println!(
"The complex solution encoded transaction should be sent to address {:?} with the value of {:?} and the \ "The complex swaps encoded solution should be sent to address {:?} and selector {:?} and the \
following encoded data: {:?}", following encoded data: {:?}",
complex_tx.to, complex_encoded_solution.interacting_with,
complex_tx.value, complex_encoded_solution.selector,
hex::encode(complex_tx.data) hex::encode(complex_encoded_solution.swaps)
); );
} }

View File

@@ -1,5 +1,6 @@
use std::io::{self, Read}; use std::io::{self, Read};
use alloy_sol_types::SolValue;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use tycho_common::{hex_bytes::Bytes, models::Chain}; use tycho_common::{hex_bytes::Bytes, models::Chain};
use tycho_execution::encoding::{ use tycho_execution::encoding::{
@@ -99,11 +100,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.build()?, .build()?,
}; };
let transactions = encoder.encode_calldata(vec![solution])?; let encoded_solutions = encoder.encode_solutions(vec![solution])?;
let encoded = serde_json::json!({ let encoded = serde_json::json!({
"to": format!("0x{}", hex::encode(&transactions[0].to)), "swaps": format!("0x{}", hex::encode(&encoded_solutions[0].swaps)),
"value": format!("0x{}", hex::encode(transactions[0].value.to_bytes_be())), "interacting_with": format!("0x{}", hex::encode(&encoded_solutions[0].interacting_with)),
"data": format!("0x{}", hex::encode(&transactions[0].data)), "selector": format!("{}",&encoded_solutions[0].selector),
"n_tokens": format!("{}", &encoded_solutions[0].n_tokens),
"permit": encoded_solutions[0].permit
.as_ref()
.map(|permit| format!("0x{}", hex::encode(permit.abi_encode())))
.unwrap_or_else(String::new),
"signature": encoded_solutions[0].signature
.as_ref()
.map(|signature| format!("0x{}", hex::encode(signature.as_bytes())))
.unwrap_or_else(String::new),
}); });
// Output the encoded result as JSON to stdout // Output the encoded result as JSON to stdout
println!( println!(

View File

@@ -145,7 +145,7 @@ impl TychoExecutorEncoderBuilder {
if let Some(chain) = self.chain { if let Some(chain) = self.chain {
let swap_encoder_registry = let swap_encoder_registry =
SwapEncoderRegistry::new(self.executors_file_path.clone(), chain.clone())?; SwapEncoderRegistry::new(self.executors_file_path.clone(), chain.clone())?;
Ok(Box::new(TychoExecutorEncoder::new(chain, swap_encoder_registry)?)) Ok(Box::new(TychoExecutorEncoder::new(swap_encoder_registry)?))
} else { } else {
Err(EncodingError::FatalError( Err(EncodingError::FatalError(
"Please set the chain and strategy before building the encoder".to_string(), "Please set the chain and strategy before building the encoder".to_string(),

View File

@@ -87,7 +87,6 @@ pub fn encode_tycho_router_call(
encoded_solution: EncodedSolution, encoded_solution: EncodedSolution,
solution: &Solution, solution: &Solution,
token_in_already_in_router: bool, token_in_already_in_router: bool,
router_address: Bytes,
native_address: Bytes, native_address: Bytes,
) -> Result<Transaction, EncodingError> { ) -> Result<Transaction, EncodingError> {
let (mut unwrap, mut wrap) = (false, false); let (mut unwrap, mut wrap) = (false, false);
@@ -246,5 +245,5 @@ pub fn encode_tycho_router_call(
} else { } else {
BigUint::ZERO BigUint::ZERO
}; };
Ok(Transaction { to: router_address, value, data: contract_interaction }) Ok(Transaction { to: encoded_solution.interacting_with, value, data: contract_interaction })
} }

View File

@@ -135,6 +135,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
); );
Ok(EncodedSolution { Ok(EncodedSolution {
selector: self.selector.clone(), selector: self.selector.clone(),
interacting_with: self.router_address.clone(),
swaps: swap_data, swaps: swap_data,
permit: None, permit: None,
signature: None, signature: None,
@@ -289,6 +290,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
let encoded_swaps = ple_encode(swaps); let encoded_swaps = ple_encode(swaps);
Ok(EncodedSolution { Ok(EncodedSolution {
interacting_with: self.router_address.clone(),
selector: self.selector.clone(), selector: self.selector.clone(),
swaps: encoded_swaps, swaps: encoded_swaps,
permit: None, permit: None,
@@ -491,6 +493,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
tokens.len() tokens.len()
}; };
Ok(EncodedSolution { Ok(EncodedSolution {
interacting_with: self.router_address.clone(),
selector: self.selector.clone(), selector: self.selector.clone(),
swaps: encoded_swaps, swaps: encoded_swaps,
permit: None, permit: None,
@@ -624,15 +627,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [ let expected_input = [
"30ace1b1", // Function selector "30ace1b1", // Function selector
@@ -713,15 +710,9 @@ mod tests {
let encoded_solution = encoder let encoded_solution = encoder
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [ let expected_input = [
"5c4b639c", // Function selector "5c4b639c", // Function selector
@@ -801,15 +792,9 @@ mod tests {
let encoded_solution = encoder let encoded_solution = encoder
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, true, eth())
encoded_solution, .unwrap()
&solution, .data;
true,
router_address(),
eth(),
)
.unwrap()
.data;
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [ let expected_input = [
"5c4b639c", // Function selector "5c4b639c", // Function selector
@@ -891,15 +876,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_single_swap_strategy_encoder_wrap", hex_calldata.as_str()); write_calldata_to_file("test_single_swap_strategy_encoder_wrap", hex_calldata.as_str());
} }
@@ -951,15 +930,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file( write_calldata_to_file(
@@ -1033,15 +1006,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_sequential_swap_strategy_encoder", hex_calldata.as_str()); write_calldata_to_file("test_sequential_swap_strategy_encoder", hex_calldata.as_str());
@@ -1102,15 +1069,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
@@ -1235,15 +1196,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata); let hex_calldata = hex::encode(&calldata);
let expected_input = [ let expected_input = [
"51bcc7b6", // selector "51bcc7b6", // selector
@@ -1359,15 +1314,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_uniswap_v3_uniswap_v2", hex_calldata.as_str()); write_calldata_to_file("test_uniswap_v3_uniswap_v2", hex_calldata.as_str());
@@ -1450,15 +1399,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_uniswap_v3_uniswap_v3", hex_calldata.as_str()); write_calldata_to_file("test_uniswap_v3_uniswap_v3", hex_calldata.as_str());
@@ -1550,15 +1493,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_uniswap_v3_curve", hex_calldata.as_str()); write_calldata_to_file("test_uniswap_v3_curve", hex_calldata.as_str());
@@ -1626,15 +1563,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str()); write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str());
@@ -1787,15 +1718,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth,
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_multi_protocol", hex_calldata.as_str()); write_calldata_to_file("test_multi_protocol", hex_calldata.as_str());
@@ -1894,15 +1819,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_split_swap_strategy_encoder", hex_calldata.as_str()); write_calldata_to_file("test_split_swap_strategy_encoder", hex_calldata.as_str());
@@ -2015,15 +1934,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata); let hex_calldata = hex::encode(&calldata);
let expected_input = [ let expected_input = [
@@ -2185,15 +2098,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata); let hex_calldata = hex::encode(&calldata);
let expected_input = [ let expected_input = [
@@ -2318,15 +2225,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str()); write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str());
} }
@@ -2375,15 +2276,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str()); write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str());
} }
@@ -2446,10 +2341,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) .unwrap()
.unwrap() .data;
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file( write_calldata_to_file(
@@ -2520,10 +2414,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) .unwrap()
.unwrap() .data;
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file( write_calldata_to_file(
@@ -2612,10 +2505,9 @@ mod tests {
encoded_solution.permit = Some(permit); encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature); encoded_solution.signature = Some(signature);
let calldata = let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth)
encode_tycho_router_call(encoded_solution, &solution, false, router_address(), eth) .unwrap()
.unwrap() .data;
.data;
let expected_input = [ let expected_input = [
"30ace1b1", // Function selector (single swap) "30ace1b1", // Function selector (single swap)
@@ -2722,15 +2614,9 @@ mod tests {
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file("test_single_encoding_strategy_curve", hex_calldata.as_str()); write_calldata_to_file("test_single_encoding_strategy_curve", hex_calldata.as_str());
@@ -2793,15 +2679,9 @@ mod tests {
let encoded_solution = encoder let encoded_solution = encoder
.encode_strategy(solution.clone()) .encode_strategy(solution.clone())
.unwrap(); .unwrap();
let calldata = encode_tycho_router_call( let calldata = encode_tycho_router_call(encoded_solution, &solution, false, eth())
encoded_solution, .unwrap()
&solution, .data;
false,
router_address(),
eth(),
)
.unwrap()
.data;
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
write_calldata_to_file( write_calldata_to_file(

View File

@@ -1,6 +1,5 @@
use std::{collections::HashSet, str::FromStr}; use std::{collections::HashSet, str::FromStr};
use num_bigint::BigUint;
use tycho_common::Bytes; use tycho_common::Bytes;
use crate::encoding::{ use crate::encoding::{
@@ -40,6 +39,7 @@ pub struct TychoRouterEncoder {
native_address: Bytes, native_address: Bytes,
wrapped_address: Bytes, wrapped_address: Bytes,
router_address: Bytes, router_address: Bytes,
#[allow(dead_code)]
token_in_already_in_router: bool, token_in_already_in_router: bool,
permit2: Option<Permit2>, permit2: Option<Permit2>,
} }
@@ -130,6 +130,41 @@ impl TychoRouterEncoder {
} }
Ok(encoded_solution) Ok(encoded_solution)
} }
/// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router.
///
/// This method wraps around Tychos example encoding logic (see [`encode_tycho_router_call`])
/// and should only be used for **prototyping or development**.
///
/// # Warning
/// This implementation uses default logic to construct the outer calldata (e.g., for setting
/// `minAmountOut`). This might not be optimal or safe for production use.
///
/// To ensure correctness, **users should implement their own encoding pipeline** using
/// [`encode_solutions`].
///
/// # Returns
/// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler.
#[allow(dead_code)]
fn encode_full_calldata(
&self,
solutions: Vec<Solution>,
) -> Result<Vec<Transaction>, EncodingError> {
let mut transactions: Vec<Transaction> = Vec::new();
for solution in solutions.iter() {
let encoded_solution = self.encode_solution(solution)?;
let transaction = encode_tycho_router_call(
encoded_solution,
solution,
self.token_in_already_in_router,
self.native_address.clone(),
)?;
transactions.push(transaction);
}
Ok(transactions)
}
} }
impl TychoEncoder for TychoRouterEncoder { impl TychoEncoder for TychoRouterEncoder {
@@ -145,24 +180,6 @@ impl TychoEncoder for TychoRouterEncoder {
Ok(result) Ok(result)
} }
fn encode_calldata(&self, solutions: Vec<Solution>) -> Result<Vec<Transaction>, EncodingError> {
let mut transactions: Vec<Transaction> = Vec::new();
for solution in solutions.iter() {
let encoded_solution = self.encode_solution(solution)?;
let transaction = encode_tycho_router_call(
encoded_solution,
solution,
self.token_in_already_in_router,
self.router_address.clone(),
self.native_address.clone(),
)?;
transactions.push(transaction);
}
Ok(transactions)
}
/// Raises an `EncodingError` if the solution is not considered valid. /// Raises an `EncodingError` if the solution is not considered valid.
/// ///
/// A solution is considered valid if all the following conditions are met: /// A solution is considered valid if all the following conditions are met:
@@ -273,22 +290,17 @@ impl TychoEncoder for TychoRouterEncoder {
#[derive(Clone)] #[derive(Clone)]
pub struct TychoExecutorEncoder { pub struct TychoExecutorEncoder {
swap_encoder_registry: SwapEncoderRegistry, swap_encoder_registry: SwapEncoderRegistry,
native_address: Bytes,
} }
impl TychoExecutorEncoder { impl TychoExecutorEncoder {
pub fn new( pub fn new(swap_encoder_registry: SwapEncoderRegistry) -> Result<Self, EncodingError> {
chain: Chain, Ok(TychoExecutorEncoder { swap_encoder_registry })
swap_encoder_registry: SwapEncoderRegistry,
) -> Result<Self, EncodingError> {
let native_address = chain.native_token()?;
Ok(TychoExecutorEncoder { swap_encoder_registry, native_address })
} }
fn encode_executor_calldata( fn encode_executor_calldata(
&self, &self,
solution: Solution, solution: Solution,
) -> Result<(Vec<u8>, Bytes), EncodingError> { ) -> Result<EncodedSolution, EncodingError> {
let grouped_swaps = group_swaps(solution.clone().swaps); let grouped_swaps = group_swaps(solution.clone().swaps);
let number_of_groups = grouped_swaps.len(); let number_of_groups = grouped_swaps.len();
if number_of_groups > 1 { if number_of_groups > 1 {
@@ -337,37 +349,30 @@ impl TychoExecutorEncoder {
let executor_address = Bytes::from_str(swap_encoder.executor_address()) let executor_address = Bytes::from_str(swap_encoder.executor_address())
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?; .map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
Ok((grouped_protocol_data, executor_address)) Ok(EncodedSolution {
swaps: grouped_protocol_data,
interacting_with: executor_address,
permit: None,
signature: None,
selector: "".to_string(),
n_tokens: 0,
})
} }
} }
impl TychoEncoder for TychoExecutorEncoder { impl TychoEncoder for TychoExecutorEncoder {
fn encode_solutions( fn encode_solutions(
&self, &self,
_solutions: Vec<Solution>, solutions: Vec<Solution>,
) -> Result<Vec<EncodedSolution>, EncodingError> { ) -> Result<Vec<EncodedSolution>, EncodingError> {
Err(EncodingError::NotImplementedError(
"Encoding solutions for TychoExecutorEncoder is not implemented".to_string(),
))
}
fn encode_calldata(&self, solutions: Vec<Solution>) -> Result<Vec<Transaction>, EncodingError> {
let mut transactions: Vec<Transaction> = Vec::new();
let solution = solutions let solution = solutions
.first() .first()
.ok_or(EncodingError::FatalError("No solutions found".to_string()))?; .ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
self.validate_solution(solution)?; self.validate_solution(solution)?;
let (contract_interaction, target_address) = let encoded_solution = self.encode_executor_calldata(solution.clone())?;
self.encode_executor_calldata(solution.clone())?;
let value = if solution.given_token == self.native_address { Ok(vec![encoded_solution])
solution.given_amount.clone()
} else {
BigUint::ZERO
};
transactions.push(Transaction { value, data: contract_interaction, to: target_address });
Ok(transactions)
} }
/// Raises an `EncodingError` if the solution is not considered valid. /// Raises an `EncodingError` if the solution is not considered valid.
@@ -388,7 +393,7 @@ impl TychoEncoder for TychoExecutorEncoder {
mod tests { mod tests {
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use num_bigint::BigInt; use num_bigint::{BigInt, BigUint};
use tycho_common::models::{protocol::ProtocolComponent, Chain as TychoCommonChain}; use tycho_common::models::{protocol::ProtocolComponent, Chain as TychoCommonChain};
use super::*; use super::*;
@@ -508,7 +513,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let transactions = encoder.encode_calldata(vec![solution]); let transactions = encoder.encode_full_calldata(vec![solution]);
assert!(transactions.is_ok()); assert!(transactions.is_ok());
let transactions = transactions.unwrap(); let transactions = transactions.unwrap();
assert_eq!(transactions.len(), 1); assert_eq!(transactions.len(), 1);
@@ -536,7 +541,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let transactions = encoder.encode_calldata(vec![solution]); let transactions = encoder.encode_full_calldata(vec![solution]);
assert!(transactions.is_ok()); assert!(transactions.is_ok());
let transactions = transactions.unwrap(); let transactions = transactions.unwrap();
assert_eq!(transactions.len(), 1); assert_eq!(transactions.len(), 1);
@@ -582,7 +587,7 @@ mod tests {
..Default::default() ..Default::default()
}; };
let transactions = encoder.encode_calldata(vec![solution]); let transactions = encoder.encode_full_calldata(vec![solution]);
assert!(transactions.is_ok()); assert!(transactions.is_ok());
let transactions = transactions.unwrap(); let transactions = transactions.unwrap();
assert_eq!(transactions.len(), 1); assert_eq!(transactions.len(), 1);
@@ -1057,9 +1062,7 @@ mod tests {
#[test] #[test]
fn test_executor_encoder_encode() { fn test_executor_encoder_encode() {
let swap_encoder_registry = get_swap_encoder_registry(); let swap_encoder_registry = get_swap_encoder_registry();
let encoder = let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry)
.unwrap();
let token_in = weth(); let token_in = weth();
let token_out = dai(); let token_out = dai();
@@ -1088,15 +1091,15 @@ mod tests {
native_action: None, native_action: None,
}; };
let transactions = encoder let encoded_solutions = encoder
.encode_calldata(vec![solution]) .encode_solutions(vec![solution])
.unwrap(); .unwrap();
let transaction = transactions let encoded = encoded_solutions
.first() .first()
.expect("Expected at least one transaction"); .expect("Expected at least one encoded solution");
let hex_protocol_data = encode(&transaction.data); let hex_protocol_data = encode(&encoded.swaps);
assert_eq!( assert_eq!(
transaction.to, encoded.interacting_with,
Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap() Bytes::from_str("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").unwrap()
); );
assert_eq!( assert_eq!(
@@ -1119,9 +1122,7 @@ mod tests {
#[test] #[test]
fn test_executor_encoder_too_many_swaps() { fn test_executor_encoder_too_many_swaps() {
let swap_encoder_registry = get_swap_encoder_registry(); let swap_encoder_registry = get_swap_encoder_registry();
let encoder = let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry)
.unwrap();
let token_in = weth(); let token_in = weth();
let token_out = dai(); let token_out = dai();
@@ -1148,16 +1149,14 @@ mod tests {
native_action: None, native_action: None,
}; };
let result = encoder.encode_calldata(vec![solution]); let result = encoder.encode_solutions(vec![solution]);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn test_executor_encoder_grouped_swaps() { fn test_executor_encoder_grouped_swaps() {
let swap_encoder_registry = get_swap_encoder_registry(); let swap_encoder_registry = get_swap_encoder_registry();
let encoder = let encoder = TychoExecutorEncoder::new(swap_encoder_registry).unwrap();
TychoExecutorEncoder::new(TychoCommonChain::Ethereum.into(), swap_encoder_registry)
.unwrap();
let usdc = usdc(); let usdc = usdc();
let pepe = pepe(); let pepe = pepe();
@@ -1174,15 +1173,15 @@ mod tests {
..Default::default() ..Default::default()
}; };
let transactions = encoder let encoded_solutions = encoder
.encode_calldata(vec![solution]) .encode_solutions(vec![solution])
.unwrap(); .unwrap();
let transaction = transactions let encoded_solution = encoded_solutions
.first() .first()
.expect("Expected at least one transaction"); .expect("Expected at least one encoded solution");
let hex_protocol_data = encode(&transaction.data); let hex_protocol_data = encode(&encoded_solution.swaps);
assert_eq!( assert_eq!(
transaction.to, encoded_solution.interacting_with,
Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap() Bytes::from_str("0xf62849f9a0b5bf2913b396098f7c7019b51a820a").unwrap()
); );
assert_eq!( assert_eq!(

View File

@@ -94,12 +94,15 @@ pub struct Transaction {
/// ///
/// # Fields /// # Fields
/// * `swaps`: Encoded swaps to be executed. /// * `swaps`: Encoded swaps to be executed.
/// * `interacting_with`: Address of the contract to be called.
/// * `selector`: The selector of the function to be called. /// * `selector`: The selector of the function to be called.
/// * `n_tokens`: Number of tokens in the swap. /// * `n_tokens`: Number of tokens in the swap.
/// * `permit`: Optional permit for the swap (if permit2 is enabled). /// * `permit`: Optional permit for the swap (if permit2 is enabled).
/// * `signature`: Optional signature for the swap (if permit2 is enabled). /// * `signature`: Optional signature for the swap (if permit2 is enabled).
#[derive(Clone, Debug)]
pub struct EncodedSolution { pub struct EncodedSolution {
pub swaps: Vec<u8>, pub swaps: Vec<u8>,
pub interacting_with: Bytes,
pub selector: String, pub selector: String,
pub n_tokens: usize, pub n_tokens: usize,
pub permit: Option<PermitSingle>, pub permit: Option<PermitSingle>,

View File

@@ -1,6 +1,6 @@
use crate::encoding::{ use crate::encoding::{
errors::EncodingError, errors::EncodingError,
models::{EncodedSolution, Solution, Transaction}, models::{EncodedSolution, Solution},
}; };
/// A high-level interface for encoding solutions into Tycho-compatible transactions or raw call /// A high-level interface for encoding solutions into Tycho-compatible transactions or raw call
@@ -47,22 +47,6 @@ pub trait TychoEncoder {
solutions: Vec<Solution>, solutions: Vec<Solution>,
) -> Result<Vec<EncodedSolution>, EncodingError>; ) -> Result<Vec<EncodedSolution>, EncodingError>;
/// Encodes a list of [`Solution`]s directly into executable transactions for the Tycho router.
///
/// This method wraps around Tychos example encoding logic (see [`encode_tycho_router_call`])
/// and should only be used for **prototyping or development**.
///
/// # Warning
/// This implementation uses default logic to construct the outer calldata (e.g., for setting
/// `minAmountOut`). This might not be optimal or safe for production use.
///
/// To ensure correctness, **users should implement their own encoding pipeline** using
/// [`encode_solutions`].
///
/// # Returns
/// A vector of fully constructed [`Transaction`]s that can be submitted to a node or bundler.
fn encode_calldata(&self, solutions: Vec<Solution>) -> Result<Vec<Transaction>, EncodingError>;
/// Performs solution-level validation and sanity checks. /// Performs solution-level validation and sanity checks.
/// ///
/// This function can be used to verify whether a proposed solution is structurally sound and /// This function can be used to verify whether a proposed solution is structurally sound and