feat: Separate encoding swaps from encoding txs

This way the user is responsible for encoding the Tycho Router method inputs that are used as guardrails in execution.

Interface changes:
- Create EncodedSolution
- StrategyEncoder
  - don't need to know have permit2 or token_in_already_in_router as attributes anymore
  - encode_strategy returns EncodedSolution now (no method encoding done here now)
- TychoEncoder
  - add encode_solution() method. This is the recommended method for users
  - needs to have permit2, token_in_already_in_router and router_address as attributes
  - permit creation is made in the router now

Also:
- create encoding_utils.rs
- update all tests

Took 2 hours 42 minutes


Took 3 minutes

Took 13 minutes
This commit is contained in:
Diana Carvalho
2025-05-21 13:33:46 +01:00
parent 6a6955d31d
commit 4e8c6ddc8c
12 changed files with 869 additions and 420 deletions

View File

@@ -6,14 +6,18 @@ use tycho_common::Bytes;
use crate::encoding::{
errors::EncodingError,
evm::{
approvals::permit2::Permit2,
constants::{GROUPABLE_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS},
encoding_utils::encode_tycho_router_call,
group_swaps::group_swaps,
strategy_encoder::strategy_encoders::{
SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
},
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
},
models::{Chain, EncodingContext, NativeAction, Solution, Transaction, TransferType},
models::{
Chain, EncodedSolution, EncodingContext, NativeAction, Solution, Transaction, TransferType,
},
strategy_encoder::StrategyEncoder,
tycho_encoder::TychoEncoder,
};
@@ -26,6 +30,8 @@ use crate::encoding::{
/// * `split_swap_strategy`: Encoder for split swaps
/// * `native_address`: Address of the chain's native token
/// * `wrapped_address`: Address of the chain's wrapped native token
/// * `router_address`: Address of the Tycho router contract
/// * `token_in_already_in_router`: Indicates if the token in is already in the router at swap time
#[derive(Clone)]
pub struct TychoRouterEncoder {
single_swap_strategy: SingleSwapStrategyEncoder,
@@ -33,6 +39,9 @@ pub struct TychoRouterEncoder {
split_swap_strategy: SplitSwapStrategyEncoder,
native_address: Bytes,
wrapped_address: Bytes,
router_address: Bytes,
token_in_already_in_router: bool,
permit2: Option<Permit2>,
}
impl TychoRouterEncoder {
@@ -45,78 +54,111 @@ impl TychoRouterEncoder {
) -> Result<Self, EncodingError> {
let native_address = chain.native_token()?;
let wrapped_address = chain.wrapped_token()?;
let permit2 = if let Some(swapper_pk) = swapper_pk.clone() {
Some(Permit2::new(swapper_pk, chain.clone())?)
} else {
None
};
Ok(TychoRouterEncoder {
single_swap_strategy: SingleSwapStrategyEncoder::new(
chain.clone(),
swap_encoder_registry.clone(),
swapper_pk.clone(),
permit2.is_some(),
router_address.clone(),
token_in_already_in_router,
)?,
sequential_swap_strategy: SequentialSwapStrategyEncoder::new(
chain.clone(),
swap_encoder_registry.clone(),
swapper_pk.clone(),
permit2.is_some(),
router_address.clone(),
token_in_already_in_router,
)?,
split_swap_strategy: SplitSwapStrategyEncoder::new(
chain,
swap_encoder_registry,
None,
permit2.is_some(),
router_address.clone(),
token_in_already_in_router,
)?,
native_address,
wrapped_address,
router_address,
token_in_already_in_router,
permit2,
})
}
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)
.collect();
let mut encoded_solution = if (solution.swaps.len() == 1) ||
(protocols.len() == 1 &&
protocols
.iter()
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str())))
{
self.single_swap_strategy
.encode_strategy(solution.clone())?
} else if solution
.swaps
.iter()
.all(|swap| swap.split == 0.0)
{
self.sequential_swap_strategy
.encode_strategy(solution.clone())?
} else {
self.split_swap_strategy
.encode_strategy(solution.clone())?
};
if let Some(permit2) = self.permit2.clone() {
let (permit, signature) = permit2.get_permit(
&self.router_address,
&solution.sender,
&solution.given_token,
&solution.given_amount,
)?;
encoded_solution.permit = Some(permit);
encoded_solution.signature = Some(signature);
}
Ok(encoded_solution)
}
}
impl TychoEncoder for TychoRouterEncoder {
fn encode_solutions(
&self,
solutions: Vec<Solution>,
) -> Result<Vec<EncodedSolution>, EncodingError> {
let mut result: Vec<EncodedSolution> = Vec::new();
for solution in solutions.iter() {
let encoded_solution = self.encode_solution(solution)?;
result.push(encoded_solution);
}
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() {
self.validate_solution(solution)?;
let encoded_solution = self.encode_solution(solution)?;
let protocols: HashSet<String> = solution
.clone()
.swaps
.into_iter()
.map(|swap| swap.component.protocol_system)
.collect();
let transaction = encode_tycho_router_call(
encoded_solution,
solution,
self.token_in_already_in_router,
self.router_address.clone(),
self.native_address.clone(),
)?;
let (contract_interaction, target_address) = if (solution.swaps.len() == 1) ||
(protocols.len() == 1 &&
protocols
.iter()
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str())))
{
self.single_swap_strategy
.encode_strategy(solution.clone())?
} else if solution
.swaps
.iter()
.all(|swap| swap.split == 0.0)
{
self.sequential_swap_strategy
.encode_strategy(solution.clone())?
} else {
self.split_swap_strategy
.encode_strategy(solution.clone())?
};
let value = if solution.given_token == self.native_address {
solution.given_amount.clone()
} else {
BigUint::ZERO
};
transactions.push(Transaction {
value,
data: contract_interaction,
to: target_address,
});
transactions.push(transaction);
}
Ok(transactions)
}
@@ -300,6 +342,14 @@ impl TychoExecutorEncoder {
}
impl TychoEncoder for TychoExecutorEncoder {
fn encode_solutions(
&self,
_solutions: Vec<Solution>,
) -> 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