Merge branch 'main' into router/hr/ENG-4239-usv4-swap-encoder
This commit is contained in:
@@ -69,7 +69,7 @@ sol! {
|
||||
}
|
||||
|
||||
impl Permit2 {
|
||||
pub fn new(signer_pk: String, chain: Chain) -> Result<Self, EncodingError> {
|
||||
pub fn new(swapper_pk: String, chain: Chain) -> Result<Self, EncodingError> {
|
||||
let (handle, runtime) = match Handle::try_current() {
|
||||
Ok(h) => (h, None),
|
||||
Err(_) => {
|
||||
@@ -80,8 +80,8 @@ impl Permit2 {
|
||||
}
|
||||
};
|
||||
let client = block_in_place(|| handle.block_on(get_client()))?;
|
||||
let pk = B256::from_str(&signer_pk).map_err(|_| {
|
||||
EncodingError::FatalError("Failed to convert signer private key to B256".to_string())
|
||||
let pk = B256::from_str(&swapper_pk).map_err(|_| {
|
||||
EncodingError::FatalError("Failed to convert swapper private key to B256".to_string())
|
||||
})?;
|
||||
let signer = PrivateKeySigner::from_bytes(&pk).map_err(|_| {
|
||||
EncodingError::FatalError("Failed to create signer from private key".to_string())
|
||||
@@ -224,9 +224,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_existing_allowance() {
|
||||
let signer_pk =
|
||||
let swapper_pk =
|
||||
"4c0883a69102937d6231471b5dbb6204fe512961708279feb1be6ae5538da033".to_string();
|
||||
let manager = Permit2::new(signer_pk, eth_chain()).unwrap();
|
||||
let manager = Permit2::new(swapper_pk, eth_chain()).unwrap();
|
||||
|
||||
let token = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||
let owner = Bytes::from_str("0x2c6a3cd97c6283b95ac8c5a4459ebb0d5fd404f4").unwrap();
|
||||
|
||||
92
src/encoding/evm/encoder_builder.rs
Normal file
92
src/encoding/evm/encoder_builder.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use tycho_core::dto::Chain;
|
||||
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::{
|
||||
strategy_encoder::strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder},
|
||||
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
||||
tycho_encoder::EVMTychoEncoder,
|
||||
},
|
||||
strategy_encoder::StrategyEncoder,
|
||||
};
|
||||
|
||||
/// Builder pattern for constructing an `EVMTychoEncoder` with customizable options.
|
||||
///
|
||||
/// This struct allows setting a chain and strategy encoder before building the final encoder.
|
||||
pub struct EVMEncoderBuilder {
|
||||
strategy: Option<Box<dyn StrategyEncoder>>,
|
||||
chain: Option<Chain>,
|
||||
}
|
||||
|
||||
impl Default for EVMEncoderBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EVMEncoderBuilder {
|
||||
pub fn new() -> Self {
|
||||
EVMEncoderBuilder { chain: None, strategy: None }
|
||||
}
|
||||
pub fn chain(mut self, chain: Chain) -> Self {
|
||||
self.chain = Some(chain);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `strategy_encoder` manually.
|
||||
///
|
||||
/// **Note**: This method should not be used in combination with `tycho_router` or
|
||||
/// `direct_execution`.
|
||||
pub fn strategy_encoder(mut self, strategy: Box<dyn StrategyEncoder>) -> Self {
|
||||
self.strategy = Some(strategy);
|
||||
self
|
||||
}
|
||||
|
||||
/// Shortcut method to initialize a `SplitSwapStrategyEncoder` with a given `swapper_pk`.
|
||||
/// **Note**: Should not be used at the same time as `strategy_encoder`.
|
||||
pub fn tycho_router(
|
||||
self,
|
||||
swapper_pk: String,
|
||||
executors_file_path: Option<String>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
if let Some(chain) = self.chain {
|
||||
let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?;
|
||||
let strategy =
|
||||
Box::new(SplitSwapStrategyEncoder::new(swapper_pk, chain, swap_encoder_registry)?);
|
||||
Ok(EVMEncoderBuilder { chain: Some(chain), strategy: Some(strategy) })
|
||||
} else {
|
||||
Err(EncodingError::FatalError(
|
||||
"Please set the chain before setting the tycho router".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcut method to initialize an `ExecutorStrategyEncoder`.
|
||||
/// **Note**: Should not be used at the same time as `strategy_encoder`.
|
||||
pub fn direct_execution(
|
||||
self,
|
||||
executors_file_path: Option<String>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
if let Some(chain) = self.chain {
|
||||
let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?;
|
||||
let strategy = Box::new(ExecutorStrategyEncoder::new(swap_encoder_registry));
|
||||
Ok(EVMEncoderBuilder { chain: Some(chain), strategy: Some(strategy) })
|
||||
} else {
|
||||
Err(EncodingError::FatalError(
|
||||
"Please set the chain before setting the strategy".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the `EVMTychoEncoder` instance using the configured chain and strategy.
|
||||
/// Returns an error if either the chain or strategy has not been set.
|
||||
pub fn build(self) -> Result<EVMTychoEncoder, EncodingError> {
|
||||
if let (Some(chain), Some(strategy)) = (self.chain, self.strategy) {
|
||||
EVMTychoEncoder::new(chain, strategy)
|
||||
} else {
|
||||
Err(EncodingError::FatalError(
|
||||
"Please set the chain and strategy before building the encoder".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod approvals;
|
||||
mod constants;
|
||||
pub mod encoder_builder;
|
||||
pub mod strategy_encoder;
|
||||
mod swap_encoder;
|
||||
pub mod tycho_encoder;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
mod group_swaps;
|
||||
pub mod strategy_encoder_registry;
|
||||
mod strategy_encoders;
|
||||
pub mod strategy_encoders;
|
||||
mod strategy_validators;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
evm::{
|
||||
strategy_encoder::strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder},
|
||||
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
||||
},
|
||||
models::{Chain, Solution},
|
||||
strategy_encoder::{StrategyEncoder, StrategyEncoderRegistry},
|
||||
};
|
||||
|
||||
/// Contains all supported strategies to encode a solution.
|
||||
///
|
||||
/// # Fields
|
||||
/// * `strategies` - A hashmap containing the name of the strategy as a key and the strategy encoder
|
||||
/// as a value.
|
||||
pub struct EVMStrategyEncoderRegistry {
|
||||
strategies: HashMap<String, Box<dyn StrategyEncoder>>,
|
||||
}
|
||||
|
||||
impl StrategyEncoderRegistry for EVMStrategyEncoderRegistry {
|
||||
fn new(
|
||||
chain: tycho_core::dto::Chain,
|
||||
executors_file_path: Option<String>,
|
||||
signer_pk: Option<String>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let chain = Chain::from(chain);
|
||||
let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain.clone())?;
|
||||
|
||||
let mut strategies: HashMap<String, Box<dyn StrategyEncoder>> = HashMap::new();
|
||||
strategies.insert(
|
||||
"executor".to_string(),
|
||||
Box::new(ExecutorStrategyEncoder::new(swap_encoder_registry.clone())),
|
||||
);
|
||||
if let Some(signer) = signer_pk {
|
||||
strategies.insert(
|
||||
"split_swap".to_string(),
|
||||
Box::new(
|
||||
SplitSwapStrategyEncoder::new(signer, chain, swap_encoder_registry).unwrap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
Ok(Self { strategies })
|
||||
}
|
||||
fn get_encoder(&self, solution: &Solution) -> Result<&Box<dyn StrategyEncoder>, EncodingError> {
|
||||
if solution.direct_execution {
|
||||
self.strategies
|
||||
.get("executor")
|
||||
.ok_or(EncodingError::FatalError("Executor strategy not found".to_string()))
|
||||
} else {
|
||||
self.strategies
|
||||
.get("split_swap")
|
||||
.ok_or(EncodingError::FatalError("Split swap strategy not found. Please pass the signer private key to the StrategySelector constructor".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for EVMStrategyEncoderRegistry {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
strategies: self
|
||||
.strategies
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone_box()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,13 +89,14 @@ pub struct SplitSwapStrategyEncoder {
|
||||
|
||||
impl SplitSwapStrategyEncoder {
|
||||
pub fn new(
|
||||
signer_pk: String,
|
||||
chain: Chain,
|
||||
swapper_pk: String,
|
||||
blockchain: tycho_core::dto::Chain,
|
||||
swap_encoder_registry: SwapEncoderRegistry,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let chain = Chain::from(blockchain);
|
||||
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.clone())?,
|
||||
permit2: Permit2::new(swapper_pk, chain.clone())?,
|
||||
selector,
|
||||
swap_encoder_registry,
|
||||
native_address: chain.native_token()?,
|
||||
@@ -340,8 +341,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::encoding::models::Swap;
|
||||
|
||||
fn eth_chain() -> Chain {
|
||||
TychoCoreChain::Ethereum.into()
|
||||
fn eth_chain() -> TychoCoreChain {
|
||||
TychoCoreChain::Ethereum
|
||||
}
|
||||
|
||||
fn eth() -> Bytes {
|
||||
@@ -387,7 +388,6 @@ mod tests {
|
||||
// The receiver was generated with `makeAddr("bob") using forge`
|
||||
receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
|
||||
swaps: vec![swap],
|
||||
direct_execution: true,
|
||||
router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
slippage: None,
|
||||
native_action: None,
|
||||
@@ -445,7 +445,6 @@ mod tests {
|
||||
sender: Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(),
|
||||
receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
|
||||
swaps: vec![swap.clone(), swap],
|
||||
direct_execution: true,
|
||||
router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
slippage: None,
|
||||
native_action: None,
|
||||
@@ -496,7 +495,6 @@ mod tests {
|
||||
// The receiver was generated with `makeAddr("bob") using forge`
|
||||
receiver: Bytes::from_str("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e").unwrap(),
|
||||
swaps: vec![swap_a, swap_b],
|
||||
direct_execution: true,
|
||||
router_address: Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap(),
|
||||
slippage: None,
|
||||
native_action: None,
|
||||
|
||||
@@ -19,8 +19,9 @@ impl SwapEncoderRegistry {
|
||||
/// executors' addresses in the file at the given path.
|
||||
pub fn new(
|
||||
executors_file_path: Option<String>,
|
||||
blockchain: Chain,
|
||||
blockchain: tycho_core::dto::Chain,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let chain = Chain::from(blockchain);
|
||||
let config_str = if let Some(ref path) = executors_file_path {
|
||||
fs::read_to_string(path).map_err(|e| {
|
||||
EncodingError::FatalError(format!(
|
||||
@@ -34,8 +35,8 @@ impl SwapEncoderRegistry {
|
||||
let config: HashMap<String, HashMap<String, String>> = serde_json::from_str(&config_str)?;
|
||||
let mut encoders = HashMap::new();
|
||||
let executors = config
|
||||
.get(&blockchain.name)
|
||||
.ok_or(EncodingError::FatalError("No executors found for blockchain".to_string()))?;
|
||||
.get(&chain.name)
|
||||
.ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?;
|
||||
for (protocol, executor_address) in executors {
|
||||
let builder = SwapEncoderBuilder::new(protocol, executor_address);
|
||||
let encoder = builder.build()?;
|
||||
|
||||
@@ -4,26 +4,27 @@ use tycho_core::Bytes;
|
||||
use crate::encoding::{
|
||||
errors::EncodingError,
|
||||
models::{Chain, NativeAction, Solution, Transaction},
|
||||
strategy_encoder::StrategyEncoderRegistry,
|
||||
strategy_encoder::StrategyEncoder,
|
||||
tycho_encoder::TychoEncoder,
|
||||
};
|
||||
|
||||
/// Represents an encoder for a swap using any strategy supported by the strategy registry.
|
||||
///
|
||||
/// # Fields
|
||||
/// * `strategy_registry`: S, the strategy registry to use to select the best strategy to encode a
|
||||
/// solution, based on its supported strategies and the solution attributes.
|
||||
/// * `strategy_encoder`: Strategy encoder to follow for encoding the solution
|
||||
/// * `native_address`: Address of the chain's native token
|
||||
/// * `wrapped_address`: Address of the chain's wrapped native token
|
||||
#[derive(Clone)]
|
||||
pub struct EVMTychoEncoder<S: StrategyEncoderRegistry> {
|
||||
strategy_registry: S,
|
||||
pub struct EVMTychoEncoder {
|
||||
strategy_encoder: Box<dyn StrategyEncoder>,
|
||||
native_address: Bytes,
|
||||
wrapped_address: Bytes,
|
||||
}
|
||||
|
||||
impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
||||
pub fn new(strategy_registry: S, chain: tycho_core::dto::Chain) -> Result<Self, EncodingError> {
|
||||
impl EVMTychoEncoder {
|
||||
pub fn new(
|
||||
chain: tycho_core::dto::Chain,
|
||||
strategy_encoder: Box<dyn StrategyEncoder>,
|
||||
) -> Result<Self, EncodingError> {
|
||||
let chain: Chain = Chain::from(chain);
|
||||
if chain.name != *"ethereum" {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
@@ -31,14 +32,14 @@ impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
||||
));
|
||||
}
|
||||
Ok(EVMTychoEncoder {
|
||||
strategy_registry,
|
||||
strategy_encoder,
|
||||
native_address: chain.native_token()?,
|
||||
wrapped_address: chain.wrapped_token()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
||||
impl EVMTychoEncoder {
|
||||
/// Raises an `EncodingError` if the solution is not considered valid.
|
||||
///
|
||||
/// A solution is considered valid if all the following conditions are met:
|
||||
@@ -92,7 +93,7 @@ impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StrategyEncoderRegistry> TychoEncoder<S> for EVMTychoEncoder<S> {
|
||||
impl TychoEncoder for EVMTychoEncoder {
|
||||
fn encode_router_calldata(
|
||||
&self,
|
||||
solutions: Vec<Solution>,
|
||||
@@ -101,11 +102,9 @@ impl<S: StrategyEncoderRegistry> TychoEncoder<S> for EVMTychoEncoder<S> {
|
||||
for solution in solutions.iter() {
|
||||
self.validate_solution(solution)?;
|
||||
|
||||
let strategy = self
|
||||
.strategy_registry
|
||||
.get_encoder(solution)?;
|
||||
let (contract_interaction, target_address, selector) =
|
||||
strategy.encode_strategy(solution.clone())?;
|
||||
let (contract_interaction, target_address, selector) = self
|
||||
.strategy_encoder
|
||||
.encode_strategy(solution.clone())?;
|
||||
|
||||
let value = match solution.native_action.as_ref() {
|
||||
Some(NativeAction::Wrap) => solution.given_amount.clone(),
|
||||
@@ -134,10 +133,6 @@ mod tests {
|
||||
models::Swap, strategy_encoder::StrategyEncoder, swap_encoder::SwapEncoder,
|
||||
};
|
||||
|
||||
struct MockStrategyRegistry {
|
||||
strategy: Box<dyn StrategyEncoder>,
|
||||
}
|
||||
|
||||
fn dai() -> Bytes {
|
||||
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
|
||||
}
|
||||
@@ -150,23 +145,6 @@ mod tests {
|
||||
Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap()
|
||||
}
|
||||
|
||||
impl StrategyEncoderRegistry for MockStrategyRegistry {
|
||||
fn new(
|
||||
_chain: tycho_core::dto::Chain,
|
||||
_executors_file_path: Option<String>,
|
||||
_signer_pk: Option<String>,
|
||||
) -> Result<MockStrategyRegistry, EncodingError> {
|
||||
Ok(Self { strategy: Box::new(MockStrategy) })
|
||||
}
|
||||
|
||||
fn get_encoder(
|
||||
&self,
|
||||
_solution: &Solution,
|
||||
) -> Result<&Box<dyn StrategyEncoder>, EncodingError> {
|
||||
Ok(&self.strategy)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MockStrategy;
|
||||
|
||||
@@ -192,10 +170,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mocked_tycho_encoder() -> EVMTychoEncoder<MockStrategyRegistry> {
|
||||
let strategy_registry =
|
||||
MockStrategyRegistry::new(TychoCoreChain::Ethereum, None, None).unwrap();
|
||||
EVMTychoEncoder::new(strategy_registry, TychoCoreChain::Ethereum).unwrap()
|
||||
fn get_mocked_tycho_encoder() -> EVMTychoEncoder {
|
||||
let strategy_encoder = Box::new(MockStrategy {});
|
||||
EVMTychoEncoder::new(TychoCoreChain::Ethereum, strategy_encoder).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user