Merge pull request #33 from propeller-heads/encoder/hr/ENG-4082-ExecutorEncoder
feat: update ExecutorEncoder and test module with contract
This commit is contained in:
4
Cargo.lock
generated
4
Cargo.lock
generated
@@ -2941,7 +2941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.13.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
@@ -3011,7 +3011,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants {
|
|||||||
assertGe(amountOut, 0);
|
assertGe(amountOut, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSwap() public {
|
function testSwapUniswapV2() public {
|
||||||
uint256 amountIn = 10 ** 18;
|
uint256 amountIn = 10 ** 18;
|
||||||
uint256 amountOut = 1847751195973566072891;
|
uint256 amountOut = 1847751195973566072891;
|
||||||
bool zeroForOne = false;
|
bool zeroForOne = false;
|
||||||
@@ -93,4 +93,31 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants {
|
|||||||
uint256 finalBalance = DAI.balanceOf(BOB);
|
uint256 finalBalance = DAI.balanceOf(BOB);
|
||||||
assertGe(finalBalance, amountOut);
|
assertGe(finalBalance, amountOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testSwapExecutorEncoderData() public {
|
||||||
|
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
|
||||||
|
bytes memory protocolData =
|
||||||
|
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f5640000000000000000000000000000000000000000100";
|
||||||
|
|
||||||
|
(IERC20 tokenIn, address target, address receiver, bool zeroForOne) =
|
||||||
|
uniswapV2Exposed.decodeParams(protocolData);
|
||||||
|
|
||||||
|
assertEq(address(tokenIn), WETH_ADDR);
|
||||||
|
assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
|
||||||
|
assertEq(receiver, 0x0000000000000000000000000000000000000001);
|
||||||
|
assertEq(zeroForOne, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSwapExecutorSwap() public {
|
||||||
|
// Generated by the ExecutorStrategyEncoder - test_executor_strategy_encode
|
||||||
|
bytes memory protocolData =
|
||||||
|
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00";
|
||||||
|
uint256 amountIn = 10 ** 18;
|
||||||
|
uint256 amountOut = 1847751195973566072891;
|
||||||
|
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
|
||||||
|
uniswapV2Exposed.swap(amountIn, protocolData);
|
||||||
|
|
||||||
|
uint256 finalBalance = DAI.balanceOf(BOB);
|
||||||
|
assertGe(finalBalance, amountOut);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRo
|
|||||||
&self,
|
&self,
|
||||||
solutions: Vec<Solution>,
|
solutions: Vec<Solution>,
|
||||||
) -> Result<Vec<Transaction>, EncodingError> {
|
) -> Result<Vec<Transaction>, EncodingError> {
|
||||||
let _approvals_calldata = self.handle_approvals(&solutions)?; // TODO: where should we append this?
|
let _approvals_calldata = self.handle_approvals(&solutions)?;
|
||||||
let mut transactions: Vec<Transaction> = Vec::new();
|
let mut transactions: Vec<Transaction> = Vec::new();
|
||||||
for solution in solutions.iter() {
|
for solution in solutions.iter() {
|
||||||
let exact_out = solution.exact_out;
|
let exact_out = solution.exact_out;
|
||||||
let straight_to_pool = solution.straight_to_pool;
|
let straight_to_pool = solution.direct_execution;
|
||||||
|
|
||||||
let strategy = self
|
let strategy = self
|
||||||
.strategy_selector
|
.strategy_selector
|
||||||
.select_strategy(solution);
|
.select_strategy(solution);
|
||||||
let method_calldata = strategy.encode_strategy((*solution).clone())?;
|
let (method_calldata, target_address) =
|
||||||
|
strategy.encode_strategy((*solution).clone())?;
|
||||||
|
|
||||||
let contract_interaction = if straight_to_pool {
|
let contract_interaction = if straight_to_pool {
|
||||||
method_calldata
|
method_calldata
|
||||||
@@ -52,7 +53,11 @@ impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRo
|
|||||||
} else {
|
} else {
|
||||||
BigUint::ZERO
|
BigUint::ZERO
|
||||||
};
|
};
|
||||||
transactions.push(Transaction { value, data: contract_interaction });
|
transactions.push(Transaction {
|
||||||
|
value,
|
||||||
|
data: contract_interaction,
|
||||||
|
to: target_address,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Ok(transactions)
|
Ok(transactions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use alloy_primitives::Address;
|
use alloy_primitives::Address;
|
||||||
use alloy_sol_types::SolValue;
|
use alloy_sol_types::SolValue;
|
||||||
|
use tycho_core::Bytes;
|
||||||
|
|
||||||
use crate::encoding::{
|
use crate::encoding::{
|
||||||
errors::EncodingError,
|
errors::EncodingError,
|
||||||
@@ -27,7 +30,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder {
|
|||||||
pub struct SplitSwapStrategyEncoder {}
|
pub struct SplitSwapStrategyEncoder {}
|
||||||
impl EVMStrategyEncoder for SplitSwapStrategyEncoder {}
|
impl EVMStrategyEncoder for SplitSwapStrategyEncoder {}
|
||||||
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, _solution: Solution) -> Result<Vec<u8>, EncodingError> {
|
fn encode_strategy(&self, _solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
fn selector(&self, _exact_out: bool) -> &str {
|
fn selector(&self, _exact_out: bool) -> &str {
|
||||||
@@ -37,10 +40,10 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
|
|
||||||
/// This strategy encoder is used for solutions that are sent directly to the pool.
|
/// This strategy encoder is used for solutions that are sent directly to the pool.
|
||||||
/// Only 1 solution with 1 swap is supported.
|
/// Only 1 solution with 1 swap is supported.
|
||||||
pub struct StraightToPoolStrategyEncoder {}
|
pub struct ExecutorStrategyEncoder {}
|
||||||
impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {}
|
impl EVMStrategyEncoder for ExecutorStrategyEncoder {}
|
||||||
impl StrategyEncoder for StraightToPoolStrategyEncoder {
|
impl StrategyEncoder for ExecutorStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<Vec<u8>, EncodingError> {
|
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||||
if solution.router_address.is_none() {
|
if solution.router_address.is_none() {
|
||||||
return Err(EncodingError::InvalidInput(
|
return Err(EncodingError::InvalidInput(
|
||||||
"Router address is required for straight to pool solutions".to_string(),
|
"Router address is required for straight to pool solutions".to_string(),
|
||||||
@@ -68,10 +71,85 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder {
|
|||||||
router_address,
|
router_address,
|
||||||
};
|
};
|
||||||
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?;
|
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context)?;
|
||||||
// TODO: here we need to pass also the address of the executor to be used
|
let executor_address = Bytes::from_str(swap_encoder.executor_address())
|
||||||
Ok(protocol_data)
|
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
|
||||||
|
Ok((protocol_data, executor_address))
|
||||||
}
|
}
|
||||||
fn selector(&self, _exact_out: bool) -> &str {
|
fn selector(&self, _exact_out: bool) -> &str {
|
||||||
unimplemented!();
|
"swap(uint256, bytes)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloy::hex::encode;
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use tycho_core::{dto::ProtocolComponent, Bytes};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::encoding::models::Swap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_executor_strategy_encode() {
|
||||||
|
let encoder = ExecutorStrategyEncoder {};
|
||||||
|
|
||||||
|
let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
|
||||||
|
let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f");
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from(1000000000000000000u64),
|
||||||
|
expected_amount: BigUint::from(1000000000000000000u64),
|
||||||
|
checked_token: token_out,
|
||||||
|
check_amount: None,
|
||||||
|
sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
|
||||||
|
// The receiver was generated with `makeAddr("bob") using forge`
|
||||||
|
receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
direct_execution: true,
|
||||||
|
router_address: Some(Bytes::zero(20)),
|
||||||
|
slippage: None,
|
||||||
|
native_action: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (protocol_data, executor_address) = encoder
|
||||||
|
.encode_strategy(solution)
|
||||||
|
.unwrap();
|
||||||
|
let hex_protocol_data = encode(&protocol_data);
|
||||||
|
assert_eq!(
|
||||||
|
executor_address,
|
||||||
|
Bytes::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
hex_protocol_data,
|
||||||
|
String::from(concat!(
|
||||||
|
// in token
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||||
|
// component id
|
||||||
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11",
|
||||||
|
// receiver
|
||||||
|
"1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e",
|
||||||
|
// zero for one
|
||||||
|
"00",
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_selector() {
|
||||||
|
let encoder = ExecutorStrategyEncoder {};
|
||||||
|
assert_eq!(encoder.selector(false), "swap(uint256, bytes)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::encoding::{
|
use crate::encoding::{
|
||||||
evm::strategy_encoder::encoder::{SplitSwapStrategyEncoder, StraightToPoolStrategyEncoder},
|
evm::strategy_encoder::encoder::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder},
|
||||||
models::Solution,
|
models::Solution,
|
||||||
strategy_encoder::{StrategyEncoder, StrategySelector},
|
strategy_encoder::{StrategyEncoder, StrategySelector},
|
||||||
};
|
};
|
||||||
@@ -8,8 +8,8 @@ pub struct EVMStrategySelector;
|
|||||||
|
|
||||||
impl StrategySelector for EVMStrategySelector {
|
impl StrategySelector for EVMStrategySelector {
|
||||||
fn select_strategy(&self, solution: &Solution) -> Box<dyn StrategyEncoder> {
|
fn select_strategy(&self, solution: &Solution) -> Box<dyn StrategyEncoder> {
|
||||||
if solution.straight_to_pool {
|
if solution.direct_execution {
|
||||||
Box::new(StraightToPoolStrategyEncoder {})
|
Box::new(ExecutorStrategyEncoder {})
|
||||||
} else {
|
} else {
|
||||||
Box::new(SplitSwapStrategyEncoder {})
|
Box::new(SplitSwapStrategyEncoder {})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ impl SwapEncoder for UniswapV2SwapEncoder {
|
|||||||
component_id,
|
component_id,
|
||||||
bytes_to_address(&encoding_context.receiver)?,
|
bytes_to_address(&encoding_context.receiver)?,
|
||||||
zero_to_one,
|
zero_to_one,
|
||||||
encoding_context.exact_out,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(args.abi_encode_packed())
|
Ok(args.abi_encode_packed())
|
||||||
@@ -211,8 +210,6 @@ mod tests {
|
|||||||
"0000000000000000000000000000000000000001",
|
"0000000000000000000000000000000000000001",
|
||||||
// zero for one
|
// zero for one
|
||||||
"00",
|
"00",
|
||||||
// exact out
|
|
||||||
"00",
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub struct Solution {
|
|||||||
/// Amount of the given token.
|
/// Amount of the given token.
|
||||||
pub given_amount: BigUint,
|
pub given_amount: BigUint,
|
||||||
/// The token being bought (exact in) or sold (exact out).
|
/// The token being bought (exact in) or sold (exact out).
|
||||||
checked_token: Bytes,
|
pub checked_token: Bytes,
|
||||||
/// Expected amount of the bought token (exact in) or sold token (exact out).
|
/// Expected amount of the bought token (exact in) or sold token (exact out).
|
||||||
pub expected_amount: BigUint,
|
pub expected_amount: BigUint,
|
||||||
/// Minimum amount to be checked for the solution to be valid.
|
/// Minimum amount to be checked for the solution to be valid.
|
||||||
@@ -23,10 +23,10 @@ pub struct Solution {
|
|||||||
pub receiver: Bytes,
|
pub receiver: Bytes,
|
||||||
/// List of swaps to fulfill the solution.
|
/// List of swaps to fulfill the solution.
|
||||||
pub swaps: Vec<Swap>,
|
pub swaps: Vec<Swap>,
|
||||||
/// If set to true, the solution will be encoded to be sent directly to the SwapExecutor and
|
/// If set to true, the solution will be encoded to be sent directly to the Executor and
|
||||||
/// skip the router. The user is responsible for managing necessary approvals and token
|
/// skip the router. The user is responsible for managing necessary approvals and token
|
||||||
/// transfers.
|
/// transfers.
|
||||||
pub straight_to_pool: bool,
|
pub direct_execution: bool,
|
||||||
// if not set, then the Propeller Router will be used
|
// if not set, then the Propeller Router will be used
|
||||||
pub router_address: Option<Bytes>,
|
pub router_address: Option<Bytes>,
|
||||||
// if set, it will be applied to check_amount
|
// if set, it will be applied to check_amount
|
||||||
@@ -60,6 +60,8 @@ pub struct Transaction {
|
|||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
// ETH value to be sent with the transaction.
|
// ETH value to be sent with the transaction.
|
||||||
pub value: BigUint,
|
pub value: BigUint,
|
||||||
|
// Address of the contract to call with the calldata
|
||||||
|
pub to: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
use tycho_core::Bytes;
|
||||||
|
|
||||||
use crate::encoding::{errors::EncodingError, models::Solution};
|
use crate::encoding::{errors::EncodingError, models::Solution};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub trait StrategyEncoder {
|
pub trait StrategyEncoder {
|
||||||
fn encode_strategy(&self, to_encode: Solution) -> Result<Vec<u8>, EncodingError>;
|
fn encode_strategy(&self, to_encode: Solution) -> Result<(Vec<u8>, Bytes), EncodingError>;
|
||||||
fn selector(&self, exact_out: bool) -> &str;
|
fn selector(&self, exact_out: bool) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user