feat: Create a EVMEncoderBuilder
- It has two three methods to be created: - new: where the user can pass any custom StrategyEncoder - tycho_router: where the default SplitSwapStrategyEncoder with Tycho Router will be used - direct_execution: where the user can encode only the execution data and integrate this into their workflow. - This replaces StrategyEncoderRegistry - EVMTychoEncoder holds directly the StrategyEncoder and not the registry (per one init of the EVMTychoEncoder there is only one possible StrategyEncoder) - Update quickstart - Update bin (add subcommands to bin to create a different instance of EVMEncoderBuilder) --- don't change below this line --- ENG-4246 Took 33 minutes Took 38 seconds Took 38 seconds
This commit is contained in:
48
README.md
48
README.md
@@ -24,30 +24,46 @@ cargo build --release
|
|||||||
cargo install --path .
|
cargo install --path .
|
||||||
```
|
```
|
||||||
|
|
||||||
After installation, the `tycho-encode` command will be available to use from any directory in your terminal. The command
|
After installation, the `tycho-encode` command will be available to use from any directory in your terminal.
|
||||||
accepts the following options:
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
The command lets you choose the encoding strategy to be used. The available strategies are:
|
||||||
|
|
||||||
|
#### Tycho Router
|
||||||
|
|
||||||
|
`tycho-router`: Encodes a transaction using the Tycho Router encoding strategy. Requires a private key for signing
|
||||||
|
Permit2.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo '<solution_payload>' | tycho-encode tycho-router -p 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Direct execution
|
||||||
|
|
||||||
|
`direct-execution`: Encodes a transaction using the direct execution encoding strategy. Does not require a private key.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo '<solution_payload>' | tycho-encode direct-execution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Encoding Transactions
|
||||||
|
|
||||||
|
The commands accept the following options:
|
||||||
|
|
||||||
- `-c`: Path to the executor addresses configuration file (defaults to `src/encoding/config/executor_addresses.json`)
|
- `-c`: Path to the executor addresses configuration file (defaults to `src/encoding/config/executor_addresses.json`)
|
||||||
- `-p`: Private key for signing approvals (required when direct_execution is false)
|
- `-p`: Private key for signing approvals (required when direct_execution is false)
|
||||||
|
|
||||||
### Encoding Transactions
|
|
||||||
|
|
||||||
To encode a transaction, you can pipe a JSON payload to the binary:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Using default config path
|
|
||||||
echo '<solution_payload>' | tycho-encode
|
|
||||||
|
|
||||||
# Using custom config path
|
|
||||||
echo '<solution_payload>' | tycho-encode -c /path/to/your/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2:
|
Here's a complete example that encodes a swap from WETH to DAI using Uniswap V2 and the Tycho Router strategy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","checked_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":1.0}],"direct_execution":true}' | tycho-encode
|
echo '{"sender":"0x1234567890123456789012345678901234567890","receiver":"0x1234567890123456789012345678901234567890","given_token":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","given_amount":"1000000000000000000","checked_token":"0x6B175474E89094C44Da98b954EedeAC495271d0F","exact_out":false,"slippage":0.01,"expected_amount":"1000000000000000000","checked_amount":"990000000000000000","router_address":"0xaa820C29648D5EA543d712cC928377Bd7206a0E7","swaps":[{"component":{"id":"0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640","protocol_system":"uniswap_v2","protocol_type_name":"UniswapV2Pool","chain":"ethereum","tokens":["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"],"contract_ids":["0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"],"static_attributes":{"factory":"0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f"},"change":"Update","creation_tx":"0x0000000000000000000000000000000000000000000000000000000000000000","created_at":"2024-02-28T12:00:00"},"token_in":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","token_out":"0x6B175474E89094C44Da98b954EedeAC495271d0F","split":0.0}],"direct_execution":true}' | tycho-encode tycho-router -p 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234
|
||||||
```
|
```
|
||||||
|
|
||||||
#### JSON Payload Structure: Solution struct
|
#### JSON Payload Structure: Solution struct
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ use tycho_core::{
|
|||||||
Bytes,
|
Bytes,
|
||||||
};
|
};
|
||||||
use tycho_execution::encoding::{
|
use tycho_execution::encoding::{
|
||||||
evm::{
|
evm::encoder_builder::EVMEncoderBuilder,
|
||||||
strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry,
|
|
||||||
tycho_encoder::EVMTychoEncoder,
|
|
||||||
},
|
|
||||||
models::{Solution, Swap},
|
models::{Solution, Swap},
|
||||||
strategy_encoder::StrategyEncoderRegistry,
|
|
||||||
tycho_encoder::TychoEncoder,
|
tycho_encoder::TychoEncoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,16 +16,15 @@ fn main() {
|
|||||||
let router_address = Bytes::from_str("0x1234567890abcdef1234567890abcdef12345678")
|
let router_address = Bytes::from_str("0x1234567890abcdef1234567890abcdef12345678")
|
||||||
.expect("Failed to create router address");
|
.expect("Failed to create router address");
|
||||||
let signer_pk =
|
let signer_pk =
|
||||||
Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string());
|
"0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string();
|
||||||
let user_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
let user_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
||||||
.expect("Failed to create user address");
|
.expect("Failed to create user address");
|
||||||
|
|
||||||
// Initialize the encoder
|
// Initialize the encoder
|
||||||
let strategy_encoder_registry =
|
let encoder = EVMEncoderBuilder::tycho_router(TychoCoreChain::Ethereum, signer_pk, None)
|
||||||
EVMStrategyEncoderRegistry::new(TychoCoreChain::Ethereum, None, signer_pk.clone())
|
.expect("Failed to create encoder builder")
|
||||||
.expect("Failed to create strategy encoder registry");
|
.build()
|
||||||
let encoder = EVMTychoEncoder::new(strategy_encoder_registry, TychoCoreChain::Ethereum)
|
.expect("Failed to build encoder");
|
||||||
.expect("Failed to create encoder");
|
|
||||||
|
|
||||||
// ------------------- Encode a simple swap -------------------
|
// ------------------- Encode a simple swap -------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub use clap::Parser;
|
pub use clap::Parser;
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
/// Encode swap transactions for the Tycho router
|
/// Encode swap transactions for the Tycho router
|
||||||
@@ -33,12 +34,24 @@ pub use clap::Parser;
|
|||||||
/// "direct_execution": false
|
/// "direct_execution": false
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
/// Private key for signing approvals (required when direct_execution is false)
|
#[command(subcommand)]
|
||||||
#[arg(short)]
|
pub command: Commands,
|
||||||
pub private_key: Option<String>,
|
}
|
||||||
|
|
||||||
/// Path to the executor addresses configuration file
|
#[derive(Subcommand)]
|
||||||
#[arg(short)]
|
pub enum Commands {
|
||||||
pub config_path: Option<String>,
|
/// Use the Tycho router encoding strategy
|
||||||
|
TychoRouter {
|
||||||
|
#[arg(short, long)]
|
||||||
|
config_path: Option<String>,
|
||||||
|
#[arg(short, long)]
|
||||||
|
private_key: String,
|
||||||
|
},
|
||||||
|
/// Use the direct execution encoding strategy
|
||||||
|
DirectExecution {
|
||||||
|
#[arg(short, long)]
|
||||||
|
config_path: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,16 @@ use std::io::{self, Read};
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tycho_core::dto::Chain;
|
use tycho_core::dto::Chain;
|
||||||
use tycho_execution::encoding::{
|
use tycho_execution::encoding::{models::Solution, tycho_encoder::TychoEncoder};
|
||||||
evm::{
|
|
||||||
strategy_encoder::strategy_encoder_registry::EVMStrategyEncoderRegistry,
|
|
||||||
tycho_encoder::EVMTychoEncoder,
|
|
||||||
},
|
|
||||||
models::Solution,
|
|
||||||
strategy_encoder::StrategyEncoderRegistry,
|
|
||||||
tycho_encoder::TychoEncoder,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod lib {
|
mod lib {
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
}
|
}
|
||||||
|
|
||||||
use lib::cli::Cli;
|
use lib::cli::Cli;
|
||||||
|
use tycho_execution::encoding::{errors::EncodingError, evm::encoder_builder::EVMEncoderBuilder};
|
||||||
|
|
||||||
|
use crate::lib::cli::Commands;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
@@ -33,8 +28,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encode the solution
|
// Encode the solution
|
||||||
let encoded = encode_swaps(&buffer, cli.config_path, cli.private_key)?;
|
let encoded = match cli.command {
|
||||||
|
Commands::TychoRouter { config_path, private_key } => {
|
||||||
|
encode_swaps(&buffer, config_path, Some(private_key), true)?
|
||||||
|
}
|
||||||
|
Commands::DirectExecution { config_path } => {
|
||||||
|
encode_swaps(&buffer, config_path, None, false)?
|
||||||
|
}
|
||||||
|
};
|
||||||
// Output the encoded result as JSON to stdout
|
// Output the encoded result as JSON to stdout
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -49,12 +50,20 @@ fn encode_swaps(
|
|||||||
input: &str,
|
input: &str,
|
||||||
config_path: Option<String>,
|
config_path: Option<String>,
|
||||||
private_key: Option<String>,
|
private_key: Option<String>,
|
||||||
) -> Result<Value, Box<dyn std::error::Error>> {
|
use_tycho_router: bool,
|
||||||
|
) -> Result<Value, EncodingError> {
|
||||||
let solution: Solution = serde_json::from_str(input)?;
|
let solution: Solution = serde_json::from_str(input)?;
|
||||||
let chain = Chain::Ethereum;
|
let chain = Chain::Ethereum;
|
||||||
|
|
||||||
let strategy_selector = EVMStrategyEncoderRegistry::new(chain, config_path, private_key)?;
|
let encoder = if use_tycho_router {
|
||||||
let encoder = EVMTychoEncoder::new(strategy_selector, chain)?;
|
let private_key = private_key.ok_or(EncodingError::FatalError(
|
||||||
|
"Private key is required for tycho_router".to_string(),
|
||||||
|
))?;
|
||||||
|
EVMEncoderBuilder::tycho_router(chain, private_key, config_path)?.build()?
|
||||||
|
} else {
|
||||||
|
EVMEncoderBuilder::direct_execution(chain, config_path)?.build()?
|
||||||
|
};
|
||||||
|
|
||||||
let transactions = encoder.encode_router_calldata(vec![solution])?;
|
let transactions = encoder.encode_router_calldata(vec![solution])?;
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
|
|||||||
44
src/encoding/evm/encoder_builder.rs
Normal file
44
src/encoding/evm/encoder_builder.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use tycho_core::dto::Chain;
|
||||||
|
|
||||||
|
use crate::encoding::{
|
||||||
|
errors::EncodingError,
|
||||||
|
evm::{
|
||||||
|
strategy_encoders::{ExecutorStrategyEncoder, SplitSwapStrategyEncoder},
|
||||||
|
swap_encoder::swap_encoder_registry::SwapEncoderRegistry,
|
||||||
|
tycho_encoder::EVMTychoEncoder,
|
||||||
|
},
|
||||||
|
strategy_encoder::StrategyEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct EVMEncoderBuilder {
|
||||||
|
strategy: Box<dyn StrategyEncoder>,
|
||||||
|
chain: Chain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EVMEncoderBuilder {
|
||||||
|
pub fn new(chain: Chain, strategy: Box<dyn StrategyEncoder>) -> Self {
|
||||||
|
EVMEncoderBuilder { chain, strategy }
|
||||||
|
}
|
||||||
|
pub fn tycho_router(
|
||||||
|
chain: Chain,
|
||||||
|
signer_pk: String,
|
||||||
|
executors_file_path: Option<String>,
|
||||||
|
) -> Result<Self, EncodingError> {
|
||||||
|
let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?;
|
||||||
|
let strategy =
|
||||||
|
Box::new(SplitSwapStrategyEncoder::new(signer_pk, chain, swap_encoder_registry)?);
|
||||||
|
Ok(EVMEncoderBuilder { chain, strategy })
|
||||||
|
}
|
||||||
|
pub fn direct_execution(
|
||||||
|
chain: Chain,
|
||||||
|
executors_file_path: Option<String>,
|
||||||
|
) -> Result<Self, EncodingError> {
|
||||||
|
let swap_encoder_registry = SwapEncoderRegistry::new(executors_file_path, chain)?;
|
||||||
|
let strategy = Box::new(ExecutorStrategyEncoder::new(swap_encoder_registry));
|
||||||
|
Ok(EVMEncoderBuilder { chain, strategy })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Result<EVMTychoEncoder, EncodingError> {
|
||||||
|
EVMTychoEncoder::new(self.chain, self.strategy)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
pub mod approvals;
|
pub mod approvals;
|
||||||
mod constants;
|
mod constants;
|
||||||
pub mod strategy_encoder;
|
pub mod encoder_builder;
|
||||||
|
pub mod strategy_encoders;
|
||||||
mod swap_encoder;
|
mod swap_encoder;
|
||||||
pub mod tycho_encoder;
|
pub mod tycho_encoder;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
mod group_swaps;
|
mod group_swaps;
|
||||||
pub mod strategy_encoder_registry;
|
|
||||||
mod strategy_encoders;
|
mod strategy_encoders;
|
||||||
mod strategy_validators;
|
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,9 +89,10 @@ pub struct SplitSwapStrategyEncoder {
|
|||||||
impl SplitSwapStrategyEncoder {
|
impl SplitSwapStrategyEncoder {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
signer_pk: String,
|
signer_pk: String,
|
||||||
chain: Chain,
|
blockchain: tycho_core::dto::Chain,
|
||||||
swap_encoder_registry: SwapEncoderRegistry,
|
swap_encoder_registry: SwapEncoderRegistry,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> 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();
|
let selector = "swap(uint256,address,address,uint256,bool,bool,uint256,address,((address,uint160,uint48,uint48),address,uint256),bytes,bytes)".to_string();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
permit2: Permit2::new(signer_pk, chain.clone())?,
|
permit2: Permit2::new(signer_pk, chain.clone())?,
|
||||||
@@ -337,8 +338,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::encoding::models::Swap;
|
use crate::encoding::models::Swap;
|
||||||
|
|
||||||
fn eth_chain() -> Chain {
|
fn eth_chain() -> TychoCoreChain {
|
||||||
TychoCoreChain::Ethereum.into()
|
TychoCoreChain::Ethereum
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eth() -> Bytes {
|
fn eth() -> Bytes {
|
||||||
@@ -19,8 +19,9 @@ impl SwapEncoderRegistry {
|
|||||||
/// executors' addresses in the file at the given path.
|
/// executors' addresses in the file at the given path.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
executors_file_path: Option<String>,
|
executors_file_path: Option<String>,
|
||||||
blockchain: Chain,
|
blockchain: tycho_core::dto::Chain,
|
||||||
) -> Result<Self, EncodingError> {
|
) -> Result<Self, EncodingError> {
|
||||||
|
let chain = Chain::from(blockchain);
|
||||||
let config_str = if let Some(ref path) = executors_file_path {
|
let config_str = if let Some(ref path) = executors_file_path {
|
||||||
fs::read_to_string(path).map_err(|e| {
|
fs::read_to_string(path).map_err(|e| {
|
||||||
EncodingError::FatalError(format!(
|
EncodingError::FatalError(format!(
|
||||||
@@ -34,8 +35,8 @@ impl SwapEncoderRegistry {
|
|||||||
let config: HashMap<String, HashMap<String, String>> = serde_json::from_str(&config_str)?;
|
let config: HashMap<String, HashMap<String, String>> = serde_json::from_str(&config_str)?;
|
||||||
let mut encoders = HashMap::new();
|
let mut encoders = HashMap::new();
|
||||||
let executors = config
|
let executors = config
|
||||||
.get(&blockchain.name)
|
.get(&chain.name)
|
||||||
.ok_or(EncodingError::FatalError("No executors found for blockchain".to_string()))?;
|
.ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?;
|
||||||
for (protocol, executor_address) in executors {
|
for (protocol, executor_address) in executors {
|
||||||
let builder = SwapEncoderBuilder::new(protocol, executor_address);
|
let builder = SwapEncoderBuilder::new(protocol, executor_address);
|
||||||
let encoder = builder.build()?;
|
let encoder = builder.build()?;
|
||||||
|
|||||||
@@ -4,26 +4,27 @@ use tycho_core::Bytes;
|
|||||||
use crate::encoding::{
|
use crate::encoding::{
|
||||||
errors::EncodingError,
|
errors::EncodingError,
|
||||||
models::{Chain, NativeAction, Solution, Transaction},
|
models::{Chain, NativeAction, Solution, Transaction},
|
||||||
strategy_encoder::StrategyEncoderRegistry,
|
strategy_encoder::StrategyEncoder,
|
||||||
tycho_encoder::TychoEncoder,
|
tycho_encoder::TychoEncoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents an encoder for a swap using any strategy supported by the strategy registry.
|
/// Represents an encoder for a swap using any strategy supported by the strategy registry.
|
||||||
///
|
///
|
||||||
/// # Fields
|
/// # Fields
|
||||||
/// * `strategy_registry`: S, the strategy registry to use to select the best strategy to encode a
|
/// * `strategy_encoder`: Strategy encoder to follow for encoding the solution
|
||||||
/// solution, based on its supported strategies and the solution attributes.
|
|
||||||
/// * `native_address`: Address of the chain's native token
|
/// * `native_address`: Address of the chain's native token
|
||||||
/// * `wrapped_address`: Address of the chain's wrapped native token
|
/// * `wrapped_address`: Address of the chain's wrapped native token
|
||||||
#[derive(Clone)]
|
pub struct EVMTychoEncoder {
|
||||||
pub struct EVMTychoEncoder<S: StrategyEncoderRegistry> {
|
strategy_encoder: Box<dyn StrategyEncoder>,
|
||||||
strategy_registry: S,
|
|
||||||
native_address: Bytes,
|
native_address: Bytes,
|
||||||
wrapped_address: Bytes,
|
wrapped_address: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
impl EVMTychoEncoder {
|
||||||
pub fn new(strategy_registry: S, chain: tycho_core::dto::Chain) -> Result<Self, EncodingError> {
|
pub fn new(
|
||||||
|
chain: tycho_core::dto::Chain,
|
||||||
|
strategy_encoder: Box<dyn StrategyEncoder>,
|
||||||
|
) -> Result<Self, EncodingError> {
|
||||||
let chain: Chain = Chain::from(chain);
|
let chain: Chain = Chain::from(chain);
|
||||||
if chain.name != *"ethereum" {
|
if chain.name != *"ethereum" {
|
||||||
return Err(EncodingError::InvalidInput(
|
return Err(EncodingError::InvalidInput(
|
||||||
@@ -31,14 +32,14 @@ impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(EVMTychoEncoder {
|
Ok(EVMTychoEncoder {
|
||||||
strategy_registry,
|
strategy_encoder,
|
||||||
native_address: chain.native_token()?,
|
native_address: chain.native_token()?,
|
||||||
wrapped_address: chain.wrapped_token()?,
|
wrapped_address: chain.wrapped_token()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: StrategyEncoderRegistry> EVMTychoEncoder<S> {
|
impl EVMTychoEncoder {
|
||||||
/// Raises an `EncodingError` if the solution is not considered valid.
|
/// Raises an `EncodingError` if the solution is not considered valid.
|
||||||
///
|
///
|
||||||
/// A solution is considered valid if all the following conditions are met:
|
/// 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(
|
fn encode_router_calldata(
|
||||||
&self,
|
&self,
|
||||||
solutions: Vec<Solution>,
|
solutions: Vec<Solution>,
|
||||||
@@ -101,11 +102,9 @@ impl<S: StrategyEncoderRegistry> TychoEncoder<S> for EVMTychoEncoder<S> {
|
|||||||
for solution in solutions.iter() {
|
for solution in solutions.iter() {
|
||||||
self.validate_solution(solution)?;
|
self.validate_solution(solution)?;
|
||||||
|
|
||||||
let strategy = self
|
let (contract_interaction, target_address, selector) = self
|
||||||
.strategy_registry
|
.strategy_encoder
|
||||||
.get_encoder(solution)?;
|
.encode_strategy(solution.clone())?;
|
||||||
let (contract_interaction, target_address, selector) =
|
|
||||||
strategy.encode_strategy(solution.clone())?;
|
|
||||||
|
|
||||||
let value = match solution.native_action.as_ref() {
|
let value = match solution.native_action.as_ref() {
|
||||||
Some(NativeAction::Wrap) => solution.given_amount.clone(),
|
Some(NativeAction::Wrap) => solution.given_amount.clone(),
|
||||||
@@ -134,10 +133,6 @@ mod tests {
|
|||||||
models::Swap, strategy_encoder::StrategyEncoder, swap_encoder::SwapEncoder,
|
models::Swap, strategy_encoder::StrategyEncoder, swap_encoder::SwapEncoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MockStrategyRegistry {
|
|
||||||
strategy: Box<dyn StrategyEncoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dai() -> Bytes {
|
fn dai() -> Bytes {
|
||||||
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
|
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
|
||||||
}
|
}
|
||||||
@@ -150,23 +145,6 @@ mod tests {
|
|||||||
Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap()
|
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)]
|
#[derive(Clone)]
|
||||||
struct MockStrategy;
|
struct MockStrategy;
|
||||||
|
|
||||||
@@ -192,10 +170,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mocked_tycho_encoder() -> EVMTychoEncoder<MockStrategyRegistry> {
|
fn get_mocked_tycho_encoder() -> EVMTychoEncoder {
|
||||||
let strategy_registry =
|
let strategy_encoder = Box::new(MockStrategy {});
|
||||||
MockStrategyRegistry::new(TychoCoreChain::Ethereum, None, None).unwrap();
|
EVMTychoEncoder::new(TychoCoreChain::Ethereum, strategy_encoder).unwrap()
|
||||||
EVMTychoEncoder::new(strategy_registry, TychoCoreChain::Ethereum).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mod errors;
|
pub mod errors;
|
||||||
#[cfg(feature = "evm")]
|
#[cfg(feature = "evm")]
|
||||||
pub mod evm;
|
pub mod evm;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use tycho_core::{dto::Chain, Bytes};
|
use tycho_core::Bytes;
|
||||||
|
|
||||||
use crate::encoding::{errors::EncodingError, models::Solution, swap_encoder::SwapEncoder};
|
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 get_swap_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>>;
|
||||||
fn clone_box(&self) -> Box<dyn StrategyEncoder>;
|
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>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use crate::encoding::{
|
use crate::encoding::{
|
||||||
errors::EncodingError,
|
errors::EncodingError,
|
||||||
models::{Solution, Transaction},
|
models::{Solution, Transaction},
|
||||||
strategy_encoder::StrategyEncoderRegistry,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An encoder must implement this trait in order to encode a solution into a Transaction for
|
/// An encoder must implement this trait in order to encode a solution into a Transaction for
|
||||||
/// execution using a Tycho router or related contracts.
|
/// execution using a Tycho router or related contracts.
|
||||||
pub trait TychoEncoder<S: StrategyEncoderRegistry> {
|
pub trait TychoEncoder {
|
||||||
fn encode_router_calldata(
|
fn encode_router_calldata(
|
||||||
&self,
|
&self,
|
||||||
solutions: Vec<Solution>,
|
solutions: Vec<Solution>,
|
||||||
|
|||||||
Reference in New Issue
Block a user