chore: merge main

This commit is contained in:
TAMARA LIPOWSKI
2025-08-21 14:31:05 -04:00
45 changed files with 3514 additions and 854 deletions

View File

@@ -1,6 +1,7 @@
use std::{io, str::Utf8Error};
use thiserror::Error;
use tycho_common::simulation::errors::SimulationError;
/// Represents the outer-level, user-facing errors of the tycho-execution encoding package.
///
@@ -41,3 +42,15 @@ impl From<Utf8Error> for EncodingError {
EncodingError::FatalError(err.to_string())
}
}
impl From<SimulationError> for EncodingError {
fn from(err: SimulationError) -> Self {
match err {
SimulationError::FatalError(err_msg) => EncodingError::FatalError(err_msg),
SimulationError::InvalidInput(err_msg, ..) => EncodingError::InvalidInput(err_msg),
SimulationError::RecoverableError(error_msg) => {
EncodingError::RecoverableError(error_msg)
}
}
}
}

View File

@@ -31,10 +31,6 @@ pub struct Permit2 {
address: Address,
client: EVMProvider,
runtime_handle: Handle,
// Store the runtime to prevent it from being dropped before use.
// This is required since tycho-execution does not have a pre-existing runtime.
// However, if the library is used in a context where a runtime already exists, it is not
// necessary to store it.
#[allow(dead_code)]
runtime: Option<Arc<Runtime>>,
}

View File

@@ -23,10 +23,6 @@ use crate::encoding::{
pub struct ProtocolApprovalsManager {
client: EVMProvider,
runtime_handle: Handle,
// Store the runtime to prevent it from being dropped before use.
// This is required since tycho-execution does not have a pre-existing runtime.
// However, if the library is used in a context where a runtime already exists, it is not
// necessary to store it.
#[allow(dead_code)]
runtime: Option<Arc<Runtime>>,
}

View File

@@ -12,15 +12,15 @@ use crate::encoding::{evm::constants::GROUPABLE_PROTOCOLS, models::Swap};
/// * `swaps`: Vec<Swap>, the sequence of swaps to be executed as a group
/// * `split`: f64, the split percentage of the first swap in the group
#[derive(Clone, Debug)]
pub struct SwapGroup<'a> {
pub struct SwapGroup {
pub token_in: Bytes,
pub token_out: Bytes,
pub protocol_system: String,
pub swaps: Vec<Swap<'a>>,
pub swaps: Vec<Swap>,
pub split: f64,
}
impl<'a> PartialEq for SwapGroup<'a> {
impl PartialEq for SwapGroup {
fn eq(&self, other: &Self) -> bool {
self.token_in == other.token_in &&
self.token_out == other.token_out &&
@@ -34,7 +34,7 @@ impl<'a> PartialEq for SwapGroup<'a> {
///
/// 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<'a>(swaps: &'a Vec<Swap<'a>>) -> Vec<SwapGroup<'a>> {
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();
@@ -87,7 +87,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::SwapBuilder;
fn weth() -> Bytes {
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
@@ -105,41 +105,26 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: wbtc.clone(),
// This represents the remaining 50%, but to avoid any rounding errors we set this to
// 0 to signify "the remainder of the WETH value". It should still be very close to 50%
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_usdc_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_wbtc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
weth.clone(),
wbtc.clone(),
)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_usdc_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v2".to_string(), ..Default::default() },
usdc.clone(),
dai.clone(),
)
.build();
let swaps = vec![swap_weth_wbtc.clone(), swap_wbtc_usdc.clone(), swap_usdc_dai.clone()];
let grouped_swaps = group_swaps(&swaps);
@@ -179,52 +164,34 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_wbtc_weth = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.5f64,
user_data: None,
protocol_state: None,
};
let swap_weth_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
// This represents the remaining 50%, but to avoid any rounding errors we set this to
// 0 to signify "the remainder of the WETH value". It should still be very close to 50%
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_weth = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
wbtc.clone(),
weth.clone(),
)
.build();
let swap_weth_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
weth.clone(),
usdc.clone(),
)
.split(0.5f64)
.build();
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
weth.clone(),
dai.clone(),
)
.build();
// Split 0 represents the remaining 50%, but to avoid any rounding errors we set this to
// 0 to signify "the remainder of the WETH value". It should still be very close to 50%
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
dai.clone(),
usdc.clone(),
)
.build();
let swaps = vec![
swap_wbtc_weth.clone(),
swap_weth_usdc.clone(),
@@ -275,52 +242,38 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
let swap_weth_wbtc = SwapBuilder::new(
ProtocolComponent {
protocol_system: "vm:balancer_v3".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: wbtc.clone(),
split: 0.5f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
weth.clone(),
wbtc.clone(),
)
.split(0.5f64)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
ProtocolComponent {
protocol_system: "vm:balancer_v3".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
// This represents the remaining 50%, but to avoid any rounding errors we set this to
// 0 to signify "the remainder of the WETH value". It should still be very close to 50%
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4_hooks".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
weth.clone(),
dai.clone(),
)
.build();
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4_hooks".to_string(), ..Default::default() },
dai.clone(),
usdc.clone(),
)
.build();
let swaps = vec![
swap_weth_wbtc.clone(),

View File

@@ -5,5 +5,7 @@ mod encoding_utils;
mod group_swaps;
pub mod strategy_encoder;
mod swap_encoder;
#[cfg(feature = "test-utils")]
pub mod testing_utils;
pub mod tycho_encoders;
pub mod utils;

View File

@@ -237,10 +237,11 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
let grouped_swaps = group_swaps(&solution.swaps);
let mut wrap = false;
let (mut wrap, mut unwrap) = (false, false);
if let Some(action) = &solution.native_action {
if action == &NativeAction::Wrap {
wrap = true
match *action {
NativeAction::Wrap => wrap = true,
NativeAction::Unwrap => unwrap = true,
}
}
@@ -260,7 +261,7 @@ 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, next_swap)?;
.get_receiver(&solution.receiver, next_swap, unwrap)?;
next_in_between_swap_optimization_allowed = next_swap_optimization;
let transfer = self
@@ -544,7 +545,6 @@ mod tests {
};
use super::*;
use crate::encoding::models::Swap;
fn eth_chain() -> Chain {
Chain::Ethereum
@@ -565,8 +565,8 @@ mod tests {
}
mod single {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_single_swap_strategy_encoder() {
// Performs a single swap from WETH to DAI on a USV2 pool, with no grouping
@@ -575,18 +575,16 @@ mod tests {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
dai.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SingleSwapStrategyEncoder::new(
eth_chain(),
@@ -637,18 +635,16 @@ mod tests {
let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
dai.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SingleSwapStrategyEncoder::new(
eth_chain(),
@@ -698,6 +694,7 @@ mod tests {
mod sequential {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_sequential_swap_strategy_encoder_no_permit2() {
@@ -709,30 +706,26 @@ mod tests {
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
let swap_weth_wbtc = SwapBuilder::new(
ProtocolComponent {
id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: wbtc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
weth.clone(),
wbtc.clone(),
)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
ProtocolComponent {
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SequentialSwapStrategyEncoder::new(
eth_chain(),
@@ -790,6 +783,7 @@ mod tests {
mod split {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_split_input_cyclic_swap() {
@@ -805,8 +799,8 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
// USDC -> WETH (Pool 1) - 60% of input
let swap_usdc_weth_pool1 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_pool1 = SwapBuilder::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
@@ -820,16 +814,15 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0.6f64, // 60% of input
user_data: None,
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.split(0.6f64)
.build();
// USDC -> WETH (Pool 2) - 40% of input (remaining)
let swap_usdc_weth_pool2 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
* Pool 2 */
protocol_system: "uniswap_v3".to_string(),
@@ -843,16 +836,14 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None, // Remaining 40%
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.build();
// WETH -> USDC (Pool 2)
let swap_weth_usdc_pool2 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2
* Pool 2 */
protocol_system: "uniswap_v2".to_string(),
@@ -866,13 +857,10 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(
eth_chain(),
@@ -961,8 +949,8 @@ mod tests {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_usdc_weth_v2 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_v2 = SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2
protocol_system: "uniswap_v2".to_string(),
static_attributes: {
@@ -975,15 +963,13 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.build();
let swap_weth_usdc_v3_pool1 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_v3_pool1 = SwapBuilder::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
@@ -997,17 +983,16 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.6f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.split(0.6f64)
.build();
let swap_weth_usdc_v3_pool2 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_v3_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
* Pool 2 */
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
@@ -1019,12 +1004,10 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(

View File

@@ -197,7 +197,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::{Swap, SwapBuilder};
#[test]
fn test_validate_path_single_swap() {
@@ -205,18 +205,16 @@ mod tests {
let eth = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap();
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.build()];
let result = validator.validate_swap_path(&swaps, &weth, &dai, &None, &eth, &weth);
assert_eq!(result, Ok(()));
}
@@ -229,30 +227,27 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5f64)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai.clone(),
usdc.clone(),
)
.build(),
];
let result = validator.validate_swap_path(&swaps, &weth, &usdc, &None, &eth, &weth);
assert_eq!(result, Ok(()));
@@ -268,31 +263,28 @@ mod tests {
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let disconnected_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.5f64)
.build(),
// This swap is disconnected from the WETH->DAI path
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
wbtc.clone(),
usdc.clone(),
)
.build(),
];
let result =
validator.validate_swap_path(&disconnected_swaps, &weth, &usdc, &None, &eth, &weth);
@@ -310,30 +302,26 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let cyclic_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
usdc.clone(),
weth.clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
weth.clone(),
usdc.clone(),
)
.build(),
];
// Test with USDC as both given token and checked token
@@ -349,18 +337,17 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let unreachable_swaps = vec![Swap {
component: ProtocolComponent {
let unreachable_swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 1.0,
user_data: None,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.split(1.0)
.build()];
let result =
validator.validate_swap_path(&unreachable_swaps, &weth, &usdc, &None, &eth, &weth);
assert!(matches!(
@@ -389,18 +376,16 @@ mod tests {
let validator = SplitSwapValidator;
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.build()];
let result = validator.validate_split_percentages(&swaps);
assert_eq!(result, Ok(()));
}
@@ -413,42 +398,38 @@ mod tests {
// Valid case: Multiple swaps with proper splits (50%, 30%, remainder)
let valid_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.3,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.3)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool3".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0, // Remainder (20%)
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.build(),
];
assert!(validator
.validate_split_percentages(&valid_swaps)
@@ -462,30 +443,28 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_total_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.7,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.7)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.3,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.3)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_total_swaps),
@@ -500,30 +479,27 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_zero_position_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_zero_position_swaps),
@@ -538,42 +514,38 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_overflow_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.6,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.6)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool3".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_overflow_swaps),
@@ -588,18 +560,16 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
weth.clone(),
usdc.clone(),
)
.build()];
let result = validator.validate_swap_path(
&swaps,
@@ -619,18 +589,16 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
usdc.clone(),
weth.clone(),
)
.build()];
let result = validator.validate_swap_path(
&swaps,

View File

@@ -82,6 +82,7 @@ impl TransferOptimization {
&self,
solution_receiver: &Bytes,
next_swap: Option<&SwapGroup>,
unwrap: bool,
) -> Result<(Bytes, bool), EncodingError> {
if let Some(next) = next_swap {
// if the protocol of the next swap supports transfer in optimization
@@ -104,7 +105,11 @@ impl TransferOptimization {
}
} else {
// last swap - there is no next swap
Ok((solution_receiver.clone(), false))
if unwrap {
Ok((self.router_address.clone(), false))
} else {
Ok((solution_receiver.clone(), false))
}
}
}
}
@@ -116,7 +121,7 @@ mod tests {
use tycho_common::models::protocol::ProtocolComponent;
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::SwapBuilder;
fn weth() -> Bytes {
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
@@ -169,18 +174,16 @@ mod tests {
#[case] expected_transfer: TransferType,
) {
// The swap token is the same as the given token, which is not the native token
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
protocol_system: "uniswap_v2".to_string(),
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
..Default::default()
},
token_in: swap_token_in.clone(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
swap_token_in.clone(),
dai(),
)
.build()];
let swap = SwapGroup {
protocol_system: protocol,
token_in: swap_token_in,
@@ -204,16 +207,19 @@ mod tests {
}
#[rstest]
// there is no next swap -> receiver is the solution receiver
#[case(None, receiver(), false)]
// there is no next swap but there is an unwrap -> receiver is the router
#[case(None, true, router_address(), false)]
// there is no next swap and no unwrap -> receiver is the solution receiver
#[case(None, false, receiver(), false)]
// protocol of next swap supports transfer in optimization
#[case(Some("uniswap_v2"), component_id(), true)]
#[case(Some("uniswap_v2"), false, component_id(), true)]
// protocol of next swap supports transfer in optimization but is callback constrained
#[case(Some("uniswap_v3"), router_address(), false)]
#[case(Some("uniswap_v3"), false, router_address(), false)]
// protocol of next swap does not support transfer in optimization
#[case(Some("vm:curve"), router_address(), false)]
#[case(Some("vm:curve"), false, router_address(), false)]
fn test_get_receiver(
#[case] protocol: Option<&str>,
#[case] unwrap: bool,
#[case] expected_receiver: Bytes,
#[case] expected_optimization: bool,
) {
@@ -232,22 +238,20 @@ mod tests {
token_in: usdc(),
token_out: dai(),
split: 0f64,
swaps: vec![Swap {
component: ProtocolComponent {
swaps: vec![SwapBuilder::new(
ProtocolComponent {
protocol_system: protocol.unwrap().to_string(),
id: component_id().to_string(),
..Default::default()
},
token_in: usdc(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
}],
usdc(),
dai(),
)
.build()],
})
};
let result = optimization.get_receiver(&receiver(), next_swap.as_ref());
let result = optimization.get_receiver(&receiver(), next_swap.as_ref(), unwrap);
assert!(result.is_ok());
let (actual_receiver, optimization_flag) = result.unwrap();

View File

@@ -5,8 +5,9 @@ use tycho_common::models::Chain;
use crate::encoding::{
errors::EncodingError,
evm::swap_encoder::swap_encoders::{
BalancerV2SwapEncoder, BalancerV3SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder,
MaverickV2SwapEncoder, UniswapV2SwapEncoder, UniswapV3SwapEncoder, UniswapV4SwapEncoder,
BalancerV2SwapEncoder, BalancerV3SwapEncoder, BebopSwapEncoder, CurveSwapEncoder,
EkuboSwapEncoder, HashflowSwapEncoder, MaverickV2SwapEncoder, UniswapV2SwapEncoder,
UniswapV3SwapEncoder, UniswapV4SwapEncoder,
},
swap_encoder::SwapEncoder,
};
@@ -87,6 +88,14 @@ impl SwapEncoderBuilder {
self.chain,
self.config,
)?)),
"rfq:bebop" => {
Ok(Box::new(BebopSwapEncoder::new(self.executor_address, self.chain, self.config)?))
}
"rfq:hashflow" => Ok(Box::new(HashflowSwapEncoder::new(
self.executor_address,
self.chain,
self.config,
)?)),
_ => Err(EncodingError::FatalError(format!(
"Unknown protocol system: {}",
self.protocol_system

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
// This module is used in integration tests as well
use std::{any::Any, collections::HashMap};
use async_trait::async_trait;
use num_bigint::BigUint;
use tycho_common::{
dto::ProtocolStateDelta,
models::{protocol::GetAmountOutParams, token::Token},
simulation::{
errors::{SimulationError, TransitionError},
indicatively_priced::{IndicativelyPriced, SignedQuote},
protocol_sim::{Balances, GetAmountOutResult, ProtocolSim},
},
Bytes,
};
#[derive(Debug)]
pub struct MockRFQState {
pub quote_amount_out: BigUint,
pub quote_data: HashMap<String, Bytes>,
}
impl ProtocolSim for MockRFQState {
fn fee(&self) -> f64 {
panic!("MockRFQState does not implement fee")
}
fn spot_price(&self, _base: &Token, _quote: &Token) -> Result<f64, SimulationError> {
panic!("MockRFQState does not implement fee")
}
fn get_amount_out(
&self,
_amount_in: BigUint,
_token_in: &Token,
_token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
panic!("MockRFQState does not implement fee")
}
fn get_limits(
&self,
_sell_token: Bytes,
_buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError> {
panic!("MockRFQState does not implement fee")
}
fn delta_transition(
&mut self,
_delta: ProtocolStateDelta,
_tokens: &HashMap<Bytes, Token>,
_balances: &Balances,
) -> Result<(), TransitionError<String>> {
panic!("MockRFQState does not implement fee")
}
fn clone_box(&self) -> Box<dyn ProtocolSim> {
panic!("MockRFQState does not implement fee")
}
fn as_any(&self) -> &dyn Any {
panic!("MockRFQState does not implement fee")
}
fn as_any_mut(&mut self) -> &mut dyn Any {
panic!("MockRFQState does not implement fee")
}
fn eq(&self, _other: &dyn ProtocolSim) -> bool {
panic!("MockRFQState does not implement fee")
}
fn as_indicatively_priced(&self) -> Result<&dyn IndicativelyPriced, SimulationError> {
Ok(self)
}
}
#[async_trait]
impl IndicativelyPriced for MockRFQState {
async fn request_signed_quote(
&self,
params: GetAmountOutParams,
) -> Result<SignedQuote, SimulationError> {
Ok(SignedQuote {
base_token: params.token_in,
quote_token: params.token_out,
amount_in: params.amount_in,
amount_out: self.quote_amount_out.clone(),
quote_attributes: self.quote_data.clone(),
})
}
}

View File

@@ -406,7 +406,7 @@ mod tests {
use tycho_common::models::{protocol::ProtocolComponent, Chain};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::{Swap, SwapBuilder};
fn dai() -> Bytes {
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
@@ -435,48 +435,44 @@ mod tests {
// Fee and tick spacing information for this test is obtained by querying the
// USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e
// Using the poolKeys function with the first 25 bytes of the pool id
fn swap_usdc_eth_univ4() -> Swap<'static> {
fn swap_usdc_eth_univ4() -> Swap {
let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d"
.to_string(),
protocol_system: "uniswap_v4_hooks".to_string(),
static_attributes: static_attributes_usdc_eth,
..Default::default()
},
token_in: usdc().clone(),
token_out: eth().clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}
usdc().clone(),
eth().clone(),
)
.build()
}
fn swap_eth_pepe_univ4() -> Swap<'static> {
fn swap_eth_pepe_univ4() -> Swap {
let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9"
.to_string(),
protocol_system: "uniswap_v4_hooks".to_string(),
static_attributes: static_attributes_eth_pepe,
..Default::default()
},
token_in: eth().clone(),
token_out: pepe().clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}
eth().clone(),
pepe().clone(),
)
.build()
}
fn router_address() -> Bytes {
@@ -514,18 +510,16 @@ mod tests {
fn test_encode_router_calldata_single_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let eth_amount_in = BigUint::from(1000u32);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -580,31 +574,27 @@ mod tests {
fn test_encode_router_calldata_sequential_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let eth_amount_in = BigUint::from(1000u32);
let swap_weth_dai = Swap {
component: ProtocolComponent {
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let swap_dai_usdc = Swap {
component: ProtocolComponent {
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: usdc(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
usdc().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -674,18 +664,16 @@ mod tests {
#[test]
fn test_validate_passes_for_wrap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -704,18 +692,16 @@ mod tests {
#[test]
fn test_validate_fails_for_wrap_wrong_input() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -739,18 +725,16 @@ mod tests {
#[test]
fn test_validate_fails_for_wrap_wrong_first_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: eth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
eth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -794,18 +778,16 @@ mod tests {
#[test]
fn test_validate_passes_for_unwrap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
weth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -823,18 +805,16 @@ mod tests {
#[test]
fn test_validate_fails_for_unwrap_wrong_output() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
weth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -859,18 +839,16 @@ mod tests {
#[test]
fn test_validate_fails_for_unwrap_wrong_last_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: eth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
eth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -900,42 +878,36 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0.5f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
weth().clone(),
dai().clone(),
)
.build(),
];
let solution = Solution {
@@ -958,54 +930,46 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: usdc(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth().clone(),
usdc().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
usdc().clone(),
dai().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: wbtc(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai().clone(),
wbtc().clone(),
)
.build(),
];
let solution = Solution {
@@ -1035,42 +999,37 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth(),
dai(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0.5f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai(),
weth(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai(),
weth(),
)
.build(),
];
let solution = Solution {
@@ -1093,30 +1052,26 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
weth(),
dai(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai(),
weth(),
)
.build(),
];
let solution = Solution {
@@ -1150,7 +1105,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::{Solution, Swap};
use crate::encoding::models::Solution;
#[test]
fn test_executor_encoder_encode() {
@@ -1160,18 +1115,16 @@ mod tests {
let token_in = weth();
let token_out = dai();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
token_in.clone(),
token_out.clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -1222,17 +1175,16 @@ mod tests {
let token_in = weth();
let token_out = dai();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
token_in.clone(),
token_out.clone(),
)
.build();
let solution = Solution {
exact_out: false,

View File

@@ -78,6 +78,9 @@ pub fn get_static_attribute(swap: &Swap, attribute_name: &str) -> Result<Vec<u8>
.to_vec())
}
/// Returns the current Tokio runtime handle, or creates a new one if it doesn't exist.
/// It also returns the runtime to prevent it from being dropped before use.
/// This is required since tycho-execution does not have a pre-existing runtime.
pub fn get_runtime() -> Result<(Handle, Option<Arc<Runtime>>), EncodingError> {
match Handle::try_current() {
Ok(h) => Ok((h, None)),

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use clap::ValueEnum;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
@@ -35,7 +37,7 @@ pub enum UserTransferType {
/// Represents a solution containing details describing an order, and instructions for filling
/// the order.
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub struct Solution<'a> {
pub struct Solution {
/// Address of the sender.
pub sender: Bytes,
/// Address of the receiver.
@@ -55,7 +57,7 @@ pub struct Solution<'a> {
#[serde(with = "biguint_string")]
pub checked_amount: BigUint,
/// List of swaps to fulfill the solution.
pub swaps: Vec<Swap<'a>>,
pub swaps: Vec<Swap>,
/// If set, the corresponding native action will be executed.
pub native_action: Option<NativeAction>,
}
@@ -74,7 +76,7 @@ pub enum NativeAction {
/// Represents a swap operation to be performed on a pool.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Swap<'a> {
pub struct Swap {
/// Protocol component from tycho indexer
pub component: ProtocolComponent,
/// Token being input into the pool.
@@ -88,33 +90,106 @@ pub struct Swap<'a> {
pub user_data: Option<Bytes>,
/// Optional protocol state used to perform the swap.
#[serde(skip)]
pub protocol_state: Option<&'a dyn ProtocolSim>,
pub protocol_state: Option<Arc<dyn ProtocolSim>>,
/// Optional estimated amount in for this Swap. This is necessary for RFQ protocols. This value
/// is used to request the quote
pub estimated_amount_in: Option<BigUint>,
}
impl<'a> Swap<'a> {
impl Swap {
pub fn new<T: Into<ProtocolComponent>>(
component: T,
token_in: Bytes,
token_out: Bytes,
split: f64,
user_data: Option<Bytes>,
protocol_state: Option<&'a dyn ProtocolSim>,
protocol_state: Option<Arc<dyn ProtocolSim>>,
estimated_amount_in: Option<BigUint>,
) -> Self {
Self { component: component.into(), token_in, token_out, split, user_data, protocol_state }
Self {
component: component.into(),
token_in,
token_out,
split,
user_data,
protocol_state,
estimated_amount_in,
}
}
}
impl<'a> PartialEq for Swap<'a> {
impl PartialEq for Swap {
fn eq(&self, other: &Self) -> bool {
self.component == other.component &&
self.token_in == other.token_in &&
self.token_out == other.token_out &&
self.split == other.split &&
self.user_data == other.user_data
self.user_data == other.user_data &&
self.estimated_amount_in == other.estimated_amount_in
// Skip protocol_state comparison since trait objects don't implement PartialEq
}
}
pub struct SwapBuilder {
component: ProtocolComponent,
token_in: Bytes,
token_out: Bytes,
split: f64,
user_data: Option<Bytes>,
protocol_state: Option<Arc<dyn ProtocolSim>>,
estimated_amount_in: Option<BigUint>,
}
impl SwapBuilder {
pub fn new<T: Into<ProtocolComponent>>(
component: T,
token_in: Bytes,
token_out: Bytes,
) -> Self {
Self {
component: component.into(),
token_in,
token_out,
split: 0.0,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
}
}
pub fn split(mut self, split: f64) -> Self {
self.split = split;
self
}
pub fn user_data(mut self, user_data: Bytes) -> Self {
self.user_data = Some(user_data);
self
}
pub fn protocol_state(mut self, protocol_state: Arc<dyn ProtocolSim>) -> Self {
self.protocol_state = Some(protocol_state);
self
}
pub fn estimated_amount_in(mut self, estimated_amount_in: BigUint) -> Self {
self.estimated_amount_in = Some(estimated_amount_in);
self
}
pub fn build(self) -> Swap {
Swap {
component: self.component,
token_in: self.token_in,
token_out: self.token_out,
split: self.split,
user_data: self.user_data,
protocol_state: self.protocol_state,
estimated_amount_in: self.estimated_amount_in,
}
}
}
/// Represents a transaction to be executed.
///
/// # Fields
@@ -262,6 +337,7 @@ mod tests {
0.5,
user_data.clone(),
None,
None,
);
assert_eq!(swap.token_in, Bytes::from("0x12"));
assert_eq!(swap.token_out, Bytes::from("0x34"));