Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Risk calculation in solvers crate #1919

Merged
merged 12 commits into from
Oct 12, 2023
1 change: 1 addition & 0 deletions crates/e2e/src/setup/colocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ weth = "{weth:?}"
base-tokens = []
max-hops = 0
max-partial-attempts = 5
risk-parameters = [0,0,0,0]
"#,
));
let args = vec![
Expand Down
1 change: 1 addition & 0 deletions crates/solvers/config/example.balancer.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
absolute-slippage = "40000000000000000" # Denominated in wei, optional
relative-slippage = "0.1" # Percentage in the [0, 1] range
risk-parameters = [0,0,0,0]

[dex]
endpoint = "https://balancer.sor.eth/api"
Expand Down
1 change: 1 addition & 0 deletions crates/solvers/config/example.baseline.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ chain-id = "1"
base-tokens = []
max-hops = 0
max-partial-attempts = 5
risk-parameters = [0,0,0,0]
1 change: 1 addition & 0 deletions crates/solvers/config/example.naive.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
risk-parameters = [0,0,0,0]
1 change: 1 addition & 0 deletions crates/solvers/config/example.oneinch.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
absolute-slippage = "40000000000000000" # Denominated in wei, optional
relative-slippage = "0.1" # Percentage in the [0, 1] range
risk-parameters = [0,0,0,0]
[dex]
chain-id = "1"
1 change: 1 addition & 0 deletions crates/solvers/config/example.paraswap.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
absolute-slippage = "40000000000000000" # Denominated in wei, optional
relative-slippage = "0.1" # Percentage in the [0, 1] range
risk-parameters = [0,0,0,0]
[dex]
exclude-dexs = [] # which dexs to ignore as liquidity sources
address = "0xdd2e786980CD58ACc5F64807b354c981f4094936" # public address of the solver
Expand Down
1 change: 1 addition & 0 deletions crates/solvers/config/example.zeroex.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
absolute-slippage = "40000000000000000" # Denominated in wei, optional
relative-slippage = "0.1" # Percentage in the [0, 1] range
risk-parameters = [0,0,0,0]

[dex]
# See here how to get a free key: https://0x.org/docs/introduction/getting-started
Expand Down
3 changes: 2 additions & 1 deletion crates/solvers/src/domain/dex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl Swap {
order: order::Order,
gas_price: auction::GasPrice,
sell_token: Option<auction::Price>,
score: solution::Score,
) -> Option<solution::Solution> {
let allowance = self.allowance();
let interactions = vec![solution::Interaction::Custom(solution::CustomInteraction {
Expand All @@ -125,7 +126,7 @@ impl Swap {
interactions,
gas: self.gas,
}
.into_solution(gas_price, sell_token)
.into_solution(gas_price, sell_token, score)
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/solvers/src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ pub mod dex;
pub mod eth;
pub mod liquidity;
pub mod order;
pub mod risk;
pub mod solution;
pub mod solver;

pub use risk::Risk;
26 changes: 26 additions & 0 deletions crates/solvers/src/domain/risk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use super::{auction::GasPrice, eth::Gas};

/// Parameters that define the possibility of a revert when executing a
/// solution.
#[derive(Debug, Default)]
pub struct Risk {
pub gas_amount_factor: f64,
pub gas_price_factor: f64,
pub nmb_orders_factor: f64,
pub intercept: f64,
}

impl Risk {
pub fn success_probability(
&self,
gas_amount: Gas,
gas_price: GasPrice,
nmb_orders: usize,
) -> f64 {
let exponent = -self.intercept
- self.gas_amount_factor * gas_amount.0.to_f64_lossy() / 1_000_000.
- self.gas_price_factor * gas_price.0 .0.to_f64_lossy() / 10_000_000_000.
- self.nmb_orders_factor * nmb_orders as f64;
1. / (1. + exponent.exp())
}
}
5 changes: 3 additions & 2 deletions crates/solvers/src/domain/solution.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::{
domain::{auction, eth, liquidity, order},
domain::{auction, eth, liquidity, order, solution},
util,
},
ethereum_types::{Address, U256},
Expand Down Expand Up @@ -46,6 +46,7 @@ impl Single {
self,
gas_price: auction::GasPrice,
sell_token: Option<auction::Price>,
score: solution::Score,
) -> Option<Solution> {
let Self {
order,
Expand Down Expand Up @@ -118,7 +119,7 @@ impl Single {
]),
trades: vec![Trade::Fulfillment(Fulfillment::new(order, executed, fee)?)],
interactions,
score: Default::default(),
score,
})
}
}
Expand Down
17 changes: 16 additions & 1 deletion crates/solvers/src/domain/solver/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use {
crate::{
boundary,
domain::{
self,
auction,
eth,
liquidity,
Expand Down Expand Up @@ -44,6 +45,9 @@ struct Inner {
/// Basically we continuously halve the amount to execute until we find a
/// valid solution or exceed this count.
max_partial_attempts: usize,

/// Parameters used to calculate the revert risk of a solution.
risk: domain::Risk,
}

impl Baseline {
Expand All @@ -54,6 +58,7 @@ impl Baseline {
base_tokens: config.base_tokens.into_iter().collect(),
max_hops: config.max_hops,
max_partial_attempts: config.max_partial_attempts,
risk: config.risk,
}))
}

Expand Down Expand Up @@ -113,14 +118,24 @@ impl Inner {
output.amount = cmp::min(output.amount, order.buy.amount);
}

let score = solution::Score::RiskAdjusted(self.risk.success_probability(
route.gas(),
auction.gas_price,
1,
));

solution::Single {
order: order.clone(),
input: route.input(),
output,
interactions,
gas: route.gas(),
}
.into_solution(auction.gas_price, sell_token)
.into_solution(
auction.gas_price,
sell_token,
score,
)
})
})
.collect()
Expand Down
9 changes: 8 additions & 1 deletion crates/solvers/src/domain/solver/dex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use {
crate::{
domain,
domain::{auction, dex::slippage, order, solution, solver::dex::fills::Fills},
infra,
},
Expand All @@ -26,6 +27,9 @@ pub struct Dex {
/// Helps to manage the strategy to fill orders (especially partially
/// fillable orders).
fills: Fills,

/// Parameters used to calculate the revert risk of a solution.
risk: domain::Risk,
}

impl Dex {
Expand All @@ -35,6 +39,7 @@ impl Dex {
slippage: config.slippage,
concurrent_requests: config.concurrent_requests,
fills: Fills::new(config.smallest_partial_fill),
risk: config.risk,
}
}

Expand Down Expand Up @@ -108,7 +113,9 @@ impl Dex {

let uid = order.uid;
let sell = tokens.reference_price(&order.sell.token);
let Some(solution) = swap.into_solution(order.clone(), gas_price, sell) else {
let score =
solution::Score::RiskAdjusted(self.risk.success_probability(swap.gas, gas_price, 1));
let Some(solution) = swap.into_solution(order.clone(), gas_price, sell, score) else {
tracing::debug!("no solution for swap");
return None;
};
Expand Down
16 changes: 14 additions & 2 deletions crates/solvers/src/domain/solver/naive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@
use {
crate::{
boundary,
domain::{auction, liquidity, order, solution},
domain::{self, auction, liquidity, order, solution},
infra::config,
},
std::collections::HashMap,
};

pub struct Naive;
pub struct Naive {
/// Parameters used to calculate the revert risk of a solution.
///
/// [ CURRENTLY NOT USED ]
/// TODO: Waiting for proper gas estimation to be implemented
risk: domain::Risk,
}

impl Naive {
/// Creates a new naive solver for the specified configuration.
pub fn new(config: config::naive::Config) -> Self {
Self { risk: config.risk }
}

/// Solves the specified auction, returning a vector of all possible
/// solutions.
pub async fn solve(&self, auction: auction::Auction) -> Vec<solution::Solution> {
Expand Down
5 changes: 4 additions & 1 deletion crates/solvers/src/infra/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ pub enum Command {
config: PathBuf,
},
/// optimistically batch similar orders and get difference from AMMs
Naive,
Naive {
#[clap(long, env)]
config: PathBuf,
},
/// forward auction to solver implementing the legacy HTTP interface
Legacy {
#[clap(long, env)]
Expand Down
12 changes: 11 additions & 1 deletion crates/solvers/src/infra/config/baseline/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::{
domain::eth,
domain::{eth, Risk},
infra::{config::unwrap_or_log, contracts},
util::serialize,
},
Expand Down Expand Up @@ -37,6 +37,10 @@ struct Config {
/// The maximum number of pieces to divide partially fillable limit orders
/// when trying to solve it against baseline liquidity.
max_partial_attempts: usize,

/// Parameters used to calculate the revert risk of a solution.
/// (gas_amount_factor, gas_price_factor, nmb_orders_factor, intercept)
risk_parameters: (f64, f64, f64, f64),
}

/// Load the driver configuration from a TOML file.
Expand Down Expand Up @@ -71,5 +75,11 @@ pub async fn load(path: &Path) -> super::Config {
.collect(),
max_hops: config.max_hops,
max_partial_attempts: config.max_partial_attempts,
risk: Risk {
gas_amount_factor: config.risk_parameters.0,
gas_price_factor: config.risk_parameters.1,
nmb_orders_factor: config.risk_parameters.2,
intercept: config.risk_parameters.3,
},
}
}
3 changes: 2 additions & 1 deletion crates/solvers/src/infra/config/baseline/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::domain::eth;
use crate::domain::{eth, Risk};

pub mod file;

Expand All @@ -7,4 +7,5 @@ pub struct Config {
pub base_tokens: Vec<eth::TokenAddress>,
pub max_hops: usize,
pub max_partial_attempts: usize,
pub risk: Risk,
}
12 changes: 11 additions & 1 deletion crates/solvers/src/infra/config/dex/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use {
crate::{
domain::{dex::slippage, eth},
domain::{dex::slippage, eth, Risk},
infra::config::unwrap_or_log,
},
bigdecimal::BigDecimal,
Expand Down Expand Up @@ -34,6 +34,10 @@ struct Config {
#[serde(default = "default_smallest_partial_fill")]
smallest_partial_fill: eth::U256,

/// Parameters used to calculate the revert risk of a solution.
/// (gas_amount_factor, gas_price_factor, nmb_orders_factor, intercept)
risk_parameters: (f64, f64, f64, f64),

/// Settings specific to the wrapped dex API.
dex: toml::Value,
}
Expand Down Expand Up @@ -73,6 +77,12 @@ pub async fn load<T: DeserializeOwned>(path: &Path) -> (super::Config, T) {
.expect("invalid slippage limits"),
concurrent_requests: config.concurrent_requests,
smallest_partial_fill: eth::Ether(config.smallest_partial_fill),
risk: Risk {
gas_amount_factor: config.risk_parameters.0,
gas_price_factor: config.risk_parameters.1,
nmb_orders_factor: config.risk_parameters.2,
intercept: config.risk_parameters.3,
},
};

(config, dex)
Expand Down
3 changes: 2 additions & 1 deletion crates/solvers/src/infra/config/dex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ pub mod paraswap;
pub mod zeroex;

use {
crate::domain::{dex::slippage, eth},
crate::domain::{dex::slippage, eth, Risk},
std::num::NonZeroUsize,
};

pub struct Config {
pub slippage: slippage::Limits,
pub concurrent_requests: NonZeroUsize,
pub smallest_partial_fill: eth::Ether,
pub risk: Risk,
}
10 changes: 10 additions & 0 deletions crates/solvers/src/infra/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ use std::fmt::Debug;
pub mod baseline;
pub mod dex;
pub mod legacy;
pub mod naive;

/// Parameters used to calculate the revert risk of a solution.
#[derive(Debug, Clone)]
pub struct RiskParameters {
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
pub gas_amount_factor: f64,
pub gas_price_factor: f64,
pub nmb_orders_factor: f64,
pub intercept: f64,
}

/// Unwraps result or logs a `TOML` parsing error.
fn unwrap_or_log<T, E, P>(result: Result<T, E>, path: &P) -> T
Expand Down
37 changes: 37 additions & 0 deletions crates/solvers/src/infra/config/naive/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use {
crate::{domain::Risk, infra::config::unwrap_or_log},
serde::Deserialize,
serde_with::serde_as,
std::path::Path,
tokio::fs,
};

#[serde_as]
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct Config {
/// Parameters used to calculate the revert risk of a solution.
/// (gas_amount_factor, gas_price_factor, nmb_orders_factor, intercept)
risk_parameters: (f64, f64, f64, f64),
}

/// Load the driver configuration from a TOML file.
///
/// # Panics
///
/// This method panics if the config is invalid or on I/O errors.
pub async fn load(path: &Path) -> super::Config {
let data = fs::read_to_string(path)
.await
.unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}"));
// Not printing detailed error because it could potentially leak secrets.
let config = unwrap_or_log(toml::de::from_str::<Config>(&data), &path);
super::Config {
risk: Risk {
gas_amount_factor: config.risk_parameters.0,
gas_price_factor: config.risk_parameters.1,
nmb_orders_factor: config.risk_parameters.2,
intercept: config.risk_parameters.3,
},
}
}
Loading