feat: Add historical_trade option to encoding

Adding it is necessary because sometimes we use the encoding crate for historical trades for testing. This is relevant for protocols that need token approvals like Balancer v2 and Curve. For this case, we prefer to set the approval flag to always be true than checking if it's necessary using and RPC. This RPC check would be wrong because it always uses the latest block

Took 30 minutes
This commit is contained in:
Diana Carvalho
2025-09-22 12:53:11 +01:00
parent a98e8d21cc
commit c51c6f52a5
6 changed files with 75 additions and 13 deletions

View File

@@ -23,6 +23,7 @@ pub struct TychoRouterEncoderBuilder {
executors_file_path: Option<String>,
router_address: Option<Bytes>,
swapper_pk: Option<String>,
historical_trade: bool,
}
impl Default for TychoRouterEncoderBuilder {
@@ -39,6 +40,7 @@ impl TychoRouterEncoderBuilder {
router_address: None,
swapper_pk: None,
user_transfer_type: None,
historical_trade: false,
}
}
pub fn chain(mut self, chain: Chain) -> Self {
@@ -65,6 +67,15 @@ impl TychoRouterEncoderBuilder {
self
}
/// Sets the `historical_trade` manually to true.
/// If set to true, it means that the encoded trade will be used in an historical block (as a
/// test) and not in the current one. This is relevant for checking token approvals in some
/// protocols (like Balancer v2).
pub fn historical_trade(mut self) -> Self {
self.historical_trade = true;
self
}
/// Sets the `swapper_pk` for the encoder. This is used to sign permit2 objects. This is only
/// needed if you intend to get the full calldata for the transfer. We do not recommend
/// using this option, you should sign and create the function calldata entirely on your
@@ -115,6 +126,7 @@ impl TychoRouterEncoderBuilder {
tycho_router_address,
user_transfer_type,
signer,
self.historical_trade,
)?))
} else {
Err(EncodingError::FatalError(

View File

@@ -58,11 +58,12 @@ use crate::encoding::{
/// funds.
///
/// # Parameters
/// - `chain_id`: Chain ID
/// - `encoded_solution`: The solution already encoded by Tycho.
/// - `solution`: The high-level solution including tokens, amounts, and receiver info.
/// - `token_in_already_in_router`: Whether the input token is already present in the router.
/// - `router_address`: The address of the Tycho Router contract.
/// - `user_transfer_type`: The desired transfer method.
/// - `native_address`: The address used to represent the native token
/// - `signer`: Optional signer for permit2
///
/// # Returns
/// A `Result<Transaction, EncodingError>` that either contains the full transaction data (to,

View File

@@ -26,12 +26,15 @@ use crate::encoding::{
/// * `function_signature`: String, the signature for the swap function in the router contract
/// * `router_address`: Address of the router to be used to execute swaps
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
/// one. This is relevant for checking token approvals in some protocols (like Balancer v2).
#[derive(Clone)]
pub struct SingleSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
function_signature: String,
router_address: Bytes,
transfer_optimization: TransferOptimization,
historical_trade: bool,
}
impl SingleSwapStrategyEncoder {
@@ -40,6 +43,7 @@ impl SingleSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
user_transfer_type: UserTransferType,
router_address: Bytes,
historical_trade: bool,
) -> Result<Self, EncodingError> {
let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
"singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
@@ -57,6 +61,7 @@ impl SingleSwapStrategyEncoder {
user_transfer_type,
router_address,
),
historical_trade,
})
}
@@ -119,6 +124,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
historical_trade: self.historical_trade,
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
@@ -171,6 +177,8 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
/// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of
/// sequential swap solutions
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
/// one. This is relevant for checking token approvals in some protocols (like Balancer v2).
#[derive(Clone)]
pub struct SequentialSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
@@ -180,6 +188,7 @@ pub struct SequentialSwapStrategyEncoder {
wrapped_address: Bytes,
sequential_swap_validator: SequentialSwapValidator,
transfer_optimization: TransferOptimization,
historical_trade: bool,
}
impl SequentialSwapStrategyEncoder {
@@ -188,6 +197,7 @@ impl SequentialSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
user_transfer_type: UserTransferType,
router_address: Bytes,
historical_trade: bool,
) -> Result<Self, EncodingError> {
let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
"sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
@@ -210,6 +220,7 @@ impl SequentialSwapStrategyEncoder {
user_transfer_type,
router_address,
),
historical_trade,
})
}
@@ -279,6 +290,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
historical_trade: self.historical_trade,
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
@@ -336,6 +348,8 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
/// solutions
/// * `router_address`: Address of the router to be used to execute swaps
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
/// one. This is relevant for checking token approvals in some protocols (like Balancer v2).
#[derive(Clone)]
pub struct SplitSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
@@ -345,6 +359,7 @@ pub struct SplitSwapStrategyEncoder {
split_swap_validator: SplitSwapValidator,
router_address: Bytes,
transfer_optimization: TransferOptimization,
historical_trade: bool,
}
impl SplitSwapStrategyEncoder {
@@ -353,6 +368,7 @@ impl SplitSwapStrategyEncoder {
swap_encoder_registry: SwapEncoderRegistry,
user_transfer_type: UserTransferType,
router_address: Bytes,
historical_trade: bool,
) -> Result<Self, EncodingError> {
let function_signature = if user_transfer_type == UserTransferType::TransferFromPermit2 {
"splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)"
@@ -374,6 +390,7 @@ impl SplitSwapStrategyEncoder {
user_transfer_type,
router_address,
),
historical_trade,
})
}
@@ -479,6 +496,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
historical_trade: self.historical_trade,
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
@@ -591,6 +609,7 @@ mod tests {
swap_encoder_registry,
UserTransferType::TransferFromPermit2,
router_address(),
false,
)
.unwrap();
let solution = Solution {
@@ -651,6 +670,7 @@ mod tests {
swap_encoder_registry,
UserTransferType::None,
router_address(),
false,
)
.unwrap();
let solution = Solution {
@@ -732,6 +752,7 @@ mod tests {
swap_encoder_registry,
UserTransferType::TransferFrom,
router_address(),
false,
)
.unwrap();
let solution = Solution {
@@ -867,6 +888,7 @@ mod tests {
swap_encoder_registry,
UserTransferType::TransferFromPermit2,
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
false,
)
.unwrap();
@@ -1015,6 +1037,7 @@ mod tests {
swap_encoder_registry,
UserTransferType::TransferFrom,
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
false,
)
.unwrap();

View File

@@ -280,19 +280,20 @@ impl SwapEncoder for BalancerV2SwapEncoder {
) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new()?;
let token = bytes_to_address(&swap.token_in)?;
let approval_needed: bool;
let mut approval_needed: bool = true;
if let Some(router_address) = &encoding_context.router_address {
if !encoding_context.historical_trade {
let tycho_router_address = bytes_to_address(router_address)?;
approval_needed = token_approvals_manager.approval_needed(
token,
tycho_router_address,
Address::from_str(&self.vault_address)
.map_err(|_| EncodingError::FatalError("Invalid vault address".to_string()))?,
Address::from_str(&self.vault_address).map_err(|_| {
EncodingError::FatalError("Invalid vault address".to_string())
})?,
)?;
} else {
approval_needed = true;
}
};
let component_id = AlloyBytes::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?;
@@ -1026,6 +1027,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = UniswapV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -1081,6 +1083,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = UniswapV3SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -1138,6 +1141,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::None,
historical_trade: true,
};
let encoder = BalancerV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -1207,6 +1211,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = UniswapV4SwapEncoder::new(
String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"),
@@ -1275,6 +1280,7 @@ mod tests {
// Token out is the same as the group token out
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = UniswapV4SwapEncoder::new(
@@ -1318,6 +1324,7 @@ mod tests {
group_token_in: usde_address.clone(),
group_token_out: wbtc_address.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
// Setup - First sequence: USDE -> USDT
@@ -1448,6 +1455,7 @@ mod tests {
exact_out: false,
router_address: Some(Bytes::default()),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = EkuboSwapEncoder::new(String::default(), Chain::Ethereum, None).unwrap();
@@ -1490,6 +1498,7 @@ mod tests {
exact_out: false,
router_address: Some(Bytes::default()),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let first_swap = SwapBuilder::new(
@@ -1687,6 +1696,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::None,
historical_trade: false,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1753,6 +1763,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::None,
historical_trade: false,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1820,6 +1831,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::None,
historical_trade: false,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1888,6 +1900,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = BalancerV3SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -1940,6 +1953,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = MaverickV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -2033,6 +2047,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = BebopSwapEncoder::new(
@@ -2107,6 +2122,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = HashflowSwapEncoder::new(
@@ -2200,6 +2216,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_type: TransferType::Transfer,
historical_trade: false,
};
let encoder = HashflowSwapEncoder::new(

View File

@@ -54,6 +54,7 @@ impl TychoRouterEncoder {
router_address: Bytes,
user_transfer_type: UserTransferType,
signer: Option<PrivateKeySigner>,
historical_trade: bool,
) -> Result<Self, EncodingError> {
let permit2 = if user_transfer_type == UserTransferType::TransferFromPermit2 {
Some(Permit2::new()?)
@@ -66,18 +67,21 @@ impl TychoRouterEncoder {
swap_encoder_registry.clone(),
user_transfer_type.clone(),
router_address.clone(),
historical_trade.clone(),
)?,
sequential_swap_strategy: SequentialSwapStrategyEncoder::new(
chain,
swap_encoder_registry.clone(),
user_transfer_type.clone(),
router_address.clone(),
historical_trade.clone(),
)?,
split_swap_strategy: SplitSwapStrategyEncoder::new(
chain,
swap_encoder_registry,
user_transfer_type.clone(),
router_address.clone(),
historical_trade,
)?,
router_address,
permit2,
@@ -331,6 +335,7 @@ impl TychoExecutorEncoder {
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_type: transfer,
historical_trade: false,
};
let mut grouped_protocol_data: Vec<Vec<u8>> = vec![];
let mut initial_protocol_data: Vec<u8> = vec![];
@@ -498,6 +503,7 @@ mod tests {
router_address(),
user_transfer_type,
None,
false,
)
.unwrap()
}

View File

@@ -273,6 +273,8 @@ impl PartialEq for PermitDetails {
/// * `group_token_in`: Token to be used as the input for the group swap.
/// * `group_token_out`: Token to be used as the output for the group swap.
/// * `transfer`: Type of transfer to be performed. See `TransferType` for more details.
/// * `historical_trade`: Whether the swap is to be done in the current block or in an historical
/// one. This is relevant for checking token approvals in some protocols (like Balancer v2).
#[derive(Clone, Debug)]
pub struct EncodingContext {
pub receiver: Bytes,
@@ -281,6 +283,7 @@ pub struct EncodingContext {
pub group_token_in: Bytes,
pub group_token_out: Bytes,
pub transfer_type: TransferType,
pub historical_trade: bool,
}
/// Represents the type of transfer to be performed into the pool.