feat: sequential swap solution validation
- Basically reuse all methods of split swap validation, minus those specific to splits.
This commit is contained in:
committed by
Diana Carvalho
parent
5d586c25e3
commit
0d8150e22f
@@ -12,7 +12,10 @@ use crate::encoding::{
|
|||||||
evm::{
|
evm::{
|
||||||
approvals::permit2::Permit2,
|
approvals::permit2::Permit2,
|
||||||
constants::DEFAULT_ROUTERS_JSON,
|
constants::DEFAULT_ROUTERS_JSON,
|
||||||
strategy_encoder::{group_swaps::group_swaps, strategy_validators::SplitSwapValidator},
|
strategy_encoder::{
|
||||||
|
group_swaps::group_swaps,
|
||||||
|
strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
|
||||||
|
},
|
||||||
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
||||||
utils::{
|
utils::{
|
||||||
biguint_to_u256, bytes_to_address, encode_input, get_min_amount_for_solution,
|
biguint_to_u256, bytes_to_address, encode_input, get_min_amount_for_solution,
|
||||||
@@ -209,12 +212,17 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
/// * `native_address`: Address of the chain's native token
|
/// * `native_address`: Address of the chain's native token
|
||||||
/// * `wrapped_address`: Address of the chain's wrapped token
|
/// * `wrapped_address`: Address of the chain's wrapped token
|
||||||
/// * `router_address`: Address of the router to be used to execute swaps
|
/// * `router_address`: Address of the router to be used to execute swaps
|
||||||
|
/// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of
|
||||||
|
/// sequential swap solutions
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SequentialSwapStrategyEncoder {
|
pub struct SequentialSwapStrategyEncoder {
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
permit2: Option<Permit2>,
|
permit2: Option<Permit2>,
|
||||||
selector: String,
|
selector: String,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
native_address: Bytes,
|
||||||
|
wrapped_address: Bytes,
|
||||||
|
sequential_swap_validator: SequentialSwapValidator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SequentialSwapStrategyEncoder {
|
impl SequentialSwapStrategyEncoder {
|
||||||
@@ -234,7 +242,15 @@ impl SequentialSwapStrategyEncoder {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
Ok(Self { permit2, selector, swap_encoder_registry, router_address })
|
Ok(Self {
|
||||||
|
permit2,
|
||||||
|
selector,
|
||||||
|
swap_encoder_registry,
|
||||||
|
router_address,
|
||||||
|
native_address: chain.native_token()?,
|
||||||
|
wrapped_address: chain.wrapped_token()?,
|
||||||
|
sequential_swap_validator: SequentialSwapValidator,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes information necessary for performing a single swap against a given executor for
|
/// Encodes information necessary for performing a single swap against a given executor for
|
||||||
@@ -251,7 +267,17 @@ impl EVMStrategyEncoder for SequentialSwapStrategyEncoder {}
|
|||||||
|
|
||||||
impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
// TODO validate sequential swaps: check valid cycles, empty swaps, etc.
|
self.sequential_swap_validator
|
||||||
|
.validate_solution_min_amounts(&solution)?;
|
||||||
|
self.sequential_swap_validator
|
||||||
|
.validate_swap_path(
|
||||||
|
&solution.swaps,
|
||||||
|
&solution.given_token,
|
||||||
|
&solution.checked_token,
|
||||||
|
&solution.native_action,
|
||||||
|
&self.native_address,
|
||||||
|
&self.wrapped_address,
|
||||||
|
)?;
|
||||||
|
|
||||||
let min_amount_out = get_min_amount_for_solution(solution.clone());
|
let min_amount_out = get_min_amount_for_solution(solution.clone());
|
||||||
let grouped_swaps = group_swaps(solution.swaps);
|
let grouped_swaps = group_swaps(solution.swaps);
|
||||||
|
|||||||
@@ -7,92 +7,10 @@ use crate::encoding::{
|
|||||||
models::{NativeAction, Solution, Swap},
|
models::{NativeAction, Solution, Swap},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Validates whether a sequence of split swaps represents a valid solution.
|
pub trait SwapValidator {
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SplitSwapValidator;
|
|
||||||
|
|
||||||
impl SplitSwapValidator {
|
|
||||||
/// Raises an error if the split percentages are invalid.
|
|
||||||
///
|
|
||||||
/// Split percentages are considered valid if all the following conditions are met:
|
|
||||||
/// * Each split amount is < 1 (100%)
|
|
||||||
/// * There is exactly one 0% split for each token, and it's the last swap specified, signifying
|
|
||||||
/// to the router to send the remainder of the token to the designated protocol
|
|
||||||
/// * 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();
|
|
||||||
for swap in swaps {
|
|
||||||
if swap.split >= 1.0 {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"Split percentage must be less than 1 (100%), got {}",
|
|
||||||
swap.split
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
swaps_by_token
|
|
||||||
.entry(swap.token_in.clone())
|
|
||||||
.or_default()
|
|
||||||
.push(swap);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (token, token_swaps) in swaps_by_token {
|
|
||||||
// Single swaps don't need remainder handling
|
|
||||||
if token_swaps.len() == 1 {
|
|
||||||
if token_swaps[0].split != 0.0 {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"Single swap must have 0% split for token {:?}",
|
|
||||||
token
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut found_zero_split = false;
|
|
||||||
let mut total_percentage = 0.0;
|
|
||||||
for (i, swap) in token_swaps.iter().enumerate() {
|
|
||||||
match (swap.split == 0.0, i == token_swaps.len() - 1) {
|
|
||||||
(true, false) => {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"The 0% split for token {:?} must be the last swap",
|
|
||||||
token
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
(true, true) => found_zero_split = true,
|
|
||||||
(false, _) => {
|
|
||||||
if swap.split < 0.0 {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"All splits must be >= 0% for token {:?}",
|
|
||||||
token
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
total_percentage += swap.split;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found_zero_split {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"Token {:?} must have exactly one 0% split for remainder handling",
|
|
||||||
token
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Total must be <100% to leave room for remainder
|
|
||||||
if total_percentage >= 1.0 {
|
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
|
||||||
"Total of non-remainder splits for token {:?} must be <100%, got {}%",
|
|
||||||
token,
|
|
||||||
total_percentage * 100.0
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Raises an error if the solution does not have checked amount set or slippage with checked
|
/// Raises an error if the solution does not have checked amount set or slippage with checked
|
||||||
/// amount set.
|
/// amount set.
|
||||||
pub fn validate_solution_min_amounts(&self, solution: &Solution) -> Result<(), EncodingError> {
|
fn validate_solution_min_amounts(&self, solution: &Solution) -> Result<(), EncodingError> {
|
||||||
if solution.checked_amount.is_none() &&
|
if solution.checked_amount.is_none() &&
|
||||||
(solution.slippage.is_none() || solution.expected_amount.is_none())
|
(solution.slippage.is_none() || solution.expected_amount.is_none())
|
||||||
{
|
{
|
||||||
@@ -113,7 +31,7 @@ impl SplitSwapValidator {
|
|||||||
/// If the given token is the native token and the native action is WRAP, it will be converted
|
/// If the given token is the native token and the native action is WRAP, it will be converted
|
||||||
/// to the wrapped token before validating the swap path. The same principle applies for the
|
/// to the wrapped token before validating the swap path. The same principle applies for the
|
||||||
/// checked token and the UNWRAP action.
|
/// checked token and the UNWRAP action.
|
||||||
pub fn validate_swap_path(
|
fn validate_swap_path(
|
||||||
&self,
|
&self,
|
||||||
swaps: &[Swap],
|
swaps: &[Swap],
|
||||||
given_token: &Bytes,
|
given_token: &Bytes,
|
||||||
@@ -197,6 +115,98 @@ impl SplitSwapValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates whether a sequence of split swaps represents a valid solution.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SplitSwapValidator;
|
||||||
|
|
||||||
|
impl SwapValidator for SplitSwapValidator {}
|
||||||
|
|
||||||
|
impl SplitSwapValidator {
|
||||||
|
/// Raises an error if the split percentages are invalid.
|
||||||
|
///
|
||||||
|
/// Split percentages are considered valid if all the following conditions are met:
|
||||||
|
/// * Each split amount is < 1 (100%)
|
||||||
|
/// * There is exactly one 0% split for each token, and it's the last swap specified, signifying
|
||||||
|
/// to the router to send the remainder of the token to the designated protocol
|
||||||
|
/// * 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();
|
||||||
|
for swap in swaps {
|
||||||
|
if swap.split >= 1.0 {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"Split percentage must be less than 1 (100%), got {}",
|
||||||
|
swap.split
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
swaps_by_token
|
||||||
|
.entry(swap.token_in.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(swap);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (token, token_swaps) in swaps_by_token {
|
||||||
|
// Single swaps don't need remainder handling
|
||||||
|
if token_swaps.len() == 1 {
|
||||||
|
if token_swaps[0].split != 0.0 {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"Single swap must have 0% split for token {:?}",
|
||||||
|
token
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut found_zero_split = false;
|
||||||
|
let mut total_percentage = 0.0;
|
||||||
|
for (i, swap) in token_swaps.iter().enumerate() {
|
||||||
|
match (swap.split == 0.0, i == token_swaps.len() - 1) {
|
||||||
|
(true, false) => {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"The 0% split for token {:?} must be the last swap",
|
||||||
|
token
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
(true, true) => found_zero_split = true,
|
||||||
|
(false, _) => {
|
||||||
|
if swap.split < 0.0 {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"All splits must be >= 0% for token {:?}",
|
||||||
|
token
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
total_percentage += swap.split;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found_zero_split {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"Token {:?} must have exactly one 0% split for remainder handling",
|
||||||
|
token
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total must be <100% to leave room for remainder
|
||||||
|
if total_percentage >= 1.0 {
|
||||||
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
|
"Total of non-remainder splits for token {:?} must be <100%, got {}%",
|
||||||
|
token,
|
||||||
|
total_percentage * 100.0
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates whether a sequence of sequential swaps represents a valid solution.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SequentialSwapValidator;
|
||||||
|
|
||||||
|
impl SwapValidator for SequentialSwapValidator {}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|||||||
Reference in New Issue
Block a user