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, Clone)]
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())
}
}
27 changes: 25 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, Risk},
util,
},
ethereum_types::{Address, U256},
Expand All @@ -22,6 +22,18 @@ impl Solution {
Self { score, ..self }
}

pub fn with_risk_adjusted_score(
self,
risk: &Risk,
gas: eth::Gas,
gas_price: auction::GasPrice,
) -> Self {
let nmb_orders = self.trades.len();
self.with_score(Score::RiskAdjusted(
risk.success_probability(gas, gas_price, nmb_orders),
))
}

/// Returns `self` with eligible interactions internalized using the
/// Settlement contract buffers.
///
Expand Down Expand Up @@ -111,6 +123,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 @@ -183,7 +196,7 @@ impl Single {
]),
trades: vec![Trade::Fulfillment(Fulfillment::new(order, executed, fee)?)],
interactions,
score: Default::default(),
score,
})
}
}
Expand Down Expand Up @@ -379,3 +392,13 @@ impl Default for Score {
Self::RiskAdjusted(1.0)
}
}

// initial tx gas used to call the settle function from the settlement contract
pub const INITIALIZATION_COST: u64 = 32_000;
/// minimum gas every settlement takes (isSolver)
pub const SETTLEMENT: u64 = 7365;
/// lower bound for an erc20 transfer.
///
/// Value was computed by taking 52 percentile median of `transfer()` costs
/// of the 90% most traded tokens by volume in the month of Oct. 2021.
pub const ERC20_TRANSFER: u64 = 27_513;
13 changes: 12 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 @@ -117,6 +122,12 @@ 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,
));

Some(
solution::Single {
order: order.clone(),
Expand All @@ -125,7 +136,7 @@ impl Inner {
interactions,
gas: route.gas(),
}
.into_solution(auction.gas_price, sell_token)?
.into_solution(auction.gas_price, sell_token, score)?
.with_buffers_internalizations(&auction.tokens),
)
})
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
28 changes: 25 additions & 3 deletions crates/solvers/src/domain/solver/naive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@
use {
crate::{
boundary,
domain::{auction, liquidity, order, solution},
domain::{self, auction, eth, liquidity, order, solution},
infra::config,
},
std::collections::HashMap,
};

pub struct Naive;
pub struct Naive {
/// Parameters used to calculate the revert risk of a solution.
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> {
let risk = self.risk.clone();
// Make sure to push the CPU-heavy code to a separate thread in order to
// not lock up the [`tokio`] runtime and cause it to slow down handling
// the real async things.
Expand All @@ -28,7 +38,19 @@ impl Naive {
let groups = group_by_token_pair(&auction);
groups
.values()
.filter_map(|group| boundary::naive::solve(&group.orders, group.liquidity))
.filter_map(|group| {
boundary::naive::solve(&group.orders, group.liquidity).map(|solution| {
let gas = solution::INITIALIZATION_COST
+ solution::SETTLEMENT
+ solution::ERC20_TRANSFER * solution.trades.len() as u64 * 2
+ group.liquidity.gas.0.as_u64(); // this is pessimistic in case the pool is not used
solution.with_risk_adjusted_score(
&risk,
eth::Gas(gas.into()),
auction.gas_price,
)
})
})
.map(|solution| solution.with_buffers_internalizations(&auction.tokens))
.collect()
})
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,
}
1 change: 1 addition & 0 deletions crates/solvers/src/infra/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Debug;
pub mod baseline;
pub mod dex;
pub mod legacy;
pub mod naive;

/// Unwraps result or logs a `TOML` parsing error.
fn unwrap_or_log<T, E, P>(result: Result<T, E>, path: &P) -> T
Expand Down
Loading