feat: Support new transfer logic in encoding
The transfer from the user into the router is supposed to happen in the router (we only support this in the executors for callback constrained protocols). This is necessary because of some security concerns that were found in the audit. This way we reduce the space of attack. - Refactored TransferOptimization not to handle TransferTypes anymore but just return bools. - Split get_transfer_type into get_transfers and get_in_between_transfer. Updates tests - Updated the strategies to use this - Updated function signatures to pass transfer_from and funds_receiver - Updated SwapEncoders to handle this - SplitSwapStrategy just assumes all tokens are sent to and from the router at all times Took 2 hours 46 minutes
This commit is contained in:
@@ -8,7 +8,6 @@ use crate::encoding::{
|
||||
constants::{CALLBACK_CONSTRAINED_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS},
|
||||
group_swaps::SwapGroup,
|
||||
},
|
||||
models::TransferType,
|
||||
};
|
||||
|
||||
/// A struct that defines how the tokens will be transferred into the given pool given the solution.
|
||||
@@ -16,7 +15,6 @@ use crate::encoding::{
|
||||
pub struct TransferOptimization {
|
||||
native_token: Bytes,
|
||||
wrapped_token: Bytes,
|
||||
permit2: bool,
|
||||
token_in_already_in_router: bool,
|
||||
router_address: Bytes,
|
||||
}
|
||||
@@ -25,69 +23,65 @@ impl TransferOptimization {
|
||||
pub fn new(
|
||||
native_token: Bytes,
|
||||
wrapped_token: Bytes,
|
||||
permit2: bool,
|
||||
token_in_already_in_router: bool,
|
||||
router_address: Bytes,
|
||||
) -> Self {
|
||||
TransferOptimization {
|
||||
native_token,
|
||||
wrapped_token,
|
||||
permit2,
|
||||
token_in_already_in_router,
|
||||
router_address,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the transfer method that should be used for the given swap and solution.
|
||||
pub fn get_transfer_type(
|
||||
&self,
|
||||
swap: SwapGroup,
|
||||
given_token: Bytes,
|
||||
wrap: bool,
|
||||
in_between_swap_optimization: bool,
|
||||
) -> TransferType {
|
||||
/// Returns the information about the necessary transfers.
|
||||
/// Returns (bool, String, bool) where:
|
||||
/// * bool: true if a transfer from the user is needed, false otherwise (it might use regular
|
||||
/// approvals or permit2)
|
||||
/// * String: the address to transfer from (if not needed it's the zero address)
|
||||
/// * bool: true if the tokens are already in the router and there only needs to be a transfer
|
||||
/// from the router into the pool
|
||||
pub fn get_transfers(&self, swap: SwapGroup, wrap: bool) -> (bool, String, bool) {
|
||||
let zero_address = Bytes::from([0u8; 20]).to_string();
|
||||
let in_transfer_required: bool =
|
||||
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
|
||||
|
||||
let is_first_swap = swap.token_in == given_token;
|
||||
|
||||
if swap.token_in == self.native_token {
|
||||
// Funds are already in router. All protocols currently take care of native transfers.
|
||||
TransferType::None
|
||||
(false, zero_address, false)
|
||||
} else if (swap.token_in == self.wrapped_token) && wrap {
|
||||
// Wrapping already happened in the router so we can just use a normal transfer.
|
||||
TransferType::TransferToProtocol
|
||||
} else if is_first_swap {
|
||||
if in_transfer_required {
|
||||
if self.token_in_already_in_router {
|
||||
// Transfer from router to pool.
|
||||
TransferType::TransferToProtocol
|
||||
} else if self.permit2 {
|
||||
// Transfer from swapper to pool using permit2.
|
||||
TransferType::TransferPermit2ToProtocol
|
||||
} else {
|
||||
// Transfer from swapper to pool.
|
||||
TransferType::TransferFromToProtocol
|
||||
}
|
||||
// in transfer is not necessary for these protocols. Only make a transfer if the
|
||||
// tokens are not already in the router
|
||||
} else if !self.token_in_already_in_router {
|
||||
if self.permit2 {
|
||||
// Transfer from swapper to router using permit2.
|
||||
TransferType::TransferPermit2ToRouter
|
||||
} else {
|
||||
// Transfer from swapper to router.
|
||||
TransferType::TransferFromToRouter
|
||||
}
|
||||
// Wrapping already happened in the router so, we just do a normal transfer.
|
||||
(false, zero_address, true)
|
||||
} else if in_transfer_required {
|
||||
if self.token_in_already_in_router {
|
||||
// Transfer from router to pool.
|
||||
(false, zero_address, true)
|
||||
} else {
|
||||
TransferType::None
|
||||
// Transfer from swapper to pool
|
||||
(true, swap.swaps[0].component.id.clone(), false)
|
||||
}
|
||||
// all other swaps
|
||||
} else if !in_transfer_required || in_between_swap_optimization {
|
||||
// funds should already be in the router or in the next pool
|
||||
TransferType::None
|
||||
// in transfer is not necessary for these protocols. Only make a transfer if the
|
||||
// tokens are not already in the router
|
||||
} else if !self.token_in_already_in_router {
|
||||
// Transfer from swapper to router using permit2.
|
||||
(true, self.router_address.to_string(), false)
|
||||
} else {
|
||||
TransferType::TransferToProtocol
|
||||
(false, zero_address, false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_in_between_transfer(
|
||||
&self,
|
||||
protocol_system: &str,
|
||||
in_between_swap_optimization: bool,
|
||||
) -> bool {
|
||||
let in_transfer_required: bool = IN_TRANSFER_REQUIRED_PROTOCOLS.contains(protocol_system);
|
||||
|
||||
if !in_transfer_required || in_between_swap_optimization {
|
||||
// funds should already be in the router or in the next pool
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,146 +149,68 @@ mod tests {
|
||||
Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_swap_transfer_from_permit2() {
|
||||
fn zero_address() -> String {
|
||||
Bytes::from([0u8; 20]).to_string()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
// WETH -(univ2)-> DAI we expect a transfer from the user to the protocol
|
||||
#[case(weth(), "uniswap_v2".to_string(), false, false, true, "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11", false)]
|
||||
// Native token swap. No transfer is needed
|
||||
#[case(eth(), "uniswap_v2".to_string(),false, false, false, zero_address(), false)]
|
||||
// ETH -(wrap)-> WETH -(univ2)-> DAI. Only a transfer from the router into the protocol is
|
||||
// needed
|
||||
#[case(weth(), "uniswap_v2".to_string(),true, false, false, zero_address(), true)]
|
||||
// USDC -(univ2)-> DAI and the tokens are already in the router. Only a transfer from the router
|
||||
// to the protocol is needed
|
||||
#[case(usdc(), "uniswap_v2".to_string(),false, true, false, zero_address(), true)]
|
||||
// USDC -(curve)-> DAI and the tokens are already in the router. No transfer is needed
|
||||
#[case(usdc(), "vm:curve".to_string(),false, true, false, zero_address(), false)]
|
||||
fn test_get_transfers(
|
||||
#[case] token_in: Bytes,
|
||||
#[case] protocol: String,
|
||||
#[case] wrap: bool,
|
||||
#[case] token_in_already_in_router: bool,
|
||||
#[case] expected_transfer_from: bool,
|
||||
#[case] expected_receiver: String,
|
||||
#[case] expected_transfer: bool,
|
||||
) {
|
||||
// The swap token is the same as the given token, which is not the native token
|
||||
let swap = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: weth(),
|
||||
let swaps = vec![Swap {
|
||||
component: ProtocolComponent {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: token_in.clone(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), true, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol);
|
||||
}];
|
||||
let swap =
|
||||
SwapGroup { protocol_system: protocol, token_in, token_out: dai(), split: 0f64, swaps };
|
||||
let optimization =
|
||||
TransferOptimization::new(eth(), weth(), token_in_already_in_router, router_address());
|
||||
let (transfer_from, receiver, transfer) = optimization.get_transfers(swap.clone(), wrap);
|
||||
assert_eq!(transfer_from, expected_transfer_from);
|
||||
assert_eq!(receiver, expected_receiver);
|
||||
assert_eq!(transfer, expected_transfer);
|
||||
}
|
||||
|
||||
#[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 = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: weth(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferFromToProtocol);
|
||||
}
|
||||
|
||||
#[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 = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: eth(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), 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 = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: weth(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||
}
|
||||
|
||||
#[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 = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: usdc(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_first_swap_funds_in_router() {
|
||||
// Not the first swap and the protocol requires the funds to be in the router (which they
|
||||
// already are, so the transfer type is None)
|
||||
let swap = SwapGroup {
|
||||
protocol_system: "vm:curve".to_string(),
|
||||
token_in: usdc(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_first_swap_in_between_swap_optimization() {
|
||||
// Not the first swap and the in between swaps are optimized. The funds should already be in
|
||||
// the next pool or in the router
|
||||
let swap = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: usdc(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true);
|
||||
assert_eq!(transfer_method, TransferType::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_swap_tokens_already_in_router_optimization() {
|
||||
// It is the first swap, tokens are already in the router and the protocol requires the
|
||||
// transfer in
|
||||
let swap = SwapGroup {
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
token_in: usdc(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_swap_tokens_already_in_router_no_transfer_needed_optimization() {
|
||||
// It is the first swap, tokens are already in the router and the protocol does not require
|
||||
// the transfer in
|
||||
let swap = SwapGroup {
|
||||
protocol_system: "vm:curve".to_string(),
|
||||
token_in: usdc(),
|
||||
token_out: dai(),
|
||||
split: 0f64,
|
||||
swaps: vec![],
|
||||
};
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address());
|
||||
let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false);
|
||||
assert_eq!(transfer_method, TransferType::None);
|
||||
#[rstest]
|
||||
// tokens need to be transferred into the pool
|
||||
#[case("uniswap_v2", false, true)]
|
||||
// tokens are already in the pool (optimization)
|
||||
#[case("uniswap_v2", true, false)]
|
||||
// tokens are already in the router and don't need a transfer
|
||||
#[case("vm:curve", false, false)]
|
||||
fn test_get_in_between_transfers(
|
||||
#[case] protocol: &str,
|
||||
#[case] in_between_swap_optimization: bool,
|
||||
#[case] expected_transfer: bool,
|
||||
) {
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, router_address());
|
||||
let transfer = optimization.get_in_between_transfer(protocol, in_between_swap_optimization);
|
||||
assert_eq!(transfer, expected_transfer);
|
||||
}
|
||||
|
||||
fn receiver() -> Bytes {
|
||||
@@ -319,7 +235,7 @@ mod tests {
|
||||
#[case] expected_receiver: Bytes,
|
||||
#[case] expected_optimization: bool,
|
||||
) {
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||
let optimization = TransferOptimization::new(eth(), weth(), false, router_address());
|
||||
|
||||
let next_swap = if protocol.is_none() {
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user