Merge branch 'main' into router/dc/return-eth
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
## [0.25.1](https://github.com/propeller-heads/tycho-execution/compare/0.25.0...0.25.1) (2025-01-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix selector - shouldn't contain spaces ([5d6f0c1](https://github.com/propeller-heads/tycho-execution/commit/5d6f0c1673932e55c1ba64b25a02a51426328e3e))
|
||||||
|
* Fix token index order in strategy encoding. ([c85c353](https://github.com/propeller-heads/tycho-execution/commit/c85c353e344a1fec4a2fbcd5b460b37f2edfc91e))
|
||||||
|
|
||||||
## [0.25.0](https://github.com/propeller-heads/tycho-execution/compare/0.24.0...0.25.0) (2025-01-31)
|
## [0.25.0](https://github.com/propeller-heads/tycho-execution/compare/0.24.0...0.25.0) (2025-01-31)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4163,7 +4163,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tycho-execution"
|
name = "tycho-execution"
|
||||||
version = "0.25.0"
|
version = "0.25.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tycho-execution"
|
name = "tycho-execution"
|
||||||
version = "0.25.0"
|
version = "0.25.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ contract TychoRouter is
|
|||||||
currentAmountIn = split > 0
|
currentAmountIn = split > 0
|
||||||
? (amounts[tokenInIndex] * split) / 0xffffff
|
? (amounts[tokenInIndex] * split) / 0xffffff
|
||||||
: remainingAmounts[tokenInIndex];
|
: remainingAmounts[tokenInIndex];
|
||||||
|
|
||||||
currentAmountOut = _callExecutor(
|
currentAmountOut = _callExecutor(
|
||||||
swapData.executor(),
|
swapData.executor(),
|
||||||
swapData.executorSelector(),
|
swapData.executorSelector(),
|
||||||
|
|||||||
@@ -671,4 +671,30 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr);
|
uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(tychoRouterAddr);
|
||||||
assertGe(finalBalance, expAmountOut);
|
assertGe(finalBalance, expAmountOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testSingleSwapIntegration() public {
|
||||||
|
// Test created with calldata from our router encoder, replacing the executor
|
||||||
|
// address with the USV2 executor address.
|
||||||
|
|
||||||
|
// Tests swapping WETH -> DAI on a USV2 pool
|
||||||
|
deal(WETH_ADDR, ALICE, 1 ether);
|
||||||
|
uint256 balancerBefore = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
// Approve permit2
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(WETH_ADDR).approve(address(permit2Address), type(uint256).max);
|
||||||
|
// Encoded solution generated using `test_split_swap_strategy_encoder` but
|
||||||
|
// manually replacing the executor address `5c2f5a71f67c01775180adc06909288b4c329308`
|
||||||
|
// with the one in this test `5615deb798bb3e4dfa0139dfa1b3d433cc23b72f`
|
||||||
|
(bool success,) = tychoRouterAddr.call(
|
||||||
|
hex"4860f9ed0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000067c43ba900000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000679cb5b10000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415bfd02ffd61c11192d1b54d76e0af125afbb32568aad37ec35f918bd5fb304cd314954213ed77c0d071301ddc45243ad57e86fe18f2905b682acc4f1a43ad8dc1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005c005a00010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fbd0625abc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000000000"
|
||||||
|
);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
uint256 balancerAfter = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertGt(balancerAfter, balancerBefore);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use thiserror::Error;
|
|||||||
/// - `RecoverableError`: Indicates that the encoding has failed with a recoverable error. Retrying
|
/// - `RecoverableError`: Indicates that the encoding has failed with a recoverable error. Retrying
|
||||||
/// at a later time may succeed. It may have failed due to a temporary issue, such as a network
|
/// at a later time may succeed. It may have failed due to a temporary issue, such as a network
|
||||||
/// problem.
|
/// problem.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub enum EncodingError {
|
pub enum EncodingError {
|
||||||
#[error("Invalid input: {0}")]
|
#[error("Invalid input: {0}")]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pub mod approvals;
|
pub mod approvals;
|
||||||
mod models;
|
mod models;
|
||||||
mod router_encoder;
|
|
||||||
mod strategy_encoder;
|
mod strategy_encoder;
|
||||||
mod swap_encoder;
|
mod swap_encoder;
|
||||||
|
mod tycho_encoder;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use num_bigint::BigUint;
|
|
||||||
use tycho_core::{models::Chain, Bytes};
|
|
||||||
|
|
||||||
use crate::encoding::{
|
|
||||||
errors::EncodingError,
|
|
||||||
models::{NativeAction, Solution, Transaction},
|
|
||||||
router_encoder::RouterEncoder,
|
|
||||||
strategy_encoder::StrategySelector,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct EVMRouterEncoder<S: StrategySelector> {
|
|
||||||
strategy_selector: S,
|
|
||||||
signer: Option<String>,
|
|
||||||
chain: Chain,
|
|
||||||
router_address: Bytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl<S: StrategySelector> EVMRouterEncoder<S> {
|
|
||||||
pub fn new(
|
|
||||||
strategy_selector: S,
|
|
||||||
router_address: String,
|
|
||||||
signer: Option<String>,
|
|
||||||
chain: Chain,
|
|
||||||
) -> Result<Self, EncodingError> {
|
|
||||||
let router_address = Bytes::from_str(&router_address)
|
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?;
|
|
||||||
Ok(EVMRouterEncoder { strategy_selector, signer, chain, router_address })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<S: StrategySelector> RouterEncoder<S> for EVMRouterEncoder<S> {
|
|
||||||
fn encode_router_calldata(
|
|
||||||
&self,
|
|
||||||
solutions: Vec<Solution>,
|
|
||||||
) -> Result<Vec<Transaction>, EncodingError> {
|
|
||||||
let mut transactions: Vec<Transaction> = Vec::new();
|
|
||||||
for solution in solutions.iter() {
|
|
||||||
if solution.exact_out {
|
|
||||||
return Err(EncodingError::FatalError(
|
|
||||||
"Currently only exact input solutions are supported".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let router_address = solution
|
|
||||||
.router_address
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(self.router_address.clone());
|
|
||||||
let strategy = self.strategy_selector.select_strategy(
|
|
||||||
solution,
|
|
||||||
self.signer.clone(),
|
|
||||||
self.chain,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (contract_interaction, target_address) =
|
|
||||||
strategy.encode_strategy(solution.clone(), router_address)?;
|
|
||||||
|
|
||||||
let value = match solution.native_action.as_ref() {
|
|
||||||
Some(NativeAction::Wrap) => solution.given_amount.clone(),
|
|
||||||
_ => BigUint::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
transactions.push(Transaction {
|
|
||||||
value,
|
|
||||||
data: contract_interaction,
|
|
||||||
to: target_address,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(transactions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{cmp::max, str::FromStr};
|
use std::{cmp::max, collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
use alloy_primitives::{aliases::U24, map::HashSet, FixedBytes, U256, U8};
|
use alloy_primitives::{aliases::U24, FixedBytes, U256, U8};
|
||||||
use alloy_sol_types::SolValue;
|
use alloy_sol_types::SolValue;
|
||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
use tycho_core::{keccak256, models::Chain, Bytes};
|
use tycho_core::{keccak256, models::Chain, Bytes};
|
||||||
@@ -60,7 +60,7 @@ pub struct SplitSwapStrategyEncoder {
|
|||||||
|
|
||||||
impl SplitSwapStrategyEncoder {
|
impl SplitSwapStrategyEncoder {
|
||||||
pub fn new(signer_pk: String, chain: Chain) -> Result<Self, EncodingError> {
|
pub fn new(signer_pk: String, chain: Chain) -> Result<Self, EncodingError> {
|
||||||
let selector = "swap(uint256, address, address, uint256, bool, bool, uint256, address, ((address,uint160,uint48,uint48),address,uint256),bytes, bytes)".to_string();
|
let selector = "swap(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string();
|
||||||
Ok(Self { permit2: Permit2::new(signer_pk, chain)?, selector })
|
Ok(Self { permit2: Permit2::new(signer_pk, chain)?, selector })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,18 +88,30 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
min_amount_out = max(user_specified_min_amount, expected_amount_with_slippage);
|
min_amount_out = max(user_specified_min_amount, expected_amount_with_slippage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The tokens array is composed of the given token, the checked token and all the
|
||||||
|
// intermediary tokens in between. The contract expects the tokens to be in this order.
|
||||||
|
let solution_tokens: HashSet<Bytes> =
|
||||||
|
vec![solution.given_token.clone(), solution.checked_token.clone()]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut tokens: Vec<Bytes> = solution
|
let intermediary_tokens: HashSet<Bytes> = solution
|
||||||
.swaps
|
.swaps
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|swap| vec![swap.token_in.clone(), swap.token_out.clone()])
|
.flat_map(|swap| vec![swap.token_in.clone(), swap.token_out.clone()])
|
||||||
.collect::<HashSet<Bytes>>()
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
.collect();
|
||||||
|
let mut intermediary_tokens: Vec<Bytes> = intermediary_tokens
|
||||||
|
.difference(&solution_tokens)
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
// this is only to make the test deterministic (same index for the same token for different
|
// this is only to make the test deterministic (same index for the same token for different
|
||||||
// runs)
|
// runs)
|
||||||
tokens.sort();
|
intermediary_tokens.sort();
|
||||||
|
|
||||||
|
let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
|
||||||
|
tokens.push(solution.given_token.clone());
|
||||||
|
tokens.extend(intermediary_tokens);
|
||||||
|
tokens.push(solution.checked_token.clone());
|
||||||
|
|
||||||
let mut swaps = vec![];
|
let mut swaps = vec![];
|
||||||
for swap in solution.swaps.iter() {
|
for swap in solution.swaps.iter() {
|
||||||
@@ -299,14 +311,14 @@ mod tests {
|
|||||||
fn test_split_swap_strategy_encoder() {
|
fn test_split_swap_strategy_encoder() {
|
||||||
// Set up a mock private key for signing
|
// Set up a mock private key for signing
|
||||||
let private_key =
|
let private_key =
|
||||||
"4c0883a69102937d6231471b5dbb6204fe512961708279feb1be6ae5538da033".to_string();
|
"0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string();
|
||||||
|
|
||||||
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
|
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
|
||||||
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
|
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
|
||||||
|
|
||||||
let swap = Swap {
|
let swap = Swap {
|
||||||
component: ProtocolComponent {
|
component: ProtocolComponent {
|
||||||
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(),
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
protocol_system: "uniswap_v2".to_string(),
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -323,19 +335,19 @@ mod tests {
|
|||||||
checked_token: dai,
|
checked_token: dai,
|
||||||
expected_amount: BigUint::from_str("3_000_000000000000000000").unwrap(),
|
expected_amount: BigUint::from_str("3_000_000000000000000000").unwrap(),
|
||||||
check_amount: None,
|
check_amount: None,
|
||||||
sender: Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(),
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
receiver: Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap(),
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
swaps: vec![swap],
|
swaps: vec![swap],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let router_address = Bytes::from_str("0x2c6A3cd97c6283b95Ac8C5A4459eBB0d5Fd404F4").unwrap();
|
let router_address = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap();
|
||||||
|
|
||||||
let (calldata, _) = encoder
|
let (calldata, _) = encoder
|
||||||
.encode_strategy(solution, router_address)
|
.encode_strategy(solution, router_address)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected_input = String::from(concat!(
|
let expected_input = String::from(concat!(
|
||||||
"e73e3baa",
|
"4860f9ed",
|
||||||
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out
|
||||||
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
||||||
@@ -343,7 +355,7 @@ mod tests {
|
|||||||
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
|
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
|
||||||
"0000000000000000000000002c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // receiver
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
));
|
));
|
||||||
// after this there is the permit and because of the deadlines (that depend on block time)
|
// after this there is the permit and because of the deadlines (that depend on block time)
|
||||||
// it's hard to assert
|
// it's hard to assert
|
||||||
@@ -370,15 +382,15 @@ mod tests {
|
|||||||
// ple encoded swaps
|
// ple encoded swaps
|
||||||
"005a",
|
"005a",
|
||||||
// Swap header
|
// Swap header
|
||||||
"01", // token in index
|
"00", // token in index
|
||||||
"00", // token out index
|
"01", // token out index
|
||||||
"000000", // split
|
"000000", // split
|
||||||
// Swap data
|
// Swap data
|
||||||
"5c2f5a71f67c01775180adc06909288b4c329308", // executor address
|
"5c2f5a71f67c01775180adc06909288b4c329308", // executor address
|
||||||
"bd0625ab", // selector
|
"bd0625ab", // selector
|
||||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
|
||||||
"2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4", // receiver
|
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
|
||||||
"00", // zero2one
|
"00", // zero2one
|
||||||
"00", // exact out
|
"00", // exact out
|
||||||
"000000", // padding
|
"000000", // padding
|
||||||
|
|||||||
163
src/encoding/evm/tycho_encoder.rs
Normal file
163
src/encoding/evm/tycho_encoder.rs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use tycho_core::{models::Chain, Bytes};
|
||||||
|
|
||||||
|
use crate::encoding::{
|
||||||
|
errors::EncodingError,
|
||||||
|
models::{NativeAction, Solution, Transaction},
|
||||||
|
strategy_encoder::StrategySelector,
|
||||||
|
tycho_encoder::TychoEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct EVMTychoEncoder<S: StrategySelector> {
|
||||||
|
strategy_selector: S,
|
||||||
|
signer: Option<String>,
|
||||||
|
chain: Chain,
|
||||||
|
router_address: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl<S: StrategySelector> EVMTychoEncoder<S> {
|
||||||
|
pub fn new(
|
||||||
|
strategy_selector: S,
|
||||||
|
router_address: String,
|
||||||
|
signer: Option<String>,
|
||||||
|
chain: Chain,
|
||||||
|
) -> Result<Self, EncodingError> {
|
||||||
|
let router_address = Bytes::from_str(&router_address)
|
||||||
|
.map_err(|_| EncodingError::FatalError("Invalid router address".to_string()))?;
|
||||||
|
Ok(EVMTychoEncoder { strategy_selector, signer, chain, router_address })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<S: StrategySelector> TychoEncoder<S> for EVMTychoEncoder<S> {
|
||||||
|
fn encode_router_calldata(
|
||||||
|
&self,
|
||||||
|
solutions: Vec<Solution>,
|
||||||
|
) -> Result<Vec<Transaction>, EncodingError> {
|
||||||
|
let mut transactions: Vec<Transaction> = Vec::new();
|
||||||
|
for solution in solutions.iter() {
|
||||||
|
if solution.exact_out {
|
||||||
|
return Err(EncodingError::FatalError(
|
||||||
|
"Currently only exact input solutions are supported".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let router_address = solution
|
||||||
|
.router_address
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(self.router_address.clone());
|
||||||
|
|
||||||
|
let strategy = self.strategy_selector.select_strategy(
|
||||||
|
solution,
|
||||||
|
self.signer.clone(),
|
||||||
|
self.chain,
|
||||||
|
)?;
|
||||||
|
let (contract_interaction, target_address) =
|
||||||
|
strategy.encode_strategy(solution.clone(), router_address)?;
|
||||||
|
|
||||||
|
let value = match solution.native_action.as_ref() {
|
||||||
|
Some(NativeAction::Wrap) => solution.given_amount.clone(),
|
||||||
|
_ => BigUint::ZERO,
|
||||||
|
};
|
||||||
|
|
||||||
|
transactions.push(Transaction {
|
||||||
|
value,
|
||||||
|
data: contract_interaction,
|
||||||
|
to: target_address,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(transactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoding::strategy_encoder::StrategyEncoder;
|
||||||
|
|
||||||
|
struct MockStrategySelector;
|
||||||
|
|
||||||
|
impl StrategySelector for MockStrategySelector {
|
||||||
|
fn select_strategy(
|
||||||
|
&self,
|
||||||
|
_solution: &Solution,
|
||||||
|
_signer: Option<String>,
|
||||||
|
_chain: Chain,
|
||||||
|
) -> Result<Box<dyn StrategyEncoder>, EncodingError> {
|
||||||
|
Ok(Box::new(MockStrategy))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MockStrategy;
|
||||||
|
|
||||||
|
impl StrategyEncoder for MockStrategy {
|
||||||
|
fn encode_strategy(
|
||||||
|
&self,
|
||||||
|
_solution: Solution,
|
||||||
|
_router_address: Bytes,
|
||||||
|
) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
|
Ok((
|
||||||
|
Bytes::from_str("0x1234")
|
||||||
|
.unwrap()
|
||||||
|
.to_vec(),
|
||||||
|
Bytes::from_str("0xabcd").unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mocker_tycho_encoder() -> EVMTychoEncoder<MockStrategySelector> {
|
||||||
|
let strategy_selector = MockStrategySelector;
|
||||||
|
EVMTychoEncoder::new(
|
||||||
|
strategy_selector,
|
||||||
|
"0x1234567890abcdef1234567890abcdef12345678".to_string(),
|
||||||
|
Some("0xabcdef".to_string()),
|
||||||
|
Chain::Ethereum,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_router_calldata() {
|
||||||
|
let encoder = get_mocker_tycho_encoder();
|
||||||
|
|
||||||
|
let eth_amount_in = BigUint::from(1000u32);
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_amount: eth_amount_in.clone(),
|
||||||
|
router_address: None,
|
||||||
|
native_action: Some(NativeAction::Wrap),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let transactions = encoder.encode_router_calldata(vec![solution]);
|
||||||
|
|
||||||
|
assert!(transactions.is_ok());
|
||||||
|
let transactions = transactions.unwrap();
|
||||||
|
assert_eq!(transactions.len(), 1);
|
||||||
|
assert_eq!(transactions[0].value, eth_amount_in);
|
||||||
|
assert_eq!(transactions[0].data, Bytes::from_str("0x1234").unwrap());
|
||||||
|
assert_eq!(transactions[0].to, Bytes::from_str("0xabcd").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_router_calldata_fails_for_exact_out() {
|
||||||
|
let encoder = get_mocker_tycho_encoder();
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: true, // This should cause an error
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = encoder.encode_router_calldata(vec![solution]);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap(),
|
||||||
|
EncodingError::FatalError(
|
||||||
|
"Currently only exact input solutions are supported".to_string()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@ mod errors;
|
|||||||
#[cfg(feature = "evm")]
|
#[cfg(feature = "evm")]
|
||||||
mod evm;
|
mod evm;
|
||||||
mod models;
|
mod models;
|
||||||
mod router_encoder;
|
|
||||||
mod strategy_encoder;
|
mod strategy_encoder;
|
||||||
mod swap_encoder;
|
mod swap_encoder;
|
||||||
|
mod tycho_encoder;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::encoding::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub trait RouterEncoder<S: StrategySelector> {
|
pub trait TychoEncoder<S: StrategySelector> {
|
||||||
fn encode_router_calldata(
|
fn encode_router_calldata(
|
||||||
&self,
|
&self,
|
||||||
solutions: Vec<Solution>,
|
solutions: Vec<Solution>,
|
||||||
Reference in New Issue
Block a user