Merge pull request #8 from propeller-heads/encoding/dc/ENG-4076-error-handling

feat: Add EncodingError
This commit is contained in:
dianacarvalho1
2025-01-17 17:11:52 +00:00
committed by GitHub
12 changed files with 99 additions and 38 deletions

2
Cargo.lock generated
View File

@@ -3044,7 +3044,6 @@ dependencies = [
"alloy", "alloy",
"alloy-primitives", "alloy-primitives",
"alloy-sol-types", "alloy-sol-types",
"anyhow",
"dotenv", "dotenv",
"hex", "hex",
"lazy_static", "lazy_static",
@@ -3052,6 +3051,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69",
"tokio", "tokio",
"tycho-core", "tycho-core",
] ]

View File

@@ -7,18 +7,17 @@ edition = "2021"
dotenv = "0.15.0" dotenv = "0.15.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
num-bigint = "0.4.6" num-bigint = "0.4.6"
hex = "0.4.3"
num-traits = "0.2.19"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"
thiserror = "1.0.69"
tokio = { version = "1.38.0", features = ["full"] } tokio = { version = "1.38.0", features = ["full"] }
alloy = { version = "0.5.4", features = ["providers"], optional = true } alloy = { version = "0.5.4", features = ["providers"], optional = true }
alloy-sol-types = { version = "0.8.14", optional = true } alloy-sol-types = { version = "0.8.14", optional = true }
alloy-primitives = { version = "0.8.9", optional = true } alloy-primitives = { version = "0.8.9", optional = true }
tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.46.0" } tycho-core = { git = "https://github.com/propeller-heads/tycho-indexer.git", package = "tycho-core", tag = "0.46.0" }
hex = "0.4.3"
anyhow = "1.0.95"
num-traits = "0.2.19"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.135"
[features] [features]
default = ["evm"] default = ["evm"]

36
src/encoding/errors.rs Normal file
View File

@@ -0,0 +1,36 @@
use std::io;
use thiserror::Error;
/// Represents the outer-level, user-facing errors of the tycho-execution encoding package.
///
/// `EncodingError` encompasses all possible errors that can occur in the package,
/// wrapping lower-level errors in a user-friendly way for easier handling and display.
/// Variants:
/// - `InvalidInput`: Indicates that the encoding has failed due to bad input parameters.
/// - `FatalError`: There is problem with the application setup.
/// - `RecoverableError`: Indicates that the encoding has failed with a recoverable error. Retrying
/// at a later time may succeed. It may have failed due to a temporary issue, such as a network
/// problem.
#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum EncodingError {
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Fatal error: {0}")]
FatalError(String),
#[error("Recoverable error: {0}")]
RecoverableError(String),
}
impl From<io::Error> for EncodingError {
fn from(err: io::Error) -> Self {
EncodingError::FatalError(err.to_string())
}
}
impl From<serde_json::Error> for EncodingError {
fn from(err: serde_json::Error) -> Self {
EncodingError::FatalError(err.to_string())
}
}

View File

@@ -1,7 +1,7 @@
use anyhow::Error;
use num_bigint::BigUint; use num_bigint::BigUint;
use crate::encoding::{ use crate::encoding::{
errors::EncodingError,
evm::utils::encode_input, evm::utils::encode_input,
models::{NativeAction, Solution, Transaction, PROPELLER_ROUTER_ADDRESS}, models::{NativeAction, Solution, Transaction, PROPELLER_ROUTER_ADDRESS},
router_encoder::RouterEncoder, router_encoder::RouterEncoder,
@@ -22,7 +22,10 @@ impl<S: StrategySelector, A: UserApprovalsManager> EVMRouterEncoder<S, A> {
} }
} }
impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRouterEncoder<S, A> { impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRouterEncoder<S, A> {
fn encode_router_calldata(&self, solutions: Vec<Solution>) -> Result<Vec<Transaction>, Error> { fn encode_router_calldata(
&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)?; // TODO: where should we append this?
let mut transactions: Vec<Transaction> = Vec::new(); let mut transactions: Vec<Transaction> = Vec::new();
for solution in solutions.iter() { for solution in solutions.iter() {
@@ -50,7 +53,7 @@ impl<S: StrategySelector, A: UserApprovalsManager> RouterEncoder<S, A> for EVMRo
Ok(transactions) Ok(transactions)
} }
fn handle_approvals(&self, solutions: &[Solution]) -> Result<Vec<Vec<u8>>, Error> { fn handle_approvals(&self, solutions: &[Solution]) -> Result<Vec<Vec<u8>>, EncodingError> {
let mut approvals = Vec::new(); let mut approvals = Vec::new();
for solution in solutions.iter() { for solution in solutions.iter() {
approvals.push(Approval { approvals.push(Approval {

View File

@@ -1,8 +1,8 @@
use alloy_primitives::Address; use alloy_primitives::Address;
use alloy_sol_types::SolValue; use alloy_sol_types::SolValue;
use anyhow::Error;
use crate::encoding::{ use crate::encoding::{
errors::EncodingError,
evm::swap_encoder::SWAP_ENCODER_REGISTRY, evm::swap_encoder::SWAP_ENCODER_REGISTRY,
models::{EncodingContext, Solution}, models::{EncodingContext, Solution},
strategy_encoder::StrategyEncoder, strategy_encoder::StrategyEncoder,
@@ -27,7 +27,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>, Error> { fn encode_strategy(&self, _solution: Solution) -> Result<Vec<u8>, EncodingError> {
todo!() todo!()
} }
fn selector(&self, _exact_out: bool) -> &str { fn selector(&self, _exact_out: bool) -> &str {
@@ -40,17 +40,26 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
pub struct StraightToPoolStrategyEncoder {} pub struct StraightToPoolStrategyEncoder {}
impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {} impl EVMStrategyEncoder for StraightToPoolStrategyEncoder {}
impl StrategyEncoder for StraightToPoolStrategyEncoder { impl StrategyEncoder for StraightToPoolStrategyEncoder {
fn encode_strategy(&self, solution: Solution) -> Result<Vec<u8>, Error> { fn encode_strategy(&self, solution: Solution) -> Result<Vec<u8>, EncodingError> {
if solution.router_address.is_none() { if solution.router_address.is_none() {
return Err(anyhow::anyhow!( return Err(EncodingError::InvalidInput(
"Router address is required for straight to pool solutions" "Router address is required for straight to pool solutions".to_string(),
)); ));
} }
let swap = solution.swaps.first().unwrap(); let swap = solution.swaps.first().unwrap();
let registry = SWAP_ENCODER_REGISTRY.read().unwrap(); let registry = SWAP_ENCODER_REGISTRY
.read()
.map_err(|_| {
EncodingError::FatalError("Failed to read the swap encoder registry".to_string())
})?;
let swap_encoder = registry let swap_encoder = registry
.get_encoder(&swap.component.protocol_system) .get_encoder(&swap.component.protocol_system)
.expect("Swap encoder not found"); .ok_or_else(|| {
EncodingError::InvalidInput(format!(
"Swap encoder not found for protocol: {}",
swap.component.protocol_system
))
})?;
let router_address = solution.router_address.unwrap(); let router_address = solution.router_address.unwrap();
let encoding_context = EncodingContext { let encoding_context = EncodingContext {

View File

@@ -2,9 +2,9 @@ use std::str::FromStr;
use alloy_primitives::Address; use alloy_primitives::Address;
use alloy_sol_types::SolValue; use alloy_sol_types::SolValue;
use anyhow::Error;
use crate::encoding::{ use crate::encoding::{
errors::EncodingError,
evm::{ evm::{
approvals::protocol_approvals_manager::ProtocolApprovalsManager, utils::bytes_to_address, approvals::protocol_approvals_manager::ProtocolApprovalsManager, utils::bytes_to_address,
}, },
@@ -25,7 +25,7 @@ impl SwapEncoder for UniswapV2SwapEncoder {
&self, &self,
_swap: Swap, _swap: Swap,
_encoding_context: EncodingContext, _encoding_context: EncodingContext,
) -> Result<Vec<u8>, Error> { ) -> Result<Vec<u8>, EncodingError> {
todo!() todo!()
} }
@@ -47,7 +47,11 @@ impl SwapEncoder for BalancerV2SwapEncoder {
.expect("Invalid string for balancer vault address"), .expect("Invalid string for balancer vault address"),
} }
} }
fn encode_swap(&self, swap: Swap, encoding_context: EncodingContext) -> Result<Vec<u8>, Error> { fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new(); let token_approvals_manager = ProtocolApprovalsManager::new();
let runtime = tokio::runtime::Handle::try_current() let runtime = tokio::runtime::Handle::try_current()
.is_err() .is_err()

View File

@@ -3,7 +3,10 @@ use std::{collections::HashMap, fs};
use serde::Deserialize; use serde::Deserialize;
use tycho_core::dto::Chain; use tycho_core::dto::Chain;
use crate::encoding::{evm::swap_encoder::builder::SwapEncoderBuilder, swap_encoder::SwapEncoder}; use crate::encoding::{
errors::EncodingError, evm::swap_encoder::builder::SwapEncoderBuilder,
swap_encoder::SwapEncoder,
};
pub struct SwapEncoderRegistry { pub struct SwapEncoderRegistry {
encoders: HashMap<String, Box<dyn SwapEncoder>>, encoders: HashMap<String, Box<dyn SwapEncoder>>,
@@ -40,7 +43,7 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn from_file(path: &str) -> Result<Self, anyhow::Error> { pub fn from_file(path: &str) -> Result<Self, EncodingError> {
let config_str = fs::read_to_string(path)?; let config_str = fs::read_to_string(path)?;
let config: Config = serde_json::from_str(&config_str)?; let config: Config = serde_json::from_str(&config_str)?;
Ok(config) Ok(config)

View File

@@ -1,18 +1,19 @@
use alloy_primitives::{Address, Keccak256, U256}; use alloy_primitives::{Address, Keccak256, U256};
use alloy_sol_types::SolValue; use alloy_sol_types::SolValue;
use anyhow::Error;
use num_bigint::BigUint; use num_bigint::BigUint;
use tycho_core::Bytes; use tycho_core::Bytes;
use crate::encoding::errors::EncodingError;
/// Safely converts a `Bytes` object to an `Address` object. /// Safely converts a `Bytes` object to an `Address` object.
/// ///
/// Checks the length of the `Bytes` before attempting to convert, and returns a `SimulationError` /// Checks the length of the `Bytes` before attempting to convert, and returns an `EncodingError`
/// if not 20 bytes long. /// if not 20 bytes long.
pub fn bytes_to_address(address: &Bytes) -> Result<Address, Error> { pub fn bytes_to_address(address: &Bytes) -> Result<Address, EncodingError> {
if address.len() == 20 { if address.len() == 20 {
Ok(Address::from_slice(address)) Ok(Address::from_slice(address))
} else { } else {
Err(anyhow::format_err!("Invalid ERC20 token address: {:?}", address)) Err(EncodingError::InvalidInput(format!("Invalid ERC20 token address: {:?}", address)))
} }
} }

View File

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

View File

@@ -1,6 +1,5 @@
use anyhow::Error;
use crate::encoding::{ use crate::encoding::{
errors::EncodingError,
models::{Solution, Transaction}, models::{Solution, Transaction},
strategy_encoder::StrategySelector, strategy_encoder::StrategySelector,
user_approvals_manager::UserApprovalsManager, user_approvals_manager::UserApprovalsManager,
@@ -8,6 +7,9 @@ use crate::encoding::{
#[allow(dead_code)] #[allow(dead_code)]
pub trait RouterEncoder<S: StrategySelector, A: UserApprovalsManager> { pub trait RouterEncoder<S: StrategySelector, A: UserApprovalsManager> {
fn encode_router_calldata(&self, solutions: Vec<Solution>) -> Result<Vec<Transaction>, Error>; fn encode_router_calldata(
fn handle_approvals(&self, solutions: &[Solution]) -> Result<Vec<Vec<u8>>, Error>; &self,
solutions: Vec<Solution>,
) -> Result<Vec<Transaction>, EncodingError>;
fn handle_approvals(&self, solutions: &[Solution]) -> Result<Vec<Vec<u8>>, EncodingError>;
} }

View File

@@ -1,10 +1,8 @@
use anyhow::Error; use crate::encoding::{errors::EncodingError, models::Solution};
use crate::encoding::models::Solution;
#[allow(dead_code)] #[allow(dead_code)]
pub trait StrategyEncoder { pub trait StrategyEncoder {
fn encode_strategy(&self, to_encode: Solution) -> Result<Vec<u8>, Error>; fn encode_strategy(&self, to_encode: Solution) -> Result<Vec<u8>, EncodingError>;
fn selector(&self, exact_out: bool) -> &str; fn selector(&self, exact_out: bool) -> &str;
} }

View File

@@ -1,12 +1,17 @@
use anyhow::Error; use crate::encoding::{
errors::EncodingError,
use crate::encoding::models::{EncodingContext, Swap}; models::{EncodingContext, Swap},
};
#[allow(dead_code)] #[allow(dead_code)]
pub trait SwapEncoder: Sync + Send { pub trait SwapEncoder: Sync + Send {
fn new(executor_address: String) -> Self fn new(executor_address: String) -> Self
where where
Self: Sized; Self: Sized;
fn encode_swap(&self, swap: Swap, encoding_context: EncodingContext) -> Result<Vec<u8>, Error>; fn encode_swap(
&self,
swap: Swap,
encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError>;
fn executor_address(&self) -> &str; fn executor_address(&self) -> &str;
} }