Merge pull request #174 from propeller-heads/encoding/dc/ENG-4446-tokens-already-in-router
feat: Allow for token_in_already_in_router
This commit is contained in:
@@ -367,4 +367,22 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
|
|||||||
assertTrue(success, "Call Failed");
|
assertTrue(success, "Call Failed");
|
||||||
assertEq(balanceAfter - balanceBefore, 1120007305574805922);
|
assertEq(balanceAfter - balanceBefore, 1120007305574805922);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testSingleSwapIntegrationNoTransferIn() public {
|
||||||
|
// Tests swapping WETH -> DAI on a USV2 pool assuming that the tokens are already inside the router
|
||||||
|
deal(WETH_ADDR, tychoRouterAddr, 1 ether);
|
||||||
|
uint256 balanceBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
// Encoded solution generated using `test_single_swap_strategy_encoder_no_transfer_in`
|
||||||
|
(bool success,) = tychoRouterAddr.call(
|
||||||
|
hex"20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000008f1d5c1cae3740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000"
|
||||||
|
);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
uint256 balanceAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertEq(balanceAfter - balanceBefore, 2659881924818443699787);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub struct TychoRouterEncoderBuilder {
|
|||||||
chain: Option<Chain>,
|
chain: Option<Chain>,
|
||||||
executors_file_path: Option<String>,
|
executors_file_path: Option<String>,
|
||||||
router_address: Option<Bytes>,
|
router_address: Option<Bytes>,
|
||||||
|
token_in_already_in_router: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TychoRouterEncoderBuilder {
|
impl Default for TychoRouterEncoderBuilder {
|
||||||
@@ -36,6 +37,7 @@ impl TychoRouterEncoderBuilder {
|
|||||||
chain: None,
|
chain: None,
|
||||||
executors_file_path: None,
|
executors_file_path: None,
|
||||||
router_address: None,
|
router_address: None,
|
||||||
|
token_in_already_in_router: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn chain(mut self, chain: TychoCommonChain) -> Self {
|
pub fn chain(mut self, chain: TychoCommonChain) -> Self {
|
||||||
@@ -62,6 +64,16 @@ impl TychoRouterEncoderBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the `token_in_already_in_router` flag.
|
||||||
|
// If set to true, the encoder will assume that the token in is already in the router.
|
||||||
|
// WARNING: this is an advanced feature and should be used with caution. Make sure you have
|
||||||
|
// checks to make sure that your tokens won't be lost. The Router is not considered safe to hold
|
||||||
|
// tokens, so if this is not done within the same transaction you will lose your tokens.
|
||||||
|
pub fn token_in_already_in_router(mut self, token_in_already_in_router: bool) -> Self {
|
||||||
|
self.token_in_already_in_router = Some(token_in_already_in_router);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the `TychoRouterEncoder` instance using the configured chain.
|
/// Builds the `TychoRouterEncoder` instance using the configured chain.
|
||||||
/// Returns an error if either the chain has not been set.
|
/// Returns an error if either the chain has not been set.
|
||||||
pub fn build(self) -> Result<Box<dyn TychoEncoder>, EncodingError> {
|
pub fn build(self) -> Result<Box<dyn TychoEncoder>, EncodingError> {
|
||||||
@@ -88,6 +100,8 @@ impl TychoRouterEncoderBuilder {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
self.swapper_pk,
|
self.swapper_pk,
|
||||||
tycho_router_address,
|
tycho_router_address,
|
||||||
|
self.token_in_already_in_router
|
||||||
|
.unwrap_or(false),
|
||||||
)?))
|
)?))
|
||||||
} else {
|
} else {
|
||||||
Err(EncodingError::FatalError(
|
Err(EncodingError::FatalError(
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ use crate::encoding::{evm::constants::GROUPABLE_PROTOCOLS, models::Swap};
|
|||||||
/// optimization.
|
/// optimization.
|
||||||
///
|
///
|
||||||
/// # Fields
|
/// # Fields
|
||||||
/// * `input_token`: Bytes, the input token of the first swap
|
/// * `token_in`: Bytes, the input token of the first swap
|
||||||
/// * `output_token`: Bytes, the output token of the final swap
|
/// * `token_out`: Bytes, the output token of the final swap
|
||||||
/// * `protocol_system`: String, the protocol system of the swaps
|
/// * `protocol_system`: String, the protocol system of the swaps
|
||||||
/// * `swaps`: Vec<Swap>, the sequence of swaps to be executed as a group
|
/// * `swaps`: Vec<Swap>, the sequence of swaps to be executed as a group
|
||||||
/// * `split`: f64, the split percentage of the first swap in the group
|
/// * `split`: f64, the split percentage of the first swap in the group
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct SwapGroup {
|
pub struct SwapGroup {
|
||||||
pub input_token: Bytes,
|
pub token_in: Bytes,
|
||||||
pub output_token: Bytes,
|
pub token_out: Bytes,
|
||||||
pub protocol_system: String,
|
pub protocol_system: String,
|
||||||
pub swaps: Vec<Swap>,
|
pub swaps: Vec<Swap>,
|
||||||
pub split: f64,
|
pub split: f64,
|
||||||
@@ -44,7 +44,7 @@ pub fn group_swaps(swaps: Vec<Swap>) -> Vec<SwapGroup> {
|
|||||||
if let Some(group) = current_group.as_mut() {
|
if let Some(group) = current_group.as_mut() {
|
||||||
group.swaps.push(swap.clone());
|
group.swaps.push(swap.clone());
|
||||||
// Update the output token of the current group.
|
// Update the output token of the current group.
|
||||||
group.output_token = swap.token_out.clone();
|
group.token_out = swap.token_out.clone();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not second or later USV4 pool. Push the current group (if it exists) and then
|
// Not second or later USV4 pool. Push the current group (if it exists) and then
|
||||||
@@ -53,8 +53,8 @@ pub fn group_swaps(swaps: Vec<Swap>) -> Vec<SwapGroup> {
|
|||||||
grouped_swaps.push(group.clone());
|
grouped_swaps.push(group.clone());
|
||||||
}
|
}
|
||||||
current_group = Some(SwapGroup {
|
current_group = Some(SwapGroup {
|
||||||
input_token: swap.token_in.clone(),
|
token_in: swap.token_in.clone(),
|
||||||
output_token: swap.token_out.clone(),
|
token_out: swap.token_out.clone(),
|
||||||
protocol_system: current_swap_protocol.clone(),
|
protocol_system: current_swap_protocol.clone(),
|
||||||
swaps: vec![swap.clone()],
|
swaps: vec![swap.clone()],
|
||||||
split: swap.split,
|
split: swap.split,
|
||||||
@@ -135,15 +135,15 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
input_token: weth,
|
token_in: weth,
|
||||||
output_token: usdc.clone(),
|
token_out: usdc.clone(),
|
||||||
protocol_system: "uniswap_v4".to_string(),
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
},
|
},
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_usdc_dai],
|
swaps: vec![swap_usdc_dai],
|
||||||
input_token: usdc,
|
token_in: usdc,
|
||||||
output_token: dai,
|
token_out: dai,
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
}
|
}
|
||||||
@@ -216,22 +216,22 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_wbtc_weth],
|
swaps: vec![swap_wbtc_weth],
|
||||||
input_token: wbtc.clone(),
|
token_in: wbtc.clone(),
|
||||||
output_token: weth.clone(),
|
token_out: weth.clone(),
|
||||||
protocol_system: "uniswap_v4".to_string(),
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
},
|
},
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_weth_usdc],
|
swaps: vec![swap_weth_usdc],
|
||||||
input_token: weth.clone(),
|
token_in: weth.clone(),
|
||||||
output_token: usdc.clone(),
|
token_out: usdc.clone(),
|
||||||
protocol_system: "uniswap_v4".to_string(),
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
split: 0.5f64,
|
split: 0.5f64,
|
||||||
},
|
},
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_weth_dai, swap_dai_usdc],
|
swaps: vec![swap_weth_dai, swap_dai_usdc],
|
||||||
input_token: weth,
|
token_in: weth,
|
||||||
output_token: usdc,
|
token_out: usdc,
|
||||||
protocol_system: "uniswap_v4".to_string(),
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
}
|
}
|
||||||
@@ -304,15 +304,15 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
input_token: weth.clone(),
|
token_in: weth.clone(),
|
||||||
output_token: usdc.clone(),
|
token_out: usdc.clone(),
|
||||||
protocol_system: "vm:balancer_v3".to_string(),
|
protocol_system: "vm:balancer_v3".to_string(),
|
||||||
split: 0.5f64,
|
split: 0.5f64,
|
||||||
},
|
},
|
||||||
SwapGroup {
|
SwapGroup {
|
||||||
swaps: vec![swap_weth_dai, swap_dai_usdc],
|
swaps: vec![swap_weth_dai, swap_dai_usdc],
|
||||||
input_token: weth,
|
token_in: weth,
|
||||||
output_token: usdc,
|
token_out: usdc,
|
||||||
protocol_system: "uniswap_v4".to_string(),
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use crate::encoding::{
|
|||||||
errors::EncodingError,
|
errors::EncodingError,
|
||||||
evm::{
|
evm::{
|
||||||
approvals::permit2::Permit2,
|
approvals::permit2::Permit2,
|
||||||
constants::{CALLBACK_CONSTRAINED_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS},
|
|
||||||
group_swaps::group_swaps,
|
group_swaps::group_swaps,
|
||||||
strategy_encoder::{
|
strategy_encoder::{
|
||||||
strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
|
strategy_validators::{SequentialSwapValidator, SplitSwapValidator, SwapValidator},
|
||||||
@@ -32,17 +31,15 @@ use crate::encoding::{
|
|||||||
/// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary
|
/// * `permit2`: Permit2, responsible for managing permit2 operations and providing necessary
|
||||||
/// signatures and permit2 objects for calling the router
|
/// signatures and permit2 objects for calling the router
|
||||||
/// * `selector`: String, the selector for the swap function in the router contract
|
/// * `selector`: String, the selector for the swap function in the router contract
|
||||||
/// * `native_address`: Address of the chain's native token
|
|
||||||
/// * `wrapped_address`: Address of the chain's wrapped token
|
|
||||||
/// * `router_address`: Address of the router to be used to execute swaps
|
/// * `router_address`: Address of the router to be used to execute swaps
|
||||||
|
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SingleSwapStrategyEncoder {
|
pub struct SingleSwapStrategyEncoder {
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
permit2: Option<Permit2>,
|
permit2: Option<Permit2>,
|
||||||
selector: String,
|
selector: String,
|
||||||
native_address: Bytes,
|
|
||||||
wrapped_address: Bytes,
|
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
transfer_optimization: TransferOptimization,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleSwapStrategyEncoder {
|
impl SingleSwapStrategyEncoder {
|
||||||
@@ -51,6 +48,7 @@ impl SingleSwapStrategyEncoder {
|
|||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
swapper_pk: Option<String>,
|
swapper_pk: Option<String>,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
token_in_already_in_router: bool,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> Result<Self, EncodingError> {
|
||||||
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
|
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,((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())
|
||||||
@@ -60,13 +58,19 @@ impl SingleSwapStrategyEncoder {
|
|||||||
"singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(),
|
"singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let permit2_is_active = permit2.is_some();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
permit2,
|
permit2,
|
||||||
selector,
|
selector,
|
||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
native_address: chain.native_token()?,
|
router_address: router_address.clone(),
|
||||||
wrapped_address: chain.wrapped_token()?,
|
transfer_optimization: TransferOptimization::new(
|
||||||
router_address,
|
chain.native_token()?,
|
||||||
|
chain.wrapped_token()?,
|
||||||
|
permit2_is_active,
|
||||||
|
token_in_already_in_router,
|
||||||
|
router_address,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +84,6 @@ impl SingleSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferOptimization for SingleSwapStrategyEncoder {}
|
|
||||||
|
|
||||||
impl StrategyEncoder for SingleSwapStrategyEncoder {
|
impl StrategyEncoder for SingleSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
let grouped_swaps = group_swaps(solution.clone().swaps);
|
let grouped_swaps = group_swaps(solution.clone().swaps);
|
||||||
@@ -125,26 +127,20 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
let swap_receiver =
|
let swap_receiver =
|
||||||
if !unwrap { solution.receiver.clone() } else { self.router_address.clone() };
|
if !unwrap { solution.receiver.clone() } else { self.router_address.clone() };
|
||||||
|
|
||||||
|
let transfer_type = self
|
||||||
|
.transfer_optimization
|
||||||
|
.get_transfer_type(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_type: transfer_type.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let transfer_type = self.get_transfer_type(
|
|
||||||
swap.clone(),
|
|
||||||
solution.given_token.clone(),
|
|
||||||
self.native_address.clone(),
|
|
||||||
self.wrapped_address.clone(),
|
|
||||||
self.permit2.clone().is_some(),
|
|
||||||
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.input_token.clone(),
|
|
||||||
group_token_out: grouped_swap.output_token.clone(),
|
|
||||||
transfer_type: transfer_type.clone(),
|
|
||||||
};
|
|
||||||
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
@@ -215,6 +211,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
/// * `router_address`: Address of the router to be used to execute swaps
|
/// * `router_address`: Address of the router to be used to execute swaps
|
||||||
/// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of
|
/// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of
|
||||||
/// sequential swap solutions
|
/// sequential swap solutions
|
||||||
|
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SequentialSwapStrategyEncoder {
|
pub struct SequentialSwapStrategyEncoder {
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
@@ -224,16 +221,16 @@ pub struct SequentialSwapStrategyEncoder {
|
|||||||
native_address: Bytes,
|
native_address: Bytes,
|
||||||
wrapped_address: Bytes,
|
wrapped_address: Bytes,
|
||||||
sequential_swap_validator: SequentialSwapValidator,
|
sequential_swap_validator: SequentialSwapValidator,
|
||||||
|
transfer_optimization: TransferOptimization,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferOptimization for SequentialSwapStrategyEncoder {}
|
|
||||||
|
|
||||||
impl SequentialSwapStrategyEncoder {
|
impl SequentialSwapStrategyEncoder {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
swapper_pk: Option<String>,
|
swapper_pk: Option<String>,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
token_in_already_in_router: bool,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> Result<Self, EncodingError> {
|
||||||
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
|
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,((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())
|
||||||
@@ -244,14 +241,22 @@ impl SequentialSwapStrategyEncoder {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let permit2_is_active = permit2.is_some();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
permit2,
|
permit2,
|
||||||
selector,
|
selector,
|
||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
router_address,
|
router_address: router_address.clone(),
|
||||||
native_address: chain.native_token()?,
|
native_address: chain.native_token()?,
|
||||||
wrapped_address: chain.wrapped_token()?,
|
wrapped_address: chain.wrapped_token()?,
|
||||||
sequential_swap_validator: SequentialSwapValidator,
|
sequential_swap_validator: SequentialSwapValidator,
|
||||||
|
transfer_optimization: TransferOptimization::new(
|
||||||
|
chain.native_token()?,
|
||||||
|
chain.wrapped_token()?,
|
||||||
|
permit2_is_active,
|
||||||
|
token_in_already_in_router,
|
||||||
|
router_address,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +296,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut swaps = vec![];
|
let mut swaps = vec![];
|
||||||
let mut next_in_between_swap_optimization = true;
|
let mut next_in_between_swap_optimization_allowed = true;
|
||||||
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
|
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
|
||||||
let protocol = grouped_swap.protocol_system.clone();
|
let protocol = grouped_swap.protocol_system.clone();
|
||||||
let swap_encoder = self
|
let swap_encoder = self
|
||||||
@@ -303,50 +308,31 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let in_between_swap_optimization = next_in_between_swap_optimization;
|
let in_between_swap_optimization_allowed = next_in_between_swap_optimization_allowed;
|
||||||
let next_swap = grouped_swaps.get(i + 1);
|
let next_swap = grouped_swaps.get(i + 1);
|
||||||
// if there is a next swap
|
let (swap_receiver, next_swap_optimization) = self
|
||||||
let swap_receiver = if let Some(next) = next_swap {
|
.transfer_optimization
|
||||||
// if the protocol of the next swap supports transfer in optimization
|
.get_receiver(solution.receiver.clone(), next_swap)?;
|
||||||
if IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&next.protocol_system.as_str()) {
|
next_in_between_swap_optimization_allowed = next_swap_optimization;
|
||||||
// if the protocol does not allow for chained swaps, we can't optimize the
|
let transfer_type = self
|
||||||
// receiver of this swap nor the transfer in of the next swap
|
.transfer_optimization
|
||||||
if CALLBACK_CONSTRAINED_PROTOCOLS.contains(&next.protocol_system.as_str()) {
|
.get_transfer_type(
|
||||||
next_in_between_swap_optimization = false;
|
grouped_swap.clone(),
|
||||||
self.router_address.clone()
|
solution.given_token.clone(),
|
||||||
} else {
|
wrap,
|
||||||
Bytes::from_str(&next.swaps[0].component.id.clone()).map_err(|_| {
|
in_between_swap_optimization_allowed,
|
||||||
EncodingError::FatalError("Invalid component id".to_string())
|
);
|
||||||
})?
|
let encoding_context = EncodingContext {
|
||||||
}
|
receiver: swap_receiver.clone(),
|
||||||
} else {
|
exact_out: solution.exact_out,
|
||||||
// the protocol of the next swap does not support transfer in optimization
|
router_address: Some(self.router_address.clone()),
|
||||||
self.router_address.clone()
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
}
|
group_token_out: grouped_swap.token_out.clone(),
|
||||||
} else {
|
transfer_type: transfer_type.clone(),
|
||||||
solution.receiver.clone() // last swap - there is not next swap
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let transfer_type = self.get_transfer_type(
|
|
||||||
swap.clone(),
|
|
||||||
solution.given_token.clone(),
|
|
||||||
self.native_address.clone(),
|
|
||||||
self.wrapped_address.clone(),
|
|
||||||
self.permit2.clone().is_some(),
|
|
||||||
wrap,
|
|
||||||
in_between_swap_optimization,
|
|
||||||
);
|
|
||||||
|
|
||||||
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.input_token.clone(),
|
|
||||||
group_token_out: grouped_swap.output_token.clone(),
|
|
||||||
transfer_type: transfer_type.clone(),
|
|
||||||
};
|
|
||||||
let protocol_data =
|
let protocol_data =
|
||||||
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
@@ -422,6 +408,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
/// * `split_swap_validator`: SplitSwapValidator, responsible for checking validity of split swap
|
/// * `split_swap_validator`: SplitSwapValidator, responsible for checking validity of split swap
|
||||||
/// solutions
|
/// solutions
|
||||||
/// * `router_address`: Address of the router to be used to execute swaps
|
/// * `router_address`: Address of the router to be used to execute swaps
|
||||||
|
/// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SplitSwapStrategyEncoder {
|
pub struct SplitSwapStrategyEncoder {
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
@@ -431,6 +418,7 @@ pub struct SplitSwapStrategyEncoder {
|
|||||||
wrapped_address: Bytes,
|
wrapped_address: Bytes,
|
||||||
split_swap_validator: SplitSwapValidator,
|
split_swap_validator: SplitSwapValidator,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
transfer_optimization: TransferOptimization,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SplitSwapStrategyEncoder {
|
impl SplitSwapStrategyEncoder {
|
||||||
@@ -439,6 +427,7 @@ impl SplitSwapStrategyEncoder {
|
|||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
swapper_pk: Option<String>,
|
swapper_pk: Option<String>,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
token_in_already_in_router: bool,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> Result<Self, EncodingError> {
|
||||||
let (permit2, selector) = if let Some(swapper_pk) = swapper_pk {
|
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,((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())
|
||||||
@@ -449,7 +438,7 @@ impl SplitSwapStrategyEncoder {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
let permit2_is_active = permit2.is_some();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
permit2,
|
permit2,
|
||||||
selector,
|
selector,
|
||||||
@@ -457,7 +446,14 @@ impl SplitSwapStrategyEncoder {
|
|||||||
native_address: chain.native_token()?,
|
native_address: chain.native_token()?,
|
||||||
wrapped_address: chain.wrapped_token()?,
|
wrapped_address: chain.wrapped_token()?,
|
||||||
split_swap_validator: SplitSwapValidator,
|
split_swap_validator: SplitSwapValidator,
|
||||||
router_address,
|
router_address: router_address.clone(),
|
||||||
|
transfer_optimization: TransferOptimization::new(
|
||||||
|
chain.native_token()?,
|
||||||
|
chain.wrapped_token()?,
|
||||||
|
permit2_is_active,
|
||||||
|
token_in_already_in_router,
|
||||||
|
router_address,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,8 +477,6 @@ impl SplitSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransferOptimization for SplitSwapStrategyEncoder {}
|
|
||||||
|
|
||||||
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
self.split_swap_validator
|
self.split_swap_validator
|
||||||
@@ -513,7 +507,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
let intermediary_tokens: HashSet<Bytes> = grouped_swaps
|
let intermediary_tokens: HashSet<Bytes> = grouped_swaps
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|grouped_swap| {
|
.flat_map(|grouped_swap| {
|
||||||
vec![grouped_swap.input_token.clone(), grouped_swap.output_token.clone()]
|
vec![grouped_swap.token_in.clone(), grouped_swap.token_out.clone()]
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let mut intermediary_tokens: Vec<Bytes> = intermediary_tokens
|
let mut intermediary_tokens: Vec<Bytes> = intermediary_tokens
|
||||||
@@ -558,40 +552,33 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let swap_receiver = if !unwrap && grouped_swap.output_token == solution.checked_token {
|
let swap_receiver = if !unwrap && grouped_swap.token_out == solution.checked_token {
|
||||||
solution.receiver.clone()
|
solution.receiver.clone()
|
||||||
} else {
|
} else {
|
||||||
self.router_address.clone()
|
self.router_address.clone()
|
||||||
};
|
};
|
||||||
|
let transfer_type = self
|
||||||
|
.transfer_optimization
|
||||||
|
.get_transfer_type(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_type: transfer_type.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let transfer_type = self.get_transfer_type(
|
|
||||||
swap.clone(),
|
|
||||||
solution.given_token.clone(),
|
|
||||||
self.native_address.clone(),
|
|
||||||
self.wrapped_address.clone(),
|
|
||||||
self.permit2.clone().is_some(),
|
|
||||||
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.input_token.clone(),
|
|
||||||
group_token_out: grouped_swap.output_token.clone(),
|
|
||||||
transfer_type: transfer_type.clone(),
|
|
||||||
};
|
|
||||||
let protocol_data =
|
let protocol_data =
|
||||||
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let swap_data = self.encode_swap_header(
|
let swap_data = self.encode_swap_header(
|
||||||
get_token_position(tokens.clone(), grouped_swap.input_token.clone())?,
|
get_token_position(tokens.clone(), grouped_swap.token_in.clone())?,
|
||||||
get_token_position(tokens.clone(), grouped_swap.output_token.clone())?,
|
get_token_position(tokens.clone(), grouped_swap.token_out.clone())?,
|
||||||
percentage_to_uint24(grouped_swap.split),
|
percentage_to_uint24(grouped_swap.split),
|
||||||
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
||||||
EncodingError::FatalError("Invalid executor address".to_string())
|
EncodingError::FatalError("Invalid executor address".to_string())
|
||||||
@@ -744,6 +731,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -827,6 +815,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -876,6 +865,85 @@ mod tests {
|
|||||||
println!("test_single_swap_strategy_encoder_no_permit2: {}", hex_calldata);
|
println!("test_single_swap_strategy_encoder_no_permit2: {}", hex_calldata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder_no_transfer_in() {
|
||||||
|
// Performs a single swap from WETH to DAI on a USV2 pool assuming that the tokens are
|
||||||
|
// already in the router
|
||||||
|
|
||||||
|
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
|
||||||
|
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
|
||||||
|
|
||||||
|
let expected_amount = Some(BigUint::from_str("2_650_000000000000000000").unwrap());
|
||||||
|
let slippage = Some(0.01f64);
|
||||||
|
let checked_amount = Some(BigUint::from_str("2_640_000000000000000000").unwrap());
|
||||||
|
let expected_min_amount = U256::from_str("2_640_000000000000000000").unwrap();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
};
|
||||||
|
let swap_encoder_registry = get_swap_encoder_registry();
|
||||||
|
let encoder = SingleSwapStrategyEncoder::new(
|
||||||
|
eth_chain(),
|
||||||
|
swap_encoder_registry,
|
||||||
|
None,
|
||||||
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: dai,
|
||||||
|
expected_amount,
|
||||||
|
slippage,
|
||||||
|
checked_amount,
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (calldata, _) = encoder
|
||||||
|
.encode_strategy(solution)
|
||||||
|
.unwrap();
|
||||||
|
let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount));
|
||||||
|
let expected_input = [
|
||||||
|
"20144a07", // Function selector
|
||||||
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
|
||||||
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
||||||
|
&expected_min_amount_encoded, // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding
|
||||||
|
|
||||||
|
// Swap data
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"00", // zero2one
|
||||||
|
"00", // transfer type
|
||||||
|
"0000000000000000000000000000", // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata, expected_input);
|
||||||
|
println!("test_single_swap_strategy_encoder_no_transfer_in: {}", hex_calldata);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_swap_strategy_encoder_wrap() {
|
fn test_single_swap_strategy_encoder_wrap() {
|
||||||
// Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH
|
// Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH
|
||||||
@@ -904,6 +972,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -956,6 +1025,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1027,6 +1097,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1086,6 +1157,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1204,6 +1276,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1321,6 +1394,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1402,6 +1476,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1487,6 +1562,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1553,6 +1629,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1652,6 +1729,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -1761,6 +1839,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key.clone()),
|
Some(private_key.clone()),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1923,6 +2002,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key.clone()),
|
Some(private_key.clone()),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2048,6 +2128,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(),
|
Bytes::from_str("0xA4AD4f68d0b91CFD19687c881e50f3A00242828c").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2113,6 +2194,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2181,6 +2263,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2268,6 +2351,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
Some(private_key),
|
Some(private_key),
|
||||||
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
Bytes::from("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395"),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let solution = Solution {
|
let solution = Solution {
|
||||||
@@ -2370,6 +2454,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -2432,6 +2517,7 @@ mod tests {
|
|||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +1,86 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use tycho_common::Bytes;
|
use tycho_common::Bytes;
|
||||||
|
|
||||||
use crate::encoding::{
|
use crate::encoding::{
|
||||||
evm::constants::IN_TRANSFER_REQUIRED_PROTOCOLS,
|
errors::EncodingError,
|
||||||
models::{Swap, TransferType},
|
evm::{
|
||||||
|
constants::{CALLBACK_CONSTRAINED_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS},
|
||||||
|
group_swaps::SwapGroup,
|
||||||
|
},
|
||||||
|
models::TransferType,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait that defines how the tokens will be transferred into the given pool given the solution.
|
/// A struct that defines how the tokens will be transferred into the given pool given the solution.
|
||||||
pub trait TransferOptimization {
|
#[derive(Clone)]
|
||||||
/// Returns the transfer method that should be used for the given swap and solution.
|
pub struct TransferOptimization {
|
||||||
#[allow(clippy::too_many_arguments)]
|
native_token: Bytes,
|
||||||
fn get_transfer_type(
|
wrapped_token: Bytes,
|
||||||
&self,
|
permit2: bool,
|
||||||
swap: Swap,
|
token_in_already_in_router: bool,
|
||||||
given_token: Bytes,
|
router_address: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferOptimization {
|
||||||
|
pub fn new(
|
||||||
native_token: Bytes,
|
native_token: Bytes,
|
||||||
wrapped_token: Bytes,
|
wrapped_token: Bytes,
|
||||||
permit2: bool,
|
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,
|
wrap: bool,
|
||||||
in_between_swap_optimization: bool,
|
in_between_swap_optimization: bool,
|
||||||
) -> TransferType {
|
) -> TransferType {
|
||||||
let in_transfer_required: bool =
|
let in_transfer_required: bool =
|
||||||
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.component.protocol_system.as_str());
|
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
|
||||||
|
|
||||||
let is_first_swap = swap.token_in == given_token;
|
let is_first_swap = swap.token_in == given_token;
|
||||||
|
|
||||||
if swap.token_in == native_token {
|
if swap.token_in == self.native_token {
|
||||||
// Funds are already in router. All protocols currently take care of native transfers.
|
// Funds are already in router. All protocols currently take care of native transfers.
|
||||||
TransferType::None
|
TransferType::None
|
||||||
} else if (swap.token_in == wrapped_token) && wrap {
|
} else if (swap.token_in == self.wrapped_token) && wrap {
|
||||||
// Wrapping already happened in the router so we can just use a normal transfer.
|
// Wrapping already happened in the router so we can just use a normal transfer.
|
||||||
TransferType::TransferToProtocol
|
TransferType::TransferToProtocol
|
||||||
} else if is_first_swap {
|
} else if is_first_swap {
|
||||||
if in_transfer_required {
|
if in_transfer_required {
|
||||||
if permit2 {
|
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.
|
// Transfer from swapper to pool using permit2.
|
||||||
TransferType::TransferPermit2ToProtocol
|
TransferType::TransferPermit2ToProtocol
|
||||||
} else {
|
} else {
|
||||||
// Transfer from swapper to pool.
|
// Transfer from swapper to pool.
|
||||||
TransferType::TransferFromToProtocol
|
TransferType::TransferFromToProtocol
|
||||||
}
|
}
|
||||||
} else if permit2 {
|
// in transfer is not necessary for these protocols. Only make a transfer if the
|
||||||
// Transfer from swapper to router using permit2.
|
// tokens are not already in the router
|
||||||
TransferType::TransferPermit2ToRouter
|
} 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
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Transfer from swapper to router.
|
TransferType::None
|
||||||
TransferType::TransferFromToRouter
|
|
||||||
}
|
}
|
||||||
// all other swaps
|
// all other swaps
|
||||||
} else if !in_transfer_required || in_between_swap_optimization {
|
} else if !in_transfer_required || in_between_swap_optimization {
|
||||||
@@ -54,17 +90,50 @@ pub trait TransferOptimization {
|
|||||||
TransferType::TransferToProtocol
|
TransferType::TransferToProtocol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the optimized receiver of the swap. This is used to chain swaps together and avoid
|
||||||
|
// unnecessary token transfers.
|
||||||
|
// Returns the receiver address and a boolean indicating whether the receiver is optimized (this
|
||||||
|
// is necessary for the next swap transfer type decision).
|
||||||
|
pub fn get_receiver(
|
||||||
|
&self,
|
||||||
|
solution_receiver: Bytes,
|
||||||
|
next_swap: Option<&SwapGroup>,
|
||||||
|
) -> Result<(Bytes, bool), EncodingError> {
|
||||||
|
if let Some(next) = next_swap {
|
||||||
|
// if the protocol of the next swap supports transfer in optimization
|
||||||
|
if IN_TRANSFER_REQUIRED_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 CALLBACK_CONSTRAINED_PROTOCOLS.contains(&next.protocol_system.as_str()) {
|
||||||
|
Ok((self.router_address.clone(), false))
|
||||||
|
} else {
|
||||||
|
Ok((
|
||||||
|
Bytes::from_str(&next.swaps[0].component.id.clone()).map_err(|_| {
|
||||||
|
EncodingError::FatalError("Invalid component id".to_string())
|
||||||
|
})?,
|
||||||
|
true,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the protocol of the next swap does not support transfer in optimization
|
||||||
|
Ok((self.router_address.clone(), false))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// last swap - there is no next swap
|
||||||
|
Ok((solution_receiver, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use alloy_primitives::hex;
|
use alloy_primitives::hex;
|
||||||
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
use rstest::rstest;
|
||||||
|
use tycho_common::models::protocol::ProtocolComponent;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::encoding::models::Swap;
|
||||||
struct MockStrategy {}
|
|
||||||
impl TransferOptimization for MockStrategy {}
|
|
||||||
|
|
||||||
fn weth() -> Bytes {
|
fn weth() -> Bytes {
|
||||||
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
|
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
|
||||||
@@ -82,39 +151,37 @@ mod tests {
|
|||||||
Bytes::from(hex!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").to_vec())
|
Bytes::from(hex!("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn router_address() -> Bytes {
|
||||||
|
Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_first_swap_transfer_from_permit2() {
|
fn test_first_swap_transfer_from_permit2() {
|
||||||
// The swap token is the same as the given token, which is not the native token
|
// The swap token is the same as the given token, which is not the native token
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: weth(),
|
token_in: weth(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), true, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), true, false, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol);
|
assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_first_swap_transfer_from() {
|
fn test_first_swap_transfer_from() {
|
||||||
// The swap token is the same as the given token, which is not the native token
|
// The swap token is the same as the given token, which is not the native token
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: weth(),
|
token_in: weth(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::TransferFromToProtocol);
|
assert_eq!(transfer_method, TransferType::TransferFromToProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,18 +189,15 @@ mod tests {
|
|||||||
fn test_first_swap_native() {
|
fn test_first_swap_native() {
|
||||||
// The swap token is the same as the given token, and it's the native token.
|
// The swap token is the same as the given token, and it's the native token.
|
||||||
// No transfer action is needed.
|
// No transfer action is needed.
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: eth(),
|
token_in: eth(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false);
|
||||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, false, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::None);
|
assert_eq!(transfer_method, TransferType::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,18 +205,15 @@ mod tests {
|
|||||||
fn test_first_swap_wrapped() {
|
fn test_first_swap_wrapped() {
|
||||||
// The swap token is NOT the same as the given token, but we are wrapping.
|
// 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.
|
// Since the swap's token in is the wrapped token - this is the first swap.
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: weth(),
|
token_in: weth(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false);
|
||||||
strategy.get_transfer_type(swap.clone(), eth(), eth(), weth(), false, true, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,18 +221,15 @@ mod tests {
|
|||||||
fn test_not_first_swap() {
|
fn test_not_first_swap() {
|
||||||
// The swap token is NOT the same as the given token, and we are NOT wrapping.
|
// The swap token is NOT the same as the given token, and we are NOT wrapping.
|
||||||
// Thus, this is not the first swap.
|
// Thus, this is not the first swap.
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: usdc(),
|
token_in: usdc(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
assert_eq!(transfer_method, TransferType::TransferToProtocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,18 +237,15 @@ mod tests {
|
|||||||
fn test_not_first_swap_funds_in_router() {
|
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
|
// 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)
|
// already are, so the transfer type is None)
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "vm:curve".to_string(),
|
||||||
protocol_system: "vm:curve".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: usdc(),
|
token_in: usdc(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false);
|
||||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, false);
|
|
||||||
assert_eq!(transfer_method, TransferType::None);
|
assert_eq!(transfer_method, TransferType::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,18 +253,100 @@ mod tests {
|
|||||||
fn test_not_first_swap_in_between_swap_optimization() {
|
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
|
// Not the first swap and the in between swaps are optimized. The funds should already be in
|
||||||
// the next pool or in the router
|
// the next pool or in the router
|
||||||
let swap = Swap {
|
let swap = SwapGroup {
|
||||||
component: ProtocolComponent {
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: usdc(),
|
token_in: usdc(),
|
||||||
token_out: dai(),
|
token_out: dai(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
|
swaps: vec![],
|
||||||
};
|
};
|
||||||
let strategy = MockStrategy {};
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
let transfer_method =
|
let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true);
|
||||||
strategy.get_transfer_type(swap.clone(), weth(), eth(), weth(), false, false, true);
|
|
||||||
assert_eq!(transfer_method, TransferType::None);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receiver() -> Bytes {
|
||||||
|
Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn component_id() -> Bytes {
|
||||||
|
Bytes::from("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
// there is no next swap -> receiver is the solution receiver
|
||||||
|
#[case(None, receiver(), false)]
|
||||||
|
// protocol of next swap supports transfer in optimization
|
||||||
|
#[case(Some("uniswap_v2"), component_id(), true)]
|
||||||
|
// protocol of next swap supports transfer in optimization but is callback constrained
|
||||||
|
#[case(Some("uniswap_v3"), router_address(), false)]
|
||||||
|
// protocol of next swap does not support transfer in optimization
|
||||||
|
#[case(Some("vm:curve"), router_address(), false)]
|
||||||
|
fn test_get_receiver(
|
||||||
|
#[case] protocol: Option<&str>,
|
||||||
|
#[case] expected_receiver: Bytes,
|
||||||
|
#[case] expected_optimization: bool,
|
||||||
|
) {
|
||||||
|
let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address());
|
||||||
|
|
||||||
|
let next_swap = if protocol.is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(SwapGroup {
|
||||||
|
protocol_system: protocol.unwrap().to_string(),
|
||||||
|
token_in: usdc(),
|
||||||
|
token_out: dai(),
|
||||||
|
split: 0f64,
|
||||||
|
swaps: vec![Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
protocol_system: protocol.unwrap().to_string(),
|
||||||
|
id: component_id().to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc(),
|
||||||
|
token_out: dai(),
|
||||||
|
split: 0f64,
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = optimization.get_receiver(receiver(), next_swap.as_ref());
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let (actual_receiver, optimization_flag) = result.unwrap();
|
||||||
|
assert_eq!(actual_receiver, expected_receiver);
|
||||||
|
assert_eq!(optimization_flag, expected_optimization);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ impl TychoRouterEncoder {
|
|||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
swapper_pk: Option<String>,
|
swapper_pk: Option<String>,
|
||||||
router_address: Bytes,
|
router_address: Bytes,
|
||||||
|
token_in_already_in_router: bool,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> Result<Self, EncodingError> {
|
||||||
let native_address = chain.native_token()?;
|
let native_address = chain.native_token()?;
|
||||||
let wrapped_address = chain.wrapped_token()?;
|
let wrapped_address = chain.wrapped_token()?;
|
||||||
@@ -48,18 +49,21 @@ impl TychoRouterEncoder {
|
|||||||
swap_encoder_registry.clone(),
|
swap_encoder_registry.clone(),
|
||||||
swapper_pk.clone(),
|
swapper_pk.clone(),
|
||||||
router_address.clone(),
|
router_address.clone(),
|
||||||
|
token_in_already_in_router,
|
||||||
)?,
|
)?,
|
||||||
sequential_swap_strategy: SequentialSwapStrategyEncoder::new(
|
sequential_swap_strategy: SequentialSwapStrategyEncoder::new(
|
||||||
chain.clone(),
|
chain.clone(),
|
||||||
swap_encoder_registry.clone(),
|
swap_encoder_registry.clone(),
|
||||||
swapper_pk.clone(),
|
swapper_pk.clone(),
|
||||||
router_address.clone(),
|
router_address.clone(),
|
||||||
|
token_in_already_in_router,
|
||||||
)?,
|
)?,
|
||||||
split_swap_strategy: SplitSwapStrategyEncoder::new(
|
split_swap_strategy: SplitSwapStrategyEncoder::new(
|
||||||
chain,
|
chain,
|
||||||
swap_encoder_registry,
|
swap_encoder_registry,
|
||||||
None,
|
None,
|
||||||
router_address.clone(),
|
router_address.clone(),
|
||||||
|
token_in_already_in_router,
|
||||||
)?,
|
)?,
|
||||||
native_address,
|
native_address,
|
||||||
wrapped_address,
|
wrapped_address,
|
||||||
@@ -258,8 +262,8 @@ impl TychoExecutorEncoder {
|
|||||||
receiver: receiver.clone(),
|
receiver: receiver.clone(),
|
||||||
exact_out: solution.exact_out,
|
exact_out: solution.exact_out,
|
||||||
router_address: None,
|
router_address: None,
|
||||||
group_token_in: grouped_swap.input_token.clone(),
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
group_token_out: grouped_swap.output_token.clone(),
|
group_token_out: grouped_swap.token_out.clone(),
|
||||||
transfer_type: TransferType::TransferToProtocol,
|
transfer_type: TransferType::TransferToProtocol,
|
||||||
};
|
};
|
||||||
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
||||||
@@ -354,6 +358,7 @@ mod tests {
|
|||||||
get_swap_encoder_registry(),
|
get_swap_encoder_registry(),
|
||||||
None,
|
None,
|
||||||
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user