Files
tycho-execution/src/encoding/evm/strategy_encoder/transfer_optimizations.rs
TAMARA LIPOWSKI a301a1cef3 feat: (WIP) Support selection of transfer into router
- For protocols like Balancer and Curve, which expect funds to be in the router at the time of swap, we must support also transferring funds from the user into the router. Doing this in the router would mean we are dealing with transfers in two different places: in the router main methods and in the executors. To avoid this, we are now performing transfers just in the executors, and two transfer types have been added to support transfers into the router.

TODO:
- Add this for Balancer and Curve (only added for USV4 atm).
- TODO consider renaming TRANSFER_FROM and TRANSFER_PERMIT2 to include "pool" in the name
2025-04-23 12:31:41 +01:00

121 lines
4.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_method(
&self,
swap: Swap,
given_token: Bytes,
native_token: Bytes,
permit2: 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());
if (swap.token_in == given_token) && send_funds_to_pool {
if swap.token_in == native_token {
// Funds are already in router. Transfer from router to pool.
TransferType::Transfer
} else if permit2 {
// Transfer from swapper to pool using permit2.
TransferType::Permit2Transfer
} else {
// Transfer from swapper to pool.
TransferType::TransferFrom
}
} else if (swap.token_in == given_token) && funds_expected_in_router {
if swap.token_in == native_token {
// Funds already in router. Do nothing.
TransferType::None
} else 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())
}
#[test]
fn test_first_swap_transfer_from_permit2() {
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_method(swap.clone(), weth(), eth(), true);
assert_eq!(transfer_method, TransferType::Permit2Transfer);
}
#[test]
fn test_first_swap_transfer_from() {
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_method(swap.clone(), weth(), eth(), false);
assert_eq!(transfer_method, TransferType::TransferFrom);
}
#[test]
fn test_first_swap_transfer() {
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_method(swap.clone(), dai(), eth(), false);
assert_eq!(transfer_method, TransferType::Transfer);
}
}