- This needed to be in Curve so that the executor can also take care of transfers from the user into the tycho router, to avoid having transfer actions mixed between the router and executors
174 lines
6.0 KiB
Rust
174 lines
6.0 KiB
Rust
use tycho_common::Bytes;
|
|
|
|
use crate::encoding::{
|
|
evm::constants::{IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER},
|
|
models::{Swap, TransferType},
|
|
};
|
|
|
|
/// A trait that defines how the tokens will be transferred into the given pool given the solution.
|
|
pub trait TransferOptimization {
|
|
/// Returns the transfer method that should be used for the given swap and solution.
|
|
fn get_transfer_type(
|
|
&self,
|
|
swap: Swap,
|
|
given_token: Bytes,
|
|
native_token: Bytes,
|
|
wrapped_token: Bytes,
|
|
permit2: bool,
|
|
wrap: bool,
|
|
) -> TransferType {
|
|
let send_funds_to_pool: bool =
|
|
IN_TRANSFER_OPTIMIZABLE_PROTOCOLS.contains(&swap.component.protocol_system.as_str());
|
|
let funds_expected_in_router: bool =
|
|
PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER.contains(&swap.component.protocol_system.as_str());
|
|
|
|
// In the case of wrapping, check if the swap's token in is the wrapped token to
|
|
// determine if it's the first swap. Otherwise, compare to the given token.
|
|
let is_first_swap =
|
|
(swap.token_in == given_token) || ((swap.token_in == wrapped_token) && wrap);
|
|
|
|
if swap.token_in == native_token {
|
|
// Funds are already in router. All protocols currently take care of native transfers.
|
|
TransferType::None
|
|
} else if is_first_swap && send_funds_to_pool {
|
|
if permit2 {
|
|
// Transfer from swapper to pool using permit2.
|
|
TransferType::Permit2Transfer
|
|
} else {
|
|
// Transfer from swapper to pool.
|
|
TransferType::TransferFrom
|
|
}
|
|
} else if is_first_swap && funds_expected_in_router {
|
|
if permit2 {
|
|
// Transfer from swapper to router using permit2.
|
|
TransferType::Permit2TransferToRouter
|
|
} else {
|
|
// Transfer from swapper to router.
|
|
TransferType::TransferToRouter
|
|
}
|
|
} else {
|
|
TransferType::Transfer
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use alloy_primitives::hex;
|
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
|
|
|
use super::*;
|
|
|
|
struct MockStrategy {}
|
|
impl TransferOptimization for MockStrategy {}
|
|
|
|
fn weth() -> Bytes {
|
|
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
|
|
}
|
|
|
|
fn eth() -> Bytes {
|
|
Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec())
|
|
}
|
|
|
|
fn dai() -> Bytes {
|
|
Bytes::from(hex!("6b175474e89094c44da98b954eedeac495271d0f").to_vec())
|
|
}
|
|
|
|
fn usdc() -> Bytes {
|
|
Bytes::from(hex!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").to_vec())
|
|
}
|
|
|
|
#[test]
|
|
fn test_first_swap_transfer_from_permit2() {
|
|
// The swap token is the same as the given token, which is not the native token
|
|
let swap = Swap {
|
|
component: ProtocolComponent {
|
|
protocol_system: "uniswap_v2".to_string(),
|
|
..Default::default()
|
|
},
|
|
token_in: weth(),
|
|
token_out: dai(),
|
|
split: 0f64,
|
|
};
|
|
let strategy = MockStrategy {};
|
|
let transfer_method =
|
|
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false);
|
|
assert_eq!(transfer_method, TransferType::Permit2Transfer);
|
|
}
|
|
|
|
#[test]
|
|
fn test_first_swap_transfer_from() {
|
|
// The swap token is the same as the given token, which is not the native token
|
|
let swap = Swap {
|
|
component: ProtocolComponent {
|
|
protocol_system: "uniswap_v2".to_string(),
|
|
..Default::default()
|
|
},
|
|
token_in: weth(),
|
|
token_out: dai(),
|
|
split: 0f64,
|
|
};
|
|
let strategy = MockStrategy {};
|
|
let transfer_method =
|
|
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false);
|
|
assert_eq!(transfer_method, TransferType::TransferFrom);
|
|
}
|
|
|
|
#[test]
|
|
fn test_first_swap_native() {
|
|
// The swap token is the same as the given token, and it's the native token.
|
|
// No transfer action is needed.
|
|
let swap = Swap {
|
|
component: ProtocolComponent {
|
|
protocol_system: "uniswap_v2".to_string(),
|
|
..Default::default()
|
|
},
|
|
token_in: eth(),
|
|
token_out: dai(),
|
|
split: 0f64,
|
|
};
|
|
let strategy = MockStrategy {};
|
|
let transfer_method =
|
|
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false);
|
|
assert_eq!(transfer_method, TransferType::None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_first_swap_wrapped() {
|
|
// The swap token is NOT the same as the given token, but we are wrapping.
|
|
// Since the swap's token in is the wrapped token - this is the first swap.
|
|
let swap = Swap {
|
|
component: ProtocolComponent {
|
|
protocol_system: "uniswap_v2".to_string(),
|
|
..Default::default()
|
|
},
|
|
token_in: weth(),
|
|
token_out: dai(),
|
|
split: 0f64,
|
|
};
|
|
let strategy = MockStrategy {};
|
|
let transfer_method =
|
|
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true);
|
|
assert_eq!(transfer_method, TransferType::TransferFrom);
|
|
}
|
|
|
|
#[test]
|
|
fn test_not_first_swap() {
|
|
// The swap token is NOT the same as the given token, and we are NOT wrapping.
|
|
// Thus, this is not the first swap.
|
|
let swap = Swap {
|
|
component: ProtocolComponent {
|
|
protocol_system: "uniswap_v2".to_string(),
|
|
..Default::default()
|
|
},
|
|
token_in: usdc(),
|
|
token_out: dai(),
|
|
split: 0f64,
|
|
};
|
|
let strategy = MockStrategy {};
|
|
let transfer_method =
|
|
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false);
|
|
assert_eq!(transfer_method, TransferType::Transfer);
|
|
}
|
|
}
|