feat: Bring back TransferType and simplify encoding logic

Took 1 hour 53 minutes
This commit is contained in:
Diana Carvalho
2025-05-16 16:59:35 +01:00
parent 9b59b8b434
commit 38748925b3
6 changed files with 230 additions and 309 deletions

View File

@@ -1,6 +1,6 @@
use std::{collections::HashSet, str::FromStr};
use alloy_primitives::{aliases::U24, Address, U256, U8};
use alloy_primitives::{aliases::U24, U256, U8};
use alloy_sol_types::SolValue;
use tycho_common::Bytes;
@@ -8,7 +8,6 @@ use crate::encoding::{
errors::EncodingError,
evm::{
approvals::permit2::Permit2,
constants::CALLBACK_CONSTRAINED_PROTOCOLS,
group_swaps::group_swaps,
strategy_encoder::{
strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
@@ -52,12 +51,11 @@ impl SingleSwapStrategyEncoder {
token_in_already_in_router: bool,
) -> Result<Self, EncodingError> {
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
(Some(Permit2::new(swapper_pk, chain.clone())?), "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
(Some(Permit2::new(swapper_pk, chain.clone())?), "singleSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
} else {
(
None,
"singleSwap(uint256,address,address,uint256,bool,bool,address,bool,address,bytes)"
.to_string(),
"singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(),
)
};
Ok(Self {
@@ -125,17 +123,16 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
let swap_receiver =
if !unwrap { solution.receiver.clone() } else { self.router_address.clone() };
let (mut transfer_from_needed, mut token_in_receiver, transfer_needed) = self
let transfer = self
.transfer_optimization
.get_transfers(grouped_swap.clone(), wrap);
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_from_needed,
transfer_needed,
transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
@@ -149,21 +146,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?,
grouped_protocol_data,
);
if CALLBACK_CONSTRAINED_PROTOCOLS.contains(
&grouped_swaps[0]
.protocol_system
.as_str(),
) {
// The first swap is from a callback constrained protocol. This means that the in
// transfer needs to happen at callback time and not before.
transfer_from_needed = false;
token_in_receiver = Address::ZERO.to_string();
}
let token_in_receiver = Address::from_str(&token_in_receiver).map_err(|_| {
EncodingError::FatalError(format!(
"Invalid funds receiver address: {token_in_receiver}"
))
})?;
let method_calldata = if let Some(permit2) = self.permit2.clone() {
let (permit, signature) = permit2.get_permit(
&self.router_address,
@@ -179,8 +162,6 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
wrap,
unwrap,
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
token_in_receiver,
permit,
signature.as_bytes().to_vec(),
swap_data,
@@ -195,8 +176,6 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
wrap,
unwrap,
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
token_in_receiver,
swap_data,
)
.abi_encode()
@@ -250,11 +229,11 @@ impl SequentialSwapStrategyEncoder {
token_in_already_in_router: bool,
) -> Result<Self, EncodingError> {
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
(Some(Permit2::new(swapper_pk, chain.clone())?), "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
(Some(Permit2::new(swapper_pk, chain.clone())?), "sequentialSwapPermit2(uint256,address,address,uint256,bool,bool,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
} else {
(
None,
"sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,address,bytes)"
"sequentialSwap(uint256,address,address,uint256,bool,bool,address,bytes)"
.to_string(),
)
};
@@ -309,9 +288,6 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
NativeAction::Unwrap => unwrap = true,
}
}
let (mut transfer_from_needed, mut token_in_receiver, transfer_needed) = self
.transfer_optimization
.get_transfers(grouped_swaps[0].clone(), wrap);
let mut swaps = vec![];
let mut next_in_between_swap_optimization_allowed = true;
@@ -332,20 +308,21 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
.get_receiver(solution.receiver.clone(), next_swap)?;
next_in_between_swap_optimization_allowed = next_swap_optimization;
let in_between_transfer = if i == 0 {
transfer_needed
} else {
self.transfer_optimization
.get_in_between_transfer(&protocol, in_between_swap_optimization_allowed)
};
let transfer = self
.transfer_optimization
.get_transfers(
grouped_swap.clone(),
solution.given_token.clone(),
wrap,
in_between_swap_optimization_allowed,
);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_from_needed: if i == 0 { transfer_from_needed } else { false },
transfer_needed: in_between_transfer,
transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
@@ -364,21 +341,6 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
swaps.push(swap_data);
}
if CALLBACK_CONSTRAINED_PROTOCOLS.contains(
&grouped_swaps[0]
.protocol_system
.as_str(),
) {
// The first swap is from a callback constrained protocol. This means that the in
// transfer needs to happen at callback time and not before.
transfer_from_needed = false;
token_in_receiver = Address::ZERO.to_string();
}
let token_in_receiver = Address::from_str(&token_in_receiver).map_err(|_| {
EncodingError::FatalError(format!(
"Invalid funds receiver address: {token_in_receiver}"
))
})?;
let encoded_swaps = ple_encode(swaps);
let method_calldata = if let Some(permit2) = self.permit2.clone() {
let (permit, signature) = permit2.get_permit(
@@ -395,8 +357,6 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
wrap,
unwrap,
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
token_in_receiver,
permit,
signature.as_bytes().to_vec(),
encoded_swaps,
@@ -411,8 +371,6 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
wrap,
unwrap,
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
token_in_receiver,
encoded_swaps,
)
.abi_encode()
@@ -466,11 +424,11 @@ impl SplitSwapStrategyEncoder {
token_in_already_in_router: bool,
) -> Result<Self, EncodingError> {
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
(Some(Permit2::new(swapper_pk, chain.clone())?), "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,bool,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
(Some(Permit2::new(swapper_pk, chain.clone())?), "splitSwapPermit2(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string())
} else {
(
None,
"splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)"
"splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bytes)"
.to_string(),
)
};
@@ -574,9 +532,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
tokens.push(solution.checked_token.clone());
}
let (transfer_from_needed, _token_in_receiver, _transfer_needed) = self
.transfer_optimization
.get_transfers(grouped_swaps[0].clone(), wrap);
let mut swaps = vec![];
for grouped_swap in grouped_swaps.iter() {
let protocol = grouped_swap.protocol_system.clone();
@@ -593,17 +548,16 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
} else {
self.router_address.clone()
};
let transfer_needed = self
let transfer = self
.transfer_optimization
.get_in_between_transfer(&protocol, false);
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
let encoding_context = EncodingContext {
receiver: swap_receiver.clone(),
exact_out: solution.exact_out,
router_address: Some(self.router_address.clone()),
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_from_needed: false,
transfer_needed,
transfer,
};
let mut grouped_protocol_data: Vec<u8> = vec![];
@@ -647,7 +601,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
unwrap,
U256::from(tokens_len),
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
permit,
signature.as_bytes().to_vec(),
encoded_swaps,
@@ -663,7 +616,6 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
unwrap,
U256::from(tokens_len),
bytes_to_address(&solution.receiver)?,
transfer_from_needed,
encoded_swaps,
)
.abi_encode()
@@ -792,7 +744,7 @@ mod tests {
.unwrap();
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [
"a93aabdf", // Function selector
"30ace1b1", // Function selector
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
@@ -800,8 +752,6 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000001", // transfer from
"000000000000000000000000a478c2975ab1ea89e8196811f51a7b7ade33eb11", // funds receiver
]
.join("");
@@ -817,13 +767,13 @@ mod tests {
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"00", // zero2one
"00", // transfer false
"00", // transfer type TransferFrom
"0000000000000000000000000000", // padding
));
let hex_calldata = encode(&calldata);
assert_eq!(hex_calldata[..584], expected_input);
assert_eq!(hex_calldata[1352..], expected_swap);
assert_eq!(hex_calldata[..456], expected_input);
assert_eq!(hex_calldata[1224..], expected_swap);
if expected_amount.is_some() & slippage.is_some() & checked_amount.is_none() {
// only write to file for 1 test case
write_calldata_to_file(
@@ -884,7 +834,7 @@ mod tests {
.unwrap();
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [
"cc60c623", // Function selector
"20144a07", // Function selector
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
@@ -892,9 +842,7 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000001", // transfer from
"000000000000000000000000a478c2975ab1ea89e8196811f51a7b7ade33eb11", // funds receiver
"0000000000000000000000000000000000000000000000000000000000000140", // offset of swap bytes
"0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes
"0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding
// Swap data
@@ -903,7 +851,7 @@ mod tests {
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"00", // zero2one
"00", // transfer false
"00", // transfer type TransferFrom
"0000000000000000000000000000", // padding
]
.join("");
@@ -968,7 +916,7 @@ mod tests {
.unwrap();
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
let expected_input = [
"cc60c623", // Function selector
"20144a07", // Function selector
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
@@ -976,9 +924,7 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000000", // transfer from
"0000000000000000000000000000000000000000000000000000000000000000", // funds receiver
"0000000000000000000000000000000000000000000000000000000000000140", // offset of swap bytes
"0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes
"0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding
// Swap data
@@ -987,7 +933,7 @@ mod tests {
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"00", // zero2one
"01", // transfer true
"01", // transfer type Transfer
"0000000000000000000000000000", // padding
]
.join("");
@@ -1240,7 +1186,7 @@ mod tests {
let hex_calldata = encode(&calldata);
let expected = String::from(concat!(
"59e3efbb", /* function selector */
"e8a980d7", /* function selector */
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token ou
@@ -1248,9 +1194,7 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000001", /* transfer from */
"000000000000000000000000bb2b8038a1640196fbe3e38816f3e67cba72d940", /* funds receiver */
"0000000000000000000000000000000000000000000000000000000000000140", /* length ple
"0000000000000000000000000000000000000000000000000000000000000100", /* length ple
* encode */
"00000000000000000000000000000000000000000000000000000000000000a8",
// swap 1
@@ -1260,7 +1204,7 @@ mod tests {
"bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id
"004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool)
"00", // zero to one
"00", // transfer false
"00", // transfer type TransferFrom
// swap 2
"0052", // swap length
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
@@ -1268,7 +1212,7 @@ mod tests {
"004375dff511095cc5a197a54140a24efef3a416", // component id
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user)
"01", // zero to one
"00", // transfer false
"02", // transfer type None
"000000000000000000000000000000000000000000000000", // padding
));
@@ -1366,7 +1310,7 @@ mod tests {
.unwrap();
let hex_calldata = hex::encode(&calldata);
let expected_input = [
"740bae0c", // selector
"51bcc7b6", // selector
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
@@ -1374,14 +1318,12 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap action
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000000", // transfer from
"0000000000000000000000000000000000000000000000000000000000000000", // funds receiver
]
.join("");
let expected_swaps = [
"00000000000000000000000000000000000000000000000000000000000000d8", // length of ple encoded swaps without padding
"006a", // ple encoded swaps
"00000000000000000000000000000000000000000000000000000000000000d6", // length of ple encoded swaps without padding
"0069", // ple encoded swaps
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
@@ -1389,9 +1331,8 @@ mod tests {
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
"01", // zero2one
"01", // transfer from true
"00", // transfer false
"006a", // ple encoded swaps
"00", // transfer type TransferFrom
"0069", // ple encoded swaps
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
@@ -1399,14 +1340,13 @@ mod tests {
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
"00", // zero2one
"00", // transfer from false
"01", // transfer true
"0000000000000000", // padding
"01", // transfer type Transfer
"00000000000000000000", // padding
]
.join("");
assert_eq!(hex_calldata[..584], expected_input);
assert_eq!(hex_calldata[1352..], expected_swaps);
assert_eq!(hex_calldata[..456], expected_input);
assert_eq!(hex_calldata[1224..], expected_swaps);
write_calldata_to_file("test_sequential_strategy_cyclic_swap", hex_calldata.as_str());
}
@@ -2083,7 +2023,7 @@ mod tests {
let hex_calldata = hex::encode(&calldata);
let expected_input = [
"308f3ce0", // selector
"7c553846", // selector
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
@@ -2092,12 +2032,11 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000001", // transfer from
]
.join("");
let expected_swaps = [
"000000000000000000000000000000000000000000000000000000000000013b", // length of ple encoded swaps without padding
"006f", // ple encoded swaps
"0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding
"006e", // ple encoded swaps
"00", // token in index
"01", // token out index
"999999", // split
@@ -2108,9 +2047,8 @@ mod tests {
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
"01", // zero2one
"00", // transfer from false
"01", // transfer true
"006f", // ple encoded swaps
"00", // transfer type TransferFrom
"006e", // ple encoded swaps
"00", // token in index
"01", // token out index
"000000", // split
@@ -2121,8 +2059,7 @@ mod tests {
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
"01", // zero2one
"00", // transfer from false
"01", // transfer true
"00", // transfer type TransferFrom
"0057", // ple encoded swaps
"01", // token in index
"00", // token out index
@@ -2132,12 +2069,12 @@ mod tests {
"b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id,
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"00", // zero2one
"01", // transfer true
"0000000000" // padding
"01", // transfer type Transfer
"00000000000000" // padding
]
.join("");
assert_eq!(hex_calldata[..584], expected_input);
assert_eq!(hex_calldata[1352..], expected_swaps);
assert_eq!(hex_calldata[..520], expected_input);
assert_eq!(hex_calldata[1288..], expected_swaps);
write_calldata_to_file("test_split_input_cyclic_swap", hex_calldata.as_str());
}
@@ -2249,7 +2186,7 @@ mod tests {
let hex_calldata = hex::encode(&calldata);
let expected_input = [
"308f3ce0", // selector
"7c553846", // selector
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
@@ -2258,12 +2195,11 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000001", // transfer from
]
.join("");
let expected_swaps = [
"000000000000000000000000000000000000000000000000000000000000013b", // length of ple encoded swaps without padding
"0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding
"0057", // ple encoded swaps
"00", // token in index
"01", // token out index
@@ -2273,8 +2209,8 @@ mod tests {
"b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
"01", // zero2one
"01", // transfer true
"006f", // ple encoded swaps
"00", // transfer type TransferFrom
"006e", // ple encoded swaps
"01", // token in index
"00", // token out index
"999999", // split
@@ -2285,9 +2221,8 @@ mod tests {
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
"00", // zero2one
"00", // transfer from false
"01", // transfer true
"006f", // ple encoded swaps
"01", // transfer type Transfer
"006e", // ple encoded swaps
"01", // token in index
"00", // token out index
"000000", // split
@@ -2298,14 +2233,13 @@ mod tests {
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
"00", // zero2one
"00", // transfer from false
"01", // transfer true
"0000000000" // padding
"01", // transfer type Transfer
"00000000000000" // padding
]
.join("");
assert_eq!(hex_calldata[..584], expected_input);
assert_eq!(hex_calldata[1352..], expected_swaps);
assert_eq!(hex_calldata[..520], expected_input);
assert_eq!(hex_calldata[1288..], expected_swaps);
write_calldata_to_file("test_split_output_cyclic_swap", hex_calldata.as_str());
}
}
@@ -2653,7 +2587,7 @@ mod tests {
.unwrap();
let expected_input = [
"a93aabdf", // Function selector (single swap)
"30ace1b1", // Function selector (single swap)
"000000000000000000000000000000000000000000000000000000003b9aca00", // amount in
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
"0000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933", // token out
@@ -2661,8 +2595,6 @@ mod tests {
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
"0000000000000000000000000000000000000000000000000000000000000000", // transfer from
"0000000000000000000000000000000000000000000000000000000000000000", // funds receiver
]
.join("");
@@ -2671,15 +2603,14 @@ mod tests {
let expected_swaps = String::from(concat!(
// length of ple encoded swaps without padding
"0000000000000000000000000000000000000000000000000000000000000087",
"0000000000000000000000000000000000000000000000000000000000000086",
// Swap data header
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
// Protocol data
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
"00", // zero2one
"01", // transfer from true
"00", // transfer false
"00", // transfer type TransferFrom
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
// First pool params
"0000000000000000000000000000000000000000", // intermediary token (ETH)
@@ -2689,13 +2620,13 @@ mod tests {
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
"0061a8", // fee
"0001f4", // tick spacing
"00000000000000000000000000000000000000000000000000" // padding
"0000000000000000000000000000000000000000000000000000" // padding
));
let hex_calldata = encode(&calldata);
assert_eq!(hex_calldata[..584], expected_input);
assert_eq!(hex_calldata[1352..], expected_swaps);
assert_eq!(hex_calldata[..456], expected_input);
assert_eq!(hex_calldata[1224..], expected_swaps);
write_calldata_to_file(
"test_single_encoding_strategy_usv4_grouped_swap",
hex_calldata.as_str(),

View File

@@ -8,6 +8,7 @@ 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.
@@ -34,54 +35,47 @@ impl TransferOptimization {
}
}
/// 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();
/// Returns the transfer type that should be used for the first transfer.
pub fn get_transfers(
&self,
swap: SwapGroup,
given_token: Bytes,
wrap: bool,
in_between_swap_optimization: bool,
) -> TransferType {
let is_first_swap = swap.token_in == given_token;
let in_transfer_required: bool =
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
if swap.token_in == self.native_token {
// Funds are already in router. All protocols currently take care of native transfers.
(false, zero_address, false)
TransferType::None
} else if (swap.token_in == self.wrapped_token) && wrap {
// 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)
TransferType::Transfer
} else if is_first_swap {
if in_transfer_required {
if self.token_in_already_in_router {
// Transfer from router to pool.
TransferType::Transfer
} else {
// Transfer from swapper to pool
TransferType::TransferFrom
}
// in transfer is not necessary for these protocols. Only make a transfer from the
// swapper to the router if the tokens are not already in the router
} else if !self.token_in_already_in_router {
// Transfer from swapper to router using.
TransferType::TransferFrom
} else {
// Transfer from swapper to pool
(true, swap.swaps[0].component.id.clone(), false)
TransferType::None
}
// in transfer is not necessary for these protocols. Only make a transfer from the swapper
// to the router if the tokens are not already in the router
} else if !self.token_in_already_in_router {
// Transfer from swapper to router using.
(true, self.router_address.to_string(), false)
} else {
(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 {
// all other swaps that not the first one
} else if !in_transfer_required || in_between_swap_optimization {
// funds should already be in the router or in the next pool
false
TransferType::None
} else {
true
TransferType::Transfer
}
}
@@ -149,31 +143,35 @@ mod tests {
Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")
}
fn zero_address() -> String {
Bytes::from([0u8; 20]).to_string()
}
#[rstest]
// First swap tests
// WETH -(univ2)-> DAI we expect a transfer from the user to the protocol
#[case(weth(), "uniswap_v2".to_string(), false, false, true, "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11", false)]
#[case(weth(), weth(), "uniswap_v2".to_string(), false, false,false, TransferType::TransferFrom)]
// Native token swap. No transfer is needed
#[case(eth(), "uniswap_v2".to_string(),false, false, false, zero_address(), false)]
#[case(eth(), eth(), "uniswap_v2".to_string(),false, false,false, TransferType::None)]
// 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)]
#[case(eth(), weth(), "uniswap_v2".to_string(),true, false,false,TransferType::Transfer)]
// 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)]
#[case(usdc(), usdc(), "uniswap_v2".to_string(),false, true,false, TransferType::Transfer)]
// 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)]
#[case(usdc(), usdc(), "vm:curve".to_string(),false, true, false,TransferType::None)]
// other swaps tests
// tokens need to be transferred into the pool
#[case(weth(), usdc(), "uniswap_v2".to_string(), false, false,false, TransferType::Transfer)]
// tokens are already in the pool (optimization)
#[case(weth(), usdc(), "uniswap_v2".to_string(), false, false, true, TransferType::None)]
// tokens are already in the router and don't need a transfer
#[case(weth(), usdc(), "vm:curve".to_string(), false, false, false, TransferType::None)]
fn test_get_transfers(
#[case] token_in: Bytes,
#[case] given_token: Bytes,
#[case] swap_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,
#[case] in_between_swap_optimization: bool,
#[case] expected_transfer: TransferType,
) {
// The swap token is the same as the given token, which is not the native token
let swaps = vec![Swap {
@@ -182,34 +180,25 @@ mod tests {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
..Default::default()
},
token_in: token_in.clone(),
token_in: swap_token_in.clone(),
token_out: dai(),
split: 0f64,
}];
let swap =
SwapGroup { protocol_system: protocol, token_in, token_out: dai(), split: 0f64, swaps };
let swap = SwapGroup {
protocol_system: protocol,
token_in: swap_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);
}
#[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);
let transfer = optimization.get_transfers(
swap.clone(),
given_token,
wrap,
in_between_swap_optimization,
);
assert_eq!(transfer, expected_transfer);
}

View File

@@ -66,7 +66,7 @@ impl SwapEncoder for UniswapV2SwapEncoder {
component_id,
bytes_to_address(&encoding_context.receiver)?,
zero_to_one,
encoding_context.transfer_needed,
(encoding_context.transfer as u8).to_be_bytes(),
);
Ok(args.abi_encode_packed())
@@ -129,8 +129,7 @@ impl SwapEncoder for UniswapV3SwapEncoder {
bytes_to_address(&encoding_context.receiver)?,
component_id,
zero_to_one,
encoding_context.transfer_from_needed,
encoding_context.transfer_needed,
(encoding_context.transfer as u8).to_be_bytes(),
);
Ok(args.abi_encode_packed())
@@ -207,8 +206,7 @@ impl SwapEncoder for UniswapV4SwapEncoder {
group_token_in_address,
group_token_out_address,
zero_to_one,
encoding_context.transfer_from_needed,
encoding_context.transfer_needed,
(encoding_context.transfer as u8).to_be_bytes(),
bytes_to_address(&encoding_context.receiver)?,
pool_params,
);
@@ -284,6 +282,7 @@ impl SwapEncoder for BalancerV2SwapEncoder {
component_id,
bytes_to_address(&encoding_context.receiver)?,
approval_needed,
(encoding_context.transfer as u8).to_be_bytes(),
);
Ok(args.abi_encode_packed())
}
@@ -345,8 +344,7 @@ impl SwapEncoder for EkuboSwapEncoder {
let mut encoded = vec![];
if encoding_context.group_token_in == swap.token_in {
encoded.extend((encoding_context.transfer_from_needed as u8).to_be_bytes());
encoded.extend((encoding_context.transfer_needed as u8).to_be_bytes());
encoded.extend((encoding_context.transfer as u8).to_be_bytes());
encoded.extend(bytes_to_address(&encoding_context.receiver)?);
encoded.extend(bytes_to_address(&swap.token_in)?);
}
@@ -578,6 +576,7 @@ impl SwapEncoder for CurveSwapEncoder {
j.to_be_bytes::<1>(),
approval_needed,
bytes_to_address(&encoding_context.receiver)?,
(encoding_context.transfer as u8).to_be_bytes(),
);
Ok(args.abi_encode_packed())
@@ -621,7 +620,7 @@ impl SwapEncoder for MaverickV2SwapEncoder {
bytes_to_address(&swap.token_in)?,
component_id,
bytes_to_address(&encoding_context.receiver)?,
encoding_context.transfer_needed,
(encoding_context.transfer as u8).to_be_bytes(),
);
Ok(args.abi_encode_packed())
}
@@ -646,7 +645,7 @@ mod tests {
};
use super::*;
use crate::encoding::evm::utils::write_calldata_to_file;
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
mod uniswap_v2 {
use super::*;
@@ -671,8 +670,7 @@ mod tests {
router_address: Some(Bytes::zero(20)),
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder = UniswapV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -695,7 +693,7 @@ mod tests {
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
// zero for one
"00",
// transfer true
// transfer type Transfer
"01",
))
);
@@ -731,8 +729,7 @@ mod tests {
router_address: Some(Bytes::zero(20)),
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder = UniswapV3SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -759,9 +756,7 @@ mod tests {
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
// zero for one
"00",
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
))
);
@@ -796,8 +791,7 @@ mod tests {
router_address: Some(Bytes::zero(20)),
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: false,
transfer_from_needed: false,
transfer: TransferType::None,
};
let encoder = BalancerV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
@@ -825,7 +819,9 @@ mod tests {
// receiver
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
// approval needed
"01"
"01",
// transfer type None
"02"
))
);
write_calldata_to_file("test_encode_balancer_v2", hex_swap.as_str());
@@ -869,8 +865,7 @@ mod tests {
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder = UniswapV4SwapEncoder::new(
String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"),
@@ -892,9 +887,7 @@ mod tests {
"dac17f958d2ee523a2206206994597c13d831ec7",
// zero for one
"01",
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
@@ -943,8 +936,7 @@ mod tests {
group_token_in: group_token_in.clone(),
// Token out is the same as the group token out
group_token_out: token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder = UniswapV4SwapEncoder::new(
@@ -987,8 +979,7 @@ mod tests {
router_address: Some(router_address.clone()),
group_token_in: usde_address.clone(),
group_token_out: wbtc_address.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
// Setup - First sequence: USDE -> USDT
@@ -1066,9 +1057,7 @@ mod tests {
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
// zero for one
"01",
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",
@@ -1125,8 +1114,7 @@ mod tests {
group_token_out: token_out.clone(),
exact_out: false,
router_address: Some(Bytes::default()),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder =
@@ -1142,9 +1130,7 @@ mod tests {
assert_eq!(
hex_swap,
concat!(
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
// receiver
"ca4f73fe97d0b987a0d12b39bbd562c779bab6f6",
@@ -1174,8 +1160,7 @@ mod tests {
group_token_out: group_token_out.clone(),
exact_out: false,
router_address: Some(Bytes::default()),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let first_swap = Swap {
@@ -1225,9 +1210,7 @@ mod tests {
combined_hex,
// transfer type
concat!(
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
// receiver
"ca4f73fe97d0b987a0d12b39bbd562c779bab6f6",
@@ -1373,8 +1356,7 @@ mod tests {
router_address: None,
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: false,
transfer_from_needed: false,
transfer: TransferType::None,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1405,7 +1387,9 @@ mod tests {
// approval needed
"01",
// receiver,
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
// transfer type None
"02",
))
);
}
@@ -1442,8 +1426,7 @@ mod tests {
router_address: None,
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: false,
transfer_from_needed: false,
transfer: TransferType::None,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1474,7 +1457,9 @@ mod tests {
// approval needed
"01",
// receiver
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
// transfer type None
"02",
))
);
}
@@ -1512,8 +1497,7 @@ mod tests {
router_address: None,
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: false,
transfer_from_needed: false,
transfer: TransferType::None,
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
@@ -1553,7 +1537,9 @@ mod tests {
// approval needed
"01",
// receiver
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
// transfer type None
"02",
))
);
}
@@ -1582,8 +1568,7 @@ mod tests {
router_address: Some(Bytes::default()),
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer: TransferType::Transfer,
};
let encoder = MaverickV2SwapEncoder::new(
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),

View File

@@ -6,14 +6,14 @@ use tycho_common::Bytes;
use crate::encoding::{
errors::EncodingError,
evm::{
constants::GROUPABLE_PROTOCOLS,
constants::{GROUPABLE_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS},
group_swaps::group_swaps,
strategy_encoder::strategy_encoders::{
SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder,
},
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
},
models::{Chain, EncodingContext, NativeAction, Solution, Transaction},
models::{Chain, EncodingContext, NativeAction, Solution, Transaction, TransferType},
strategy_encoder::StrategyEncoder,
tycho_encoder::TychoEncoder,
};
@@ -273,14 +273,20 @@ impl TychoExecutorEncoder {
let mut grouped_protocol_data: Vec<u8> = vec![];
for swap in grouped_swap.swaps.iter() {
let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS
.contains(&swap.component.protocol_system.as_str())
{
TransferType::Transfer
} else {
TransferType::None
};
let encoding_context = EncodingContext {
receiver: receiver.clone(),
exact_out: solution.exact_out,
router_address: None,
group_token_in: grouped_swap.token_in.clone(),
group_token_out: grouped_swap.token_out.clone(),
transfer_needed: true,
transfer_from_needed: false,
transfer,
};
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
grouped_protocol_data.extend(protocol_data);
@@ -462,7 +468,7 @@ mod tests {
Bytes::from_str("0x3ede3eca2a72b3aecc820e955b36f38437d01395").unwrap()
);
// single swap selector
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "cc60c623");
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "20144a07");
}
#[test]
@@ -487,7 +493,7 @@ mod tests {
let transactions = transactions.unwrap();
assert_eq!(transactions.len(), 1);
// single swap selector
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "cc60c623");
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "20144a07");
}
#[test]
@@ -534,7 +540,7 @@ mod tests {
assert_eq!(transactions.len(), 1);
assert_eq!(transactions[0].value, eth_amount_in);
// sequential swap selector
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "59e3efbb");
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e8a980d7");
}
#[test]
@@ -1148,9 +1154,7 @@ mod tests {
"6982508145454ce325ddbe47a25d4ec3d2311933",
// zero for one
"00",
// transfer from false
"00",
// transfer true
// transfer type Transfer
"01",
// receiver
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2",