Merge branch 'main' into router/hr/ENG-4239-usv4-swap-encoder

This commit is contained in:
Harsh Vardhan Roy
2025-02-20 00:07:43 +05:30
committed by GitHub
20 changed files with 262 additions and 241 deletions

View File

@@ -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();

View 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(),
))
}
}
}

View File

@@ -1,5 +1,6 @@
pub mod approvals;
mod constants;
pub mod encoder_builder;
pub mod strategy_encoder;
mod swap_encoder;
pub mod tycho_encoder;

View File

@@ -1,4 +1,3 @@
mod group_swaps;
pub mod strategy_encoder_registry;
mod strategy_encoders;
pub mod strategy_encoders;
mod strategy_validators;

View File

@@ -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(),
}
}
}

View File

@@ -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,

View File

@@ -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()?;

View File

@@ -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]

View File

@@ -1,4 +1,4 @@
mod errors;
pub mod errors;
#[cfg(feature = "evm")]
pub mod evm;
pub mod models;

View File

@@ -45,11 +45,6 @@ pub struct Solution {
pub router_address: Bytes,
/// If set, the corresponding native action will be executed.
pub native_action: Option<NativeAction>,
/// 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.
#[serde(default)]
pub direct_execution: bool,
}
/// Represents an action to be performed on the native token either before or after the swap.

View File

@@ -1,4 +1,4 @@
use tycho_core::{dto::Chain, Bytes};
use tycho_core::Bytes;
use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::SwapEncoder};
@@ -13,19 +13,3 @@ pub trait StrategyEncoder {
fn get_swap_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>>;
fn clone_box(&self) -> Box<dyn StrategyEncoder>;
}
/// Contains the supported strategies to encode a solution, and chooses the best strategy to encode
/// a solution based on the solution's attributes.
pub trait StrategyEncoderRegistry {
fn new(
chain: Chain,
executors_file_path: Option<String>,
signer_pk: Option<String>,
) -> Result<Self, EncodingError>
where
Self: Sized;
/// Returns the strategy encoder that should be used to encode the given solution.
#[allow(clippy::borrowed_box)]
fn get_encoder(&self, solution: &Solution) -> Result<&Box<dyn StrategyEncoder>, EncodingError>;
}

View File

@@ -1,12 +1,11 @@
use crate::encoding::{
errors::EncodingError,
models::{Solution, Transaction},
strategy_encoder::StrategyEncoderRegistry,
};
/// An encoder must implement this trait in order to encode a solution into a Transaction for
/// execution using a Tycho router or related contracts.
pub trait TychoEncoder<S: StrategyEncoderRegistry> {
pub trait TychoEncoder {
fn encode_router_calldata(
&self,
solutions: Vec<Solution>,