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"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.13.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
@@ -3011,7 +3011,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -80,7 +80,7 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants {
|
||||
assertGe(amountOut, 0);
|
||||
}
|
||||
|
||||
function testSwap() public {
|
||||
function testSwapUniswapV2() public {
|
||||
uint256 amountIn = 10 ** 18;
|
||||
uint256 amountOut = 1847751195973566072891;
|
||||
bool zeroForOne = false;
|
||||
@@ -93,4 +93,31 @@ contract UniswapV2ExecutorTest is UniswapV2ExecutorExposed, Test, Constants {
|
||||
uint256 finalBalance = DAI.balanceOf(BOB);
|
||||
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,
|
||||
solutions: Vec<Solution>,
|
||||
) -> 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();
|
||||
for solution in solutions.iter() {
|
||||
let exact_out = solution.exact_out;
|
||||
let straight_to_pool = solution.straight_to_pool;
|
||||
let straight_to_pool = solution.direct_execution;
|
||||
|
||||
let strategy = self
|
||||
.strategy_selector
|
||||
.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 {
|
||||
method_calldata
|
||||
@@ -52,7 +53,11 @@ impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRo
|
||||
} else {
|
||||
BigUint::ZERO
|
||||
};
|
||||
transactions.push(Transaction { value, data: contract_interaction });
|
||||
transactions.push(Transaction {
|
||||
value,
|
||||
data: contract_interaction,
|
||||
to: target_address,
|
||||
});
|
||||
}
|
||||
Ok(transactions)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use alloy_primitives::Address;
|
||||
use alloy_sol_types::SolValue;
|
||||
use tycho_core::Bytes;
|
||||
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
@@ -27,7 +30,7 @@ pub trait EVMStrategyEncoder: StrategyEncoder {
|
||||
pub struct SplitSwapStrategyEncoder {}
|
||||
impl EVMStrategyEncoder 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!()
|
||||
}
|
||||
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.
|
||||
/// Only 1 solution with 1 swap is supported.
|
||||
pub struct StraightToPoolStrategyEncoder {}
|
||||
impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {}
|
||||
impl StrategyEncoder for StraightToPoolStrategyEncoder {
|
||||
fn encode_strategy(&self, solution: Solution) -> Result<Vec<u8>, EncodingError> {
|
||||
pub struct ExecutorStrategyEncoder {}
|
||||
impl EVMStrategyEncoder for ExecutorStrategyEncoder {}
|
||||
impl StrategyEncoder for ExecutorStrategyEncoder {
|
||||
fn encode_strategy(&self, solution: Solution) -> Result<(Vec<u8>, Bytes), EncodingError> {
|
||||
if solution.router_address.is_none() {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"Router address is required for straight to pool solutions".to_string(),
|
||||
@@ -68,10 +71,85 @@ impl StrategyEncoder for StraightToPoolStrategyEncoder {
|
||||
router_address,
|
||||
};
|
||||
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
|
||||
Ok(protocol_data)
|
||||
let executor_address = Bytes::from_str(swap_encoder.executor_address())
|
||||
.map_err(|_| EncodingError::FatalError("Invalid executor address".to_string()))?;
|
||||
Ok((protocol_data, executor_address))
|
||||
}
|
||||
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::{
|
||||
evm::strategy_encoder::encoder::{SplitSwapStrategyEncoder, StraightToPoolStrategyEncoder},
|
||||
evm::strategy_encoder::encoder::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder},
|
||||
models::Solution,
|
||||
strategy_encoder::{StrategyEncoder, StrategySelector},
|
||||
};
|
||||
@@ -8,8 +8,8 @@ pub struct EVMStrategySelector;
|
||||
|
||||
impl StrategySelector for EVMStrategySelector {
|
||||
fn select_strategy(&self, solution: &Solution) -> Box<dyn StrategyEncoder> {
|
||||
if solution.straight_to_pool {
|
||||
Box::new(StraightToPoolStrategyEncoder {})
|
||||
if solution.direct_execution {
|
||||
Box::new(ExecutorStrategyEncoder {})
|
||||
} else {
|
||||
Box::new(SplitSwapStrategyEncoder {})
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ impl SwapEncoder for UniswapV2SwapEncoder {
|
||||
component_id,
|
||||
bytes_to_address(&encoding_context.receiver)?,
|
||||
zero_to_one,
|
||||
encoding_context.exact_out,
|
||||
);
|
||||
|
||||
Ok(args.abi_encode_packed())
|
||||
@@ -211,8 +210,6 @@ mod tests {
|
||||
"0000000000000000000000000000000000000001",
|
||||
// zero for one
|
||||
"00",
|
||||
// exact out
|
||||
"00",
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub struct Solution {
|
||||
/// Amount of the given token.
|
||||
pub given_amount: BigUint,
|
||||
/// 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).
|
||||
pub expected_amount: BigUint,
|
||||
/// Minimum amount to be checked for the solution to be valid.
|
||||
@@ -23,10 +23,10 @@ pub struct Solution {
|
||||
pub receiver: Bytes,
|
||||
/// List of swaps to fulfill the solution.
|
||||
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
|
||||
/// transfers.
|
||||
pub straight_to_pool: bool,
|
||||
pub direct_execution: bool,
|
||||
// if not set, then the Propeller Router will be used
|
||||
pub router_address: Option<Bytes>,
|
||||
// if set, it will be applied to check_amount
|
||||
@@ -60,6 +60,8 @@ pub struct Transaction {
|
||||
pub data: Vec<u8>,
|
||||
// ETH value to be sent with the transaction.
|
||||
pub value: BigUint,
|
||||
// Address of the contract to call with the calldata
|
||||
pub to: Bytes,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use tycho_core::Bytes;
|
||||
|
||||
use crate::encoding::{errors::EncodingError, models::Solution};
|
||||
|
||||
#[allow(dead_code)]
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user