fix: Do not group split swaps

As far as we know, Uniswap V4 has no support for optimizing split swaps - only sequential swaps. We must make sure we don't accidentally group split swaps too - as this will likely revert due to mismatched input and output values.
This commit is contained in:
TAMARA LIPOWSKI
2025-02-17 16:50:59 -05:00
parent e94154bc2d
commit 957bf898f2

View File

@@ -453,9 +453,14 @@ impl UniswapV4StrategyEncoder {
let mut grouped_swaps: Vec<SwapGroup> = Vec::new();
let mut current_group: Option<SwapGroup> = None;
let mut last_swap_was_usv4 = false;
let mut last_swap_out_token = Bytes::default();
for swap in swaps {
let current_swap_is_usv4 = swap.component.protocol_system == "uniswap_v4";
if current_swap_is_usv4 && last_swap_was_usv4 {
// Split 0 can also mean that the swap is the remaining part of a branch of splits,
// so we need to check the last swap's out token as well
let is_split = swap.split != 0.0 || swap.token_in != last_swap_out_token;
if current_swap_is_usv4 && last_swap_was_usv4 && !is_split {
// Second or later USV4 pool in a sequence of USV4 pools. Merge to the current group
if let Some(group) = current_group.as_mut() {
group.swaps.push(swap.clone());
@@ -477,6 +482,7 @@ impl UniswapV4StrategyEncoder {
});
}
last_swap_was_usv4 = current_swap_is_usv4;
last_swap_out_token = swap.token_out.clone();
}
if let Some(group) = current_group.as_mut() {
grouped_swaps.push(group.clone());
@@ -1084,7 +1090,12 @@ mod tests {
}
#[test]
fn test_group_swaps() {
fn test_group_swaps_simple() {
// The first and second swaps can be grouped since there is no split, and they are
// both USV4.
//
// WETH ──(USV4)──> WBTC ───(USV4)──> USDC ───(USV2)──> DAI
//
// Set up a mock private key for signing
let private_key =
"0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string();
@@ -1154,6 +1165,100 @@ mod tests {
);
}
#[test]
fn test_group_swaps_complex_split() {
// There is a split in the solution, but it's possible to combine two of the USV4 splits.
// The WETH -> USDC swap cannot get grouped with anything, but the WETH -> DAI and
// DAI -> USDC swaps can be grouped.
//
// ┌──(USV4)──> USDC
// WBTC ──> (USV4)──> WETH ─┤
// └──(USV4)──> DAI ───(USV4)──> USDC
let private_key =
"0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string();
let weth = weth();
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_wbtc_weth = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: weth.clone(),
split: 0f64,
};
let swap_weth_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.5f64,
};
let swap_weth_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
// This represents the remaining 50%, but to avoid any rounding errors we set this to
// 0 to signify "the remainder of the WETH value". It should still be very close to 50%
split: 0f64,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
};
let swap_encoder_registry = get_swap_encoder_registry();
let encoder =
UniswapV4StrategyEncoder::new(private_key, eth_chain(), swap_encoder_registry).unwrap();
let grouped_swaps = encoder.group_swaps(vec![
swap_wbtc_weth.clone(),
swap_weth_usdc.clone(),
swap_weth_dai.clone(),
swap_dai_usdc.clone(),
]);
assert_eq!(
grouped_swaps,
vec![
SwapGroup {
swaps: vec![swap_wbtc_weth],
input_token: wbtc.clone(),
output_token: weth.clone(),
protocol_system: "uniswap_v4".to_string(),
split: 0f64,
},
SwapGroup {
swaps: vec![swap_weth_usdc],
input_token: weth.clone(),
output_token: usdc.clone(),
protocol_system: "uniswap_v4".to_string(),
split: 0.5f64,
},
SwapGroup {
swaps: vec![swap_weth_dai, swap_dai_usdc],
input_token: weth,
output_token: usdc,
protocol_system: "uniswap_v4".to_string(),
split: 0f64,
}
]
);
}
#[test]
fn test_usv4_encoding_strategy() {
// Performs a split swap from WETH to USDC though WBTC using two consecutive USV4 pools