Skip to content

Commit

Permalink
IntoWei for driver tests (#2472)
Browse files Browse the repository at this point in the history
# Description
Working with ETH token amounts can be cumbersome because of the 18
decimal places representing values. To simplify this, an adopted method
from the e2e crate is used that handles values in terms of e18,
effectively managing the precision required for Ethereum's decimal
system.
  • Loading branch information
squadgazzz authored Mar 8, 2024
1 parent 55d7a6d commit 780431d
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 92 deletions.
10 changes: 5 additions & 5 deletions crates/driver/src/tests/cases/merge_settlements.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::tests::{
setup,
setup::{ab_order, ab_pool, ab_solution, cd_order, cd_pool, cd_solution, Solution},
cases::EtherExt,
setup::{self, ab_order, ab_pool, ab_solution, cd_order, cd_pool, cd_solution, Solution, Test},
};

/// Test that settlements can be merged.
Expand All @@ -9,7 +9,7 @@ use crate::tests::{
async fn possible() {
let ab_order = ab_order();
let cd_order = cd_order();
let test = setup()
let test: Test = setup::setup()
.pool(cd_pool())
.pool(ab_pool())
.order(ab_order.clone())
Expand Down Expand Up @@ -38,10 +38,10 @@ async fn possible() {
#[ignore]
async fn impossible() {
let order = ab_order();
let test = setup()
let test = setup::setup()
.pool(ab_pool())
.order(order.clone())
.order(order.clone().rename("reduced order").reduce_amount(1000000000000000u128.into()))
.order(order.clone().rename("reduced order").reduce_amount("1e-3".ether().into_wei()))
// These two solutions result in different clearing prices (due to different surplus),
// so they can't be merged.
.solution(ab_solution())
Expand Down
97 changes: 85 additions & 12 deletions crates/driver/src/tests/cases/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
//! Test cases.
use {
crate::{domain::eth, util::conv::u256::U256Ext},
bigdecimal::{num_traits::CheckedMul, BigDecimal, FromPrimitive, Signed},
num::BigRational,
number::conversions::big_decimal_to_big_rational,
std::str::FromStr,
};

pub mod buy_eth;
pub mod example_config;
pub mod fees;
Expand All @@ -21,28 +29,93 @@ const SOLVER_NAME: &str = "test1";

/// The default surplus factor. Set to a high value to ensure a positive score
/// by default. Use a surplus factor of 1 if you want to test negative scores.
pub const DEFAULT_SURPLUS_FACTOR: u64 = 10000000000u64;
pub const DEFAULT_SURPLUS_FACTOR: &str = "1e-8";

pub const DEFAULT_POOL_AMOUNT_A: u128 = 100000000000000000000000u128;
pub const DEFAULT_POOL_AMOUNT_B: u128 = 6000000000000000000000u128;
pub const DEFAULT_POOL_AMOUNT_C: u128 = 100000000000000000000000u128;
pub const DEFAULT_POOL_AMOUNT_D: u128 = 6000000000000000000000u128;
pub const DEFAULT_POOL_AMOUNT_A: u64 = 100000;
pub const DEFAULT_POOL_AMOUNT_B: u64 = 6000;
pub const DEFAULT_POOL_AMOUNT_C: u64 = 100000;
pub const DEFAULT_POOL_AMOUNT_D: u64 = 6000;

/// The order amount for orders selling token "A" for "B".
pub const AB_ORDER_AMOUNT: u128 = 50000000000000000000u128;
pub const AB_ORDER_AMOUNT: u64 = 50;

/// The order amount for orders selling token "C" for "D".
pub const CD_ORDER_AMOUNT: u128 = 40000000000000000000u128;
pub const CD_ORDER_AMOUNT: u64 = 40;

pub const ETH_ORDER_AMOUNT: u128 = 40000000000000000000u128;
pub const ETH_ORDER_AMOUNT: u64 = 40;

/// With the default amounts defined above, this is the expected score range for
/// both buy and sell orders.
pub const DEFAULT_SCORE_MIN: u128 = 2000000000000000000u128;
pub const DEFAULT_SCORE_MAX: u128 = 500000000000000000000000000000u128;
pub const DEFAULT_SCORE_MIN: u64 = 2;
pub const DEFAULT_SCORE_MAX: u64 = 500000000000;

/// The default solver fee for limit orders.
pub const DEFAULT_SOLVER_FEE: u128 = 100u128;
pub const DEFAULT_SOLVER_FEE: &str = "1e-16";

/// The default maximum value to be payout out to solver per solution
pub const DEFAULT_SCORE_CAP: u128 = 10000000000000000u128;
pub const DEFAULT_SCORE_CAP: &str = "1e-2";

/// A generic wrapper struct for representing amounts in Ether using high
/// precision.
///
/// The `Ether` struct wraps numeric types in `BigRational` to facilitate
/// operations and conversions related to Ether values.
pub struct Ether(BigRational);

impl Ether {
/// Converts the value into Wei, the smallest unit of Ethereum.
pub fn into_wei(self) -> eth::U256 {
BigRational::from_f64(1e18)
.and_then(|exp| self.0.checked_mul(&exp))
.and_then(|wei| eth::U256::from_big_rational(&wei).ok())
.unwrap()
}
}

/// Extension trait for numeric types to conveniently wrap values in `Ether`.
///
/// This trait provides the `ether` method for native numeric types, allowing
/// them to be easily wrapped in an `Ether` type for further conversion into
/// Wei.
///
/// # Examples
///
/// ```
/// assert_eq(1.ether().into_wei(), U256::exp10(18))
/// assert_eq(1u64.ether().into_wei(), U256::exp10(18))
/// assert_eq("1e-18".ether().into_wei(), U256::from(1)))
/// ```
pub trait EtherExt {
/// Converts a value into an `Ether` instance.
fn ether(self) -> Ether
where
Self: Sized;
}

/// Due to the precision limitations of f64, which may lead to inaccuracies when
/// dealing with values having up to 17 decimal places, converting strings
/// directly into Ether is recommended. This approach ensures
/// precise representation and manipulation of such high-precision values.
impl EtherExt for &str {
fn ether(self) -> Ether {
let value = big_decimal_to_big_rational(&BigDecimal::from_str(self).unwrap());
assert!(
!value.is_negative(),
"Ether supports non-negative values only"
);
Ether(value)
}
}

impl EtherExt for u64 {
fn ether(self) -> Ether {
Ether(BigRational::from_u64(self).unwrap())
}
}

impl EtherExt for i32 {
fn ether(self) -> Ether {
assert!(self >= 0, "Ether supports non-negative values only");
Ether(BigRational::from_i32(self).unwrap())
}
}
15 changes: 9 additions & 6 deletions crates/driver/src/tests/cases/order_prioritization.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::tests::setup::{ab_order, ab_pool, ab_solution, setup, Order};
use crate::tests::{
cases::EtherExt,
setup::{ab_order, ab_pool, ab_solution, setup, Order},
};

/// Test that orders are sorted correctly before being sent to the solver:
/// market orders come before limit orders, and orders that are more likely to
Expand All @@ -11,14 +14,14 @@ async fn sorting() {
.pool(ab_pool())
// Orders with better price ratios come first.
.order(ab_order())
.order(ab_order().reduce_amount(1000000000000000u128.into()).rename("second order"))
.order(ab_order().reduce_amount("1e-3".ether().into_wei()).rename("second order"))
// Limit orders come after market orders.
.order(
ab_order()
.rename("third order")
.limit()
)
.order(ab_order().reduce_amount(1000000000000000u128.into()).rename("fourth order").limit())
.order(ab_order().reduce_amount("1e-3".ether().into_wei()).rename("fourth order").limit())
.solution(ab_solution())
.done()
.await;
Expand All @@ -37,19 +40,19 @@ async fn filtering() {
.pool(ab_pool())
// Orders with better price ratios come first.
.order(ab_order())
.order(ab_order().reduce_amount(1000000000000000u128.into()).rename("second order"))
.order(ab_order().reduce_amount("1e-3".ether().into_wei()).rename("second order"))
// Filter out the next order, because the trader doesn't have enough balance to cover it.
.order(
ab_order()
.rename("third order")
.multiply_amount(100000000000000000u128.into())
.multiply_amount("0.1".ether().into_wei())
.filtered()
)
// Filter out the next order. It can't be fulfilled due to the balance that is required to
// fulfill the previous orders.
.order(
Order {
sell_amount: 4999999999900002000000000000000u128.into(),
sell_amount: "4999999999900.002".ether().into_wei(),
surplus_factor: 1.into(),
..ab_order()
}
Expand Down
85 changes: 43 additions & 42 deletions crates/driver/src/tests/cases/protocol_fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
domain::{competition::order, eth},
tests::{
self,
cases::EtherExt,
setup::{
ab_adjusted_pool,
ab_liquidity_quote,
Expand Down Expand Up @@ -69,13 +70,13 @@ async fn surplus_protocol_fee_buy_order_not_capped() {
let test_case = TestCase {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 100000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 100.ether().into_wei(),
executed_buy_amount: 40.ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand All @@ -92,13 +93,13 @@ async fn surplus_protocol_fee_sell_order_not_capped() {
let test_case = TestCase {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 20000000002000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 50.ether().into_wei(),
executed_buy_amount: "20.000000002".ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand All @@ -115,13 +116,13 @@ async fn surplus_protocol_fee_buy_order_capped() {
let test_case = TestCase {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 55000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 55.ether().into_wei(),
executed_buy_amount: 40.ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand All @@ -138,13 +139,13 @@ async fn surplus_protocol_fee_sell_order_capped() {
let test_case = TestCase {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 36000000000000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 50.ether().into_wei(),
executed_buy_amount: 36.ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand All @@ -157,13 +158,13 @@ async fn volume_protocol_fee_buy_order() {
let test_case = TestCase {
order_side: order::Side::Buy,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 75000000000000000000u128.into(),
executed_buy_amount: 40000000000000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 75.ether().into_wei(),
executed_buy_amount: 40.ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand All @@ -176,13 +177,13 @@ async fn volume_protocol_fee_sell_order() {
let test_case = TestCase {
order_side: order::Side::Sell,
fee_policy,
order_sell_amount: 50000000000000000000u128.into(),
solver_fee: Some(10000000000000000000u128.into()),
quote_sell_amount: 50000000000000000000u128.into(),
quote_buy_amount: 40000000000000000000u128.into(),
executed: 40000000000000000000u128.into(),
executed_sell_amount: 50000000000000000000u128.into(),
executed_buy_amount: 20000000000000000000u128.into(),
order_sell_amount: 50.ether().into_wei(),
solver_fee: Some(10.ether().into_wei()),
quote_sell_amount: 50.ether().into_wei(),
quote_buy_amount: 40.ether().into_wei(),
executed: 40.ether().into_wei(),
executed_sell_amount: 50.ether().into_wei(),
executed_buy_amount: 20.ether().into_wei(),
};

protocol_fee_test_case(test_case).await;
Expand Down
10 changes: 5 additions & 5 deletions crates/driver/src/tests/cases/score_competition.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Test that driver properly does competition.
use crate::tests::{
cases::DEFAULT_SCORE_MIN,
cases::{EtherExt, DEFAULT_SCORE_MIN},
setup::{ab_order, ab_pool, ab_solution, setup, Score},
};

Expand All @@ -12,13 +12,13 @@ async fn solver_score_winner() {
let test = setup()
.pool(ab_pool())
.order(order.clone())
.solution(ab_solution().score(Score::Solver { score: 2902421280589416499u128.into()})) // higher than objective value
.solution(ab_solution().score(Score::Solver { score: "2.902421280589416499".ether().into_wei()})) // higher than objective value
.solution(ab_solution().score(Score::RiskAdjusted{ success_probability: 0.4}))
.done()
.await;

let solve = test.solve().await.ok();
assert_eq!(solve.score(), 2902421280589416499u128.into());
assert_eq!(solve.score(), "2.902421280589416499".ether().into_wei());
solve.orders(&[order]);
test.reveal().await.ok().calldata();
}
Expand All @@ -31,7 +31,7 @@ async fn risk_adjusted_score_winner() {
.pool(ab_pool())
.order(order.clone())
.solution(ab_solution().score(Score::Solver {
score: DEFAULT_SCORE_MIN.into(),
score: DEFAULT_SCORE_MIN.ether().into_wei(),
}))
.solution(ab_solution().score(Score::RiskAdjusted {
success_probability: 0.9,
Expand All @@ -40,7 +40,7 @@ async fn risk_adjusted_score_winner() {
.await;

let solve = test.solve().await.ok();
assert!(solve.score() != DEFAULT_SCORE_MIN.into());
assert!(solve.score() != DEFAULT_SCORE_MIN.ether().into_wei());
solve.orders(&[order]);
test.reveal().await.ok().calldata();
}
4 changes: 2 additions & 2 deletions crates/driver/src/tests/cases/settle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use {
domain::competition::order,
tests::{
self,
cases::DEFAULT_SOLVER_FEE,
cases::{EtherExt, DEFAULT_SOLVER_FEE},
setup::{ab_order, ab_pool, ab_solution},
},
},
Expand All @@ -19,7 +19,7 @@ async fn matrix() {
for kind in [order::Kind::Market, order::Kind::Limit] {
let solver_fee = match kind {
order::Kind::Market => None,
order::Kind::Limit { .. } => Some(DEFAULT_SOLVER_FEE.into()),
order::Kind::Limit { .. } => Some(DEFAULT_SOLVER_FEE.ether().into_wei()),
order::Kind::Liquidity => None,
};
let test = tests::setup()
Expand Down
Loading

0 comments on commit 780431d

Please sign in to comment.