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
This commit is contained in:
committed by
Diana Carvalho
parent
59a80dc392
commit
a301a1cef3
@@ -26,3 +26,14 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> =
|
||||
set.insert("uniswap_v3");
|
||||
set
|
||||
});
|
||||
|
||||
/// These protocols expect funds to be in the router at the time of swap.
|
||||
pub static PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER: LazyLock<HashSet<&'static str>> =
|
||||
LazyLock::new(|| {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("curve");
|
||||
set.insert("balancer_v2");
|
||||
// TODO remove uniswap_v4 when we add callback support for transfer optimizations
|
||||
set.insert("uniswap_v4");
|
||||
set
|
||||
});
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct SingleSwapStrategyEncoder {
|
||||
swap_encoder_registry: SwapEncoderRegistry,
|
||||
permit2: Option<Permit2>,
|
||||
selector: String,
|
||||
native_address: Bytes,
|
||||
router_address: Bytes,
|
||||
}
|
||||
|
||||
@@ -57,7 +58,13 @@ impl SingleSwapStrategyEncoder {
|
||||
"singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(),
|
||||
)
|
||||
};
|
||||
Ok(Self { permit2, selector, swap_encoder_registry, router_address })
|
||||
Ok(Self {
|
||||
permit2,
|
||||
selector,
|
||||
swap_encoder_registry,
|
||||
native_address: chain.native_token()?,
|
||||
router_address,
|
||||
})
|
||||
}
|
||||
|
||||
/// Encodes information necessary for performing a single hop against a given executor for
|
||||
@@ -117,6 +124,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
||||
let transfer_type = self.get_transfer_method(
|
||||
swap.clone(),
|
||||
solution.given_token.clone(),
|
||||
self.native_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
);
|
||||
|
||||
@@ -289,6 +297,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||
let transfer_type = self.get_transfer_method(
|
||||
swap.clone(),
|
||||
solution.given_token.clone(),
|
||||
self.native_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
);
|
||||
|
||||
@@ -515,6 +524,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
||||
let transfer_type = self.get_transfer_method(
|
||||
swap.clone(),
|
||||
solution.given_token.clone(),
|
||||
self.native_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
);
|
||||
|
||||
@@ -857,6 +867,7 @@ mod tests {
|
||||
"0000000000000000000000000000", // padding
|
||||
));
|
||||
let hex_calldata = encode(&calldata);
|
||||
println!("{}", hex_calldata);
|
||||
|
||||
assert_eq!(hex_calldata[..456], expected_input);
|
||||
assert_eq!(hex_calldata[1224..], expected_swap);
|
||||
@@ -1354,9 +1365,9 @@ mod tests {
|
||||
|
||||
let expected_swaps = String::from(concat!(
|
||||
// length of ple encoded swaps without padding
|
||||
"0000000000000000000000000000000000000000000000000000000000000078",
|
||||
"0000000000000000000000000000000000000000000000000000000000000079",
|
||||
// ple encoded swaps
|
||||
"0076", // Swap length
|
||||
"0077", // Swap length
|
||||
"00", // token in index
|
||||
"01", // token out index
|
||||
"000000", // split
|
||||
@@ -1366,6 +1377,7 @@ mod tests {
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in
|
||||
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
|
||||
"00", // zero2one
|
||||
"04", // transfer type (transfer to router)
|
||||
// First pool params
|
||||
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
||||
"000bb8", // fee
|
||||
@@ -1374,7 +1386,7 @@ mod tests {
|
||||
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
|
||||
"0061a8", // fee
|
||||
"0001f4", // tick spacing
|
||||
"0000000000000000" // padding
|
||||
"00000000000000" // padding
|
||||
));
|
||||
|
||||
let hex_calldata = encode(&calldata);
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
use tycho_common::Bytes;
|
||||
|
||||
use crate::encoding::{
|
||||
evm::constants::IN_TRANSFER_OPTIMIZABLE_PROTOCOLS,
|
||||
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.
|
||||
///
|
||||
/// If the swap is for the in token of the solution and the protocol supports transferring
|
||||
/// straight from the user, it will return `TransferType::Permit2Transfer` or
|
||||
/// `TransferType::TransferFrom`.
|
||||
fn get_transfer_method(&self, swap: Swap, given_token: Bytes, permit2: bool) -> TransferType {
|
||||
let optimize_in_transfer =
|
||||
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());
|
||||
if (swap.token_in == given_token) && optimize_in_transfer {
|
||||
if permit2 {
|
||||
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
|
||||
}
|
||||
@@ -41,6 +62,10 @@ mod tests {
|
||||
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
|
||||
}
|
||||
|
||||
fn eth() -> Bytes {
|
||||
Bytes::from(hex!("0000000000000000000000000000000000000000").to_vec())
|
||||
}
|
||||
|
||||
fn dai() -> Bytes {
|
||||
Bytes::from(hex!("6b175474e89094c44da98b954eedeac495271d0f").to_vec())
|
||||
}
|
||||
@@ -57,7 +82,7 @@ mod tests {
|
||||
split: 0f64,
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), true);
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), true);
|
||||
assert_eq!(transfer_method, TransferType::Permit2Transfer);
|
||||
}
|
||||
|
||||
@@ -73,7 +98,7 @@ mod tests {
|
||||
split: 0f64,
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), false);
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), weth(), eth(), false);
|
||||
assert_eq!(transfer_method, TransferType::TransferFrom);
|
||||
}
|
||||
|
||||
@@ -89,7 +114,7 @@ mod tests {
|
||||
split: 0f64,
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), false);
|
||||
let transfer_method = strategy.get_transfer_method(swap.clone(), dai(), eth(), false);
|
||||
assert_eq!(transfer_method, TransferType::Transfer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,13 @@ impl SwapEncoder for UniswapV4SwapEncoder {
|
||||
let pool_params =
|
||||
(token_out_address, pool_fee_u24, pool_tick_spacing_u24).abi_encode_packed();
|
||||
|
||||
let args = (group_token_in_address, group_token_out_address, zero_to_one, pool_params);
|
||||
let args = (
|
||||
group_token_in_address,
|
||||
group_token_out_address,
|
||||
zero_to_one,
|
||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||
pool_params,
|
||||
);
|
||||
|
||||
Ok(args.abi_encode_packed())
|
||||
}
|
||||
@@ -786,6 +792,8 @@ mod tests {
|
||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||
// zero for one
|
||||
"01",
|
||||
// transfer type
|
||||
"00",
|
||||
// pool params:
|
||||
// - intermediary token
|
||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||
@@ -942,6 +950,7 @@ mod tests {
|
||||
let combined_hex =
|
||||
format!("{}{}", encode(&initial_encoded_swap), encode(&second_encoded_swap));
|
||||
|
||||
println!("{}", combined_hex);
|
||||
assert_eq!(
|
||||
combined_hex,
|
||||
String::from(concat!(
|
||||
@@ -951,6 +960,8 @@ mod tests {
|
||||
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
||||
// zero for one
|
||||
"01",
|
||||
// transfer type
|
||||
"00",
|
||||
// pool params:
|
||||
// - intermediary token USDT
|
||||
"dac17f958d2ee523a2206206994597c13d831ec7",
|
||||
|
||||
@@ -1048,6 +1048,8 @@ mod tests {
|
||||
"6982508145454ce325ddbe47a25d4ec3d2311933",
|
||||
// zero for one
|
||||
"00",
|
||||
// transfer type
|
||||
"00",
|
||||
// first pool intermediary token (ETH)
|
||||
"0000000000000000000000000000000000000000",
|
||||
// fee
|
||||
|
||||
@@ -103,6 +103,8 @@ pub struct Transaction {
|
||||
/// * `Transfer`: Transfer the token from the router into the pool.
|
||||
/// * `TransferFrom`: Transfer the token from the swapper to the pool.
|
||||
/// * `Permit2Transfer`: Transfer the token from the sender to the pool using Permit2.
|
||||
/// * `TransferToRouter`: Transfer the token from the swapper to the router.
|
||||
/// * `Permit2TransferToRouter`: Transfer the token from the swapper to the router using Permit2.
|
||||
/// * `None`: No transfer is needed. Tokens are already in the pool.
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -110,7 +112,9 @@ pub enum TransferType {
|
||||
Transfer = 0,
|
||||
TransferFrom = 1,
|
||||
Permit2Transfer = 2,
|
||||
None = 3,
|
||||
TransferToRouter = 3,
|
||||
Permit2TransferToRouter = 4,
|
||||
None = 5,
|
||||
}
|
||||
|
||||
/// Represents necessary attributes for encoding an order.
|
||||
|
||||
Reference in New Issue
Block a user