feat: Support in between swaps optimizations
--- don't change below this line --- ENG-4314 Took 31 seconds
This commit is contained in:
@@ -340,4 +340,100 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
|
||||
tychoRouter.exposedSequentialSwap(amountIn, pleEncode(swaps));
|
||||
assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99889294);
|
||||
}
|
||||
|
||||
function testUSV3USV2Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC and DAI using USV3 and USV2 pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
// Encoded solution generated using `test_uniswap_v3_uniswap_v2`
|
||||
(bool success,) = tychoRouterAddr.call(
|
||||
hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000bf00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb8004375dff511095cc5a197a54140a24efef3a416cbcdf9626bc03e24f779434178a73a0b4bad62ed000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010500"
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2554299052);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testUSV3USV3Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV3 pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV3)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
// Encoded solution generated using `test_uniswap_v3_uniswap_v3`
|
||||
(bool success,) = tychoRouterAddr.call(
|
||||
hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100692e234dae75c793f67a35089c9d99245e1c58470b2260fac5e5542a773aa44fbcfedf7c193bc2c599a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc299ac8ca7087fa4a2a1fb6357269965a2014abc35010000000000000000000000"
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2647438249);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testUSV3CurveIntegration() public {
|
||||
// Performs a sequential swap from WETH to USDT though WBTC using USV3 and Curve pools
|
||||
//
|
||||
// WETH ──(USV3)──> WBTC ───(USV3)──> USDT
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
// Encoded solution generated using `test_uniswap_v3_curve`
|
||||
(bool success,) = tychoRouterAddr.call(
|
||||
hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae460301000105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000"
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2650183330);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
|
||||
function testBalancerV2USV2Integration() public {
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using Balancer v2 and USV2 pools
|
||||
//
|
||||
// WETH ──(balancer)──> WBTC ───(USV2)──> USDC
|
||||
deal(WETH_ADDR, ALICE, 1 ether);
|
||||
uint256 balanceBefore = IERC20(USDT_ADDR).balanceOf(ALICE);
|
||||
|
||||
// Approve permit2
|
||||
vm.startPrank(ALICE);
|
||||
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||
// Encoded solution generated using `test_uniswap_v3_curve`
|
||||
(bool success,) = tychoRouterAddr.call(
|
||||
hex"e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c80072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e004375dff511095cc5a197a54140a24efef3a416010300525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
vm.stopPrank();
|
||||
|
||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||
|
||||
assertTrue(success, "Call Failed");
|
||||
assertEq(balanceAfter - balanceBefore, 2549391308);
|
||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ pub const PROTOCOL_SPECIFIC_CONFIG: &str =
|
||||
pub static GROUPABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("uniswap_v4");
|
||||
set.insert("balancer_v3");
|
||||
set.insert("vm:balancer_v3");
|
||||
set.insert("ekubo_v2");
|
||||
set
|
||||
});
|
||||
@@ -23,18 +23,21 @@ pub static IN_TRANSFER_OPTIMIZABLE_PROTOCOLS: LazyLock<HashSet<&'static str>> =
|
||||
LazyLock::new(|| {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("uniswap_v2");
|
||||
set.insert("sushiswap_v2");
|
||||
set.insert("pancakeswap_v2");
|
||||
set.insert("uniswap_v3");
|
||||
set.insert("pancakeswap_v3");
|
||||
set.insert("ekubo_v2");
|
||||
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>> =
|
||||
// These protocols do not support chained swaps from the same protocol. This is the case for uniswap
|
||||
// v3 because of the callback logic. The only way for this to work it would be to call the second
|
||||
// swap during the callback of the first swap. This is currently not supported.
|
||||
pub static PROTOCOLS_NOT_OPTIMIZED_CHAINED_SWAPS: LazyLock<HashSet<&'static str>> =
|
||||
LazyLock::new(|| {
|
||||
let mut set = HashSet::new();
|
||||
set.insert("vm:curve");
|
||||
set.insert("balancer_v2");
|
||||
// TODO remove uniswap_v4 when we add callback support for transfer optimizations
|
||||
set.insert("uniswap_v4");
|
||||
set.insert("uniswap_v3");
|
||||
set.insert("pancakeswap_v3");
|
||||
set
|
||||
});
|
||||
|
||||
@@ -255,7 +255,7 @@ mod tests {
|
||||
|
||||
let swap_weth_wbtc = Swap {
|
||||
component: ProtocolComponent {
|
||||
protocol_system: "balancer_v3".to_string(),
|
||||
protocol_system: "vm:balancer_v3".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: weth.clone(),
|
||||
@@ -264,7 +264,7 @@ mod tests {
|
||||
};
|
||||
let swap_wbtc_usdc = Swap {
|
||||
component: ProtocolComponent {
|
||||
protocol_system: "balancer_v3".to_string(),
|
||||
protocol_system: "vm:balancer_v3".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: wbtc.clone(),
|
||||
@@ -306,7 +306,7 @@ mod tests {
|
||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||
input_token: weth.clone(),
|
||||
output_token: usdc.clone(),
|
||||
protocol_system: "balancer_v3".to_string(),
|
||||
protocol_system: "vm:balancer_v3".to_string(),
|
||||
split: 0.5f64,
|
||||
},
|
||||
SwapGroup {
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::{
|
||||
approvals::permit2::Permit2,
|
||||
constants::{IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, PROTOCOLS_NOT_OPTIMIZED_CHAINED_SWAPS},
|
||||
group_swaps::group_swaps,
|
||||
strategy_encoder::{
|
||||
strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
|
||||
@@ -133,6 +134,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
||||
self.wrapped_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
wrap,
|
||||
false,
|
||||
);
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
@@ -289,6 +291,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||
}
|
||||
|
||||
let mut swaps = vec![];
|
||||
let mut next_in_between_swap_optimization = true;
|
||||
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
|
||||
let protocol = grouped_swap.protocol_system.clone();
|
||||
let swap_encoder = self
|
||||
@@ -300,12 +303,31 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||
))
|
||||
})?;
|
||||
|
||||
// if it is the last swap and there isn't an unwrap at the end, we can set the receiver
|
||||
// to the final user
|
||||
let swap_receiver = if i == grouped_swaps.len() - 1 && !unwrap {
|
||||
solution.receiver.clone()
|
||||
} else {
|
||||
let in_between_swap_optimization = next_in_between_swap_optimization;
|
||||
let next_swap = grouped_swaps.get(i + 1);
|
||||
// if there is a next swap
|
||||
let swap_receiver = if let Some(next) = next_swap {
|
||||
// if the protocol of the next swap supports transfer in optimization
|
||||
if IN_TRANSFER_OPTIMIZABLE_PROTOCOLS.contains(&next.protocol_system.as_str()) {
|
||||
// if the protocol does not allow for chained swaps, we can't optimize the
|
||||
// receiver of this swap nor the transfer in of the next swap
|
||||
if PROTOCOLS_NOT_OPTIMIZED_CHAINED_SWAPS
|
||||
.contains(&next.protocol_system.as_str()) &&
|
||||
PROTOCOLS_NOT_OPTIMIZED_CHAINED_SWAPS.contains(protocol.as_str())
|
||||
{
|
||||
next_in_between_swap_optimization = false;
|
||||
self.router_address.clone()
|
||||
} else {
|
||||
Bytes::from_str(&next.swaps[0].component.id.clone()).map_err(|_| {
|
||||
EncodingError::FatalError("Invalid component id".to_string())
|
||||
})?
|
||||
}
|
||||
} else {
|
||||
// the protocol of the next swap does not support transfer in optimization
|
||||
self.router_address.clone()
|
||||
}
|
||||
} else {
|
||||
solution.receiver.clone() // last swap - there is not next swap
|
||||
};
|
||||
|
||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||
@@ -317,6 +339,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||
self.wrapped_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
wrap,
|
||||
in_between_swap_optimization,
|
||||
);
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
@@ -553,6 +576,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
||||
self.wrapped_address.clone(),
|
||||
self.permit2.clone().is_some(),
|
||||
wrap,
|
||||
false,
|
||||
);
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
@@ -1148,12 +1172,15 @@ mod tests {
|
||||
println!("test_split_swap_strategy_encoder_complex_route: {}", _hex_calldata);
|
||||
}
|
||||
|
||||
mod sequential {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sequential_swap_strategy_encoder_complex_route() {
|
||||
// Note: This test does not assert anything. It is only used to obtain integration test
|
||||
// data for our router solidity test.
|
||||
//
|
||||
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
|
||||
//
|
||||
// WETH ───(USV2)──> WBTC ───(USV2)──> USDC
|
||||
|
||||
@@ -1216,7 +1243,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sequential_swap_strategy_encoder_no_permit2() {
|
||||
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
|
||||
//
|
||||
// WETH ───(USV2)──> WBTC ───(USV2)──> USDC
|
||||
|
||||
@@ -1277,7 +1304,7 @@ mod tests {
|
||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
|
||||
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token ou
|
||||
"00000000000000000000000000000000000000000000000000000000018f61ec", // min amount out
|
||||
"00000000000000000000000000000000000000000000000000000000018f61ec", /* min amount out */
|
||||
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||
@@ -1289,7 +1316,7 @@ mod tests {
|
||||
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||
"bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id
|
||||
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver (router)
|
||||
"004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool)
|
||||
"00", // zero to one
|
||||
"01", // transfer type
|
||||
// swap 2
|
||||
@@ -1299,13 +1326,312 @@ mod tests {
|
||||
"004375dff511095cc5a197a54140a24efef3a416", // component id
|
||||
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user)
|
||||
"01", // zero to one
|
||||
"00", // transfer type
|
||||
"05", // transfer type - None
|
||||
"000000000000000000000000000000000000000000000000", // padding
|
||||
));
|
||||
|
||||
assert_eq!(hex_calldata, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniswap_v3_uniswap_v2() {
|
||||
// Note: This test does not assert anything. It is only used to obtain integration test
|
||||
// data for our router solidity test.
|
||||
//
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV3 and USV2
|
||||
// pools
|
||||
//
|
||||
// WETH ───(USV3)──> WBTC ───(USV2)──> USDC
|
||||
|
||||
let weth = weth();
|
||||
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||
|
||||
let swap_weth_wbtc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||
protocol_system: "uniswap_v3".to_string(),
|
||||
static_attributes: {
|
||||
let mut attrs = HashMap::new();
|
||||
attrs.insert(
|
||||
"fee".to_string(),
|
||||
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||
);
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
token_in: weth.clone(),
|
||||
token_out: wbtc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_wbtc_usdc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: wbtc.clone(),
|
||||
token_out: usdc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_encoder_registry = get_swap_encoder_registry();
|
||||
let encoder = SequentialSwapStrategyEncoder::new(
|
||||
eth_chain(),
|
||||
swap_encoder_registry,
|
||||
None,
|
||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: weth,
|
||||
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||
checked_token: usdc,
|
||||
expected_amount: None,
|
||||
checked_amount: Some(BigUint::from_str("26173932").unwrap()),
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (calldata, _) = encoder
|
||||
.encode_strategy(solution)
|
||||
.unwrap();
|
||||
|
||||
let _hex_calldata = encode(&calldata);
|
||||
println!("test_uniswap_v3_uniswap_v2: {}", _hex_calldata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniswap_v3_uniswap_v3() {
|
||||
// Note: This test does not assert anything. It is only used to obtain integration test
|
||||
// data for our router solidity test.
|
||||
//
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using USV3 pools
|
||||
// There is no optimization between the two USV3 pools, this is currently not supported.
|
||||
//
|
||||
// WETH ───(USV3)──> WBTC ───(USV3)──> USDC
|
||||
|
||||
let weth = weth();
|
||||
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||
|
||||
let swap_weth_wbtc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||
protocol_system: "uniswap_v3".to_string(),
|
||||
static_attributes: {
|
||||
let mut attrs = HashMap::new();
|
||||
attrs.insert(
|
||||
"fee".to_string(),
|
||||
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||
);
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
token_in: weth.clone(),
|
||||
token_out: wbtc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_wbtc_usdc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35".to_string(),
|
||||
protocol_system: "uniswap_v3".to_string(),
|
||||
static_attributes: {
|
||||
let mut attrs = HashMap::new();
|
||||
attrs.insert(
|
||||
"fee".to_string(),
|
||||
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||
);
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
token_in: wbtc.clone(),
|
||||
token_out: usdc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_encoder_registry = get_swap_encoder_registry();
|
||||
let encoder = SequentialSwapStrategyEncoder::new(
|
||||
eth_chain(),
|
||||
swap_encoder_registry,
|
||||
None,
|
||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: weth,
|
||||
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||
checked_token: usdc,
|
||||
expected_amount: None,
|
||||
checked_amount: Some(BigUint::from_str("26173932").unwrap()),
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (calldata, _) = encoder
|
||||
.encode_strategy(solution)
|
||||
.unwrap();
|
||||
|
||||
let _hex_calldata = encode(&calldata);
|
||||
println!("test_uniswap_v3_uniswap_v3: {}", _hex_calldata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uniswap_v3_curve() {
|
||||
// Note: This test does not assert anything. It is only used to obtain integration test
|
||||
// data for our router solidity test.
|
||||
//
|
||||
// Performs a sequential swap from WETH to USDT though WBTC using USV3 and curve
|
||||
// pools
|
||||
//
|
||||
// WETH ───(USV3)──> WBTC ───(curve)──> USDT
|
||||
|
||||
let weth = weth();
|
||||
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||
let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap();
|
||||
|
||||
let swap_weth_wbtc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||
protocol_system: "uniswap_v3".to_string(),
|
||||
static_attributes: {
|
||||
let mut attrs = HashMap::new();
|
||||
attrs.insert(
|
||||
"fee".to_string(),
|
||||
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||
);
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
token_in: weth.clone(),
|
||||
token_out: wbtc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
|
||||
let swap_wbtc_usdt = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: String::from("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"),
|
||||
protocol_system: String::from("vm:curve"),
|
||||
static_attributes: {
|
||||
let mut attrs: HashMap<String, Bytes> = HashMap::new();
|
||||
attrs.insert(
|
||||
"factory".into(),
|
||||
Bytes::from(
|
||||
"0x0000000000000000000000000000000000000000"
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
),
|
||||
);
|
||||
attrs
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
token_in: wbtc.clone(),
|
||||
token_out: usdt.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_encoder_registry = get_swap_encoder_registry();
|
||||
let encoder = SequentialSwapStrategyEncoder::new(
|
||||
eth_chain(),
|
||||
swap_encoder_registry,
|
||||
None,
|
||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: weth,
|
||||
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||
checked_token: usdt,
|
||||
expected_amount: None,
|
||||
checked_amount: Some(BigUint::from_str("26173932").unwrap()),
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdt],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (calldata, _) = encoder
|
||||
.encode_strategy(solution)
|
||||
.unwrap();
|
||||
|
||||
let _hex_calldata = encode(&calldata);
|
||||
println!("test_uniswap_v3_curve: {}", _hex_calldata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_balancer_v2_uniswap_v2() {
|
||||
// Note: This test does not assert anything. It is only used to obtain integration test
|
||||
// data for our router solidity test.
|
||||
//
|
||||
// Performs a sequential swap from WETH to USDC though WBTC using balancer and USV2
|
||||
// pools
|
||||
//
|
||||
// WETH ───(balancer)──> WBTC ───(USV2)──> USDC
|
||||
|
||||
let weth = weth();
|
||||
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||
|
||||
let swap_weth_wbtc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e"
|
||||
.to_string(),
|
||||
protocol_system: "vm:balancer_v2".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: weth.clone(),
|
||||
token_out: wbtc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
|
||||
let swap_wbtc_usdc = Swap {
|
||||
component: ProtocolComponent {
|
||||
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||
protocol_system: "uniswap_v2".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
token_in: wbtc.clone(),
|
||||
token_out: usdc.clone(),
|
||||
split: 0f64,
|
||||
};
|
||||
let swap_encoder_registry = get_swap_encoder_registry();
|
||||
let encoder = SequentialSwapStrategyEncoder::new(
|
||||
eth_chain(),
|
||||
swap_encoder_registry,
|
||||
None,
|
||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: weth,
|
||||
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||
checked_token: usdc,
|
||||
expected_amount: None,
|
||||
checked_amount: Some(BigUint::from_str("26173932").unwrap()),
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (calldata, _) = encoder
|
||||
.encode_strategy(solution)
|
||||
.unwrap();
|
||||
|
||||
let _hex_calldata = encode(&calldata);
|
||||
println!("test_balancer_v2_uniswap_v2: {}", _hex_calldata);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_split_encoding_strategy_usv4() {
|
||||
// Performs a sequential swap from USDC to PEPE though ETH using two consecutive USV4 pools
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use tycho_common::Bytes;
|
||||
|
||||
use crate::encoding::{
|
||||
evm::constants::{IN_TRANSFER_OPTIMIZABLE_PROTOCOLS, PROTOCOLS_EXPECTING_FUNDS_IN_ROUTER},
|
||||
evm::constants::IN_TRANSFER_OPTIMIZABLE_PROTOCOLS,
|
||||
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.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn get_transfer_type(
|
||||
&self,
|
||||
swap: Swap,
|
||||
@@ -16,22 +17,22 @@ pub trait TransferOptimization {
|
||||
wrapped_token: Bytes,
|
||||
permit2: bool,
|
||||
wrap: bool,
|
||||
in_between_swap_optimization: bool,
|
||||
) -> TransferType {
|
||||
let send_funds_to_pool: bool =
|
||||
let in_transfer_optimizable: 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;
|
||||
|
||||
if swap.token_in == native_token {
|
||||
// Funds are already in router. All protocols currently take care of native transfers.
|
||||
TransferType::None
|
||||
} else if (swap.token_in == wrapped_token) && wrap {
|
||||
// Wrapping already happened in the router so we can just use a normal transfer.
|
||||
TransferType::TransferToProtocol
|
||||
} else if is_first_swap && send_funds_to_pool {
|
||||
// first swap
|
||||
} else if is_first_swap {
|
||||
if in_transfer_optimizable {
|
||||
if permit2 {
|
||||
// Transfer from swapper to pool using permit2.
|
||||
TransferType::TransferPermit2ToProtocol
|
||||
@@ -39,14 +40,17 @@ pub trait TransferOptimization {
|
||||
// Transfer from swapper to pool.
|
||||
TransferType::TransferFromToProtocol
|
||||
}
|
||||
} else if is_first_swap && funds_expected_in_router {
|
||||
if permit2 {
|
||||
} else if permit2 {
|
||||
// Transfer from swapper to router using permit2.
|
||||
TransferType::TransferPermit2ToRouter
|
||||
} else {
|
||||
// Transfer from swapper to router.
|
||||
TransferType::TransferFromToRouter
|
||||
}
|
||||
// all other swaps
|
||||
} else if !in_transfer_optimizable || in_between_swap_optimization {
|
||||
// funds should already be in the router or in the next pool
|
||||
TransferType::None
|
||||
} else {
|
||||
TransferType::TransferToProtocol
|
||||
}
|
||||
@@ -93,7 +97,7 @@ mod tests {
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method =
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false);
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol);
|
||||
}
|
||||
|
||||
@@ -111,7 +115,7 @@ mod tests {
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method =
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false);
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferFromToProtocol);
|
||||
}
|
||||
|
||||
@@ -130,7 +134,7 @@ mod tests {
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method =
|
||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false);
|
||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false, false);
|
||||
assert_eq!(transfer_method, TransferType::None);
|
||||
}
|
||||
|
||||
@@ -149,7 +153,7 @@ mod tests {
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method =
|
||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true);
|
||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true, false);
|
||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||
}
|
||||
|
||||
@@ -168,7 +172,45 @@ mod tests {
|
||||
};
|
||||
let strategy = MockStrategy {};
|
||||
let transfer_method =
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false);
|
||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, 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 = Swap {
|
||||
component: ProtocolComponent {
|
||||
protocol_system: "vm:curve".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, 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 = 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, true);
|
||||
assert_eq!(transfer_method, TransferType::None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user