Skip to content

Commit

Permalink
Refactor surplus calculation (#2399)
Browse files Browse the repository at this point in the history
# Description
Only refactor, no new functionalities added.

I've extracted the calculation of surplus **in surplus token** into
separate function. Since this new function is not strictly related to
`solution::fee` and since it will be reused for rank by surplus feature,
I moved it to the `solution::trade` file where it belongs.

The rest of surplus functionalities needed strictly for `solution::fee`,
like `surplus_in_sell_token` is kept in that file.

## How to test
Existing tests.
  • Loading branch information
sunce86 authored Feb 13, 2024
1 parent 9a9f19a commit 99484cd
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 118 deletions.
118 changes: 25 additions & 93 deletions crates/driver/src/domain/competition/solution/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
//! Executed = 1 WETH
use {
super::trade::{Fee, Fulfillment, InvalidExecutedAmount},
super::{
trade,
trade::{ClearingPrices, Fee, Fulfillment},
},
crate::domain::{
competition::{
order,
Expand All @@ -42,13 +45,16 @@ impl Fulfillment {
let fee = match self.surplus_fee() {
None => {
if !protocol_fee.is_zero() {
return Err(Error::ProtocolFeeOnStaticOrder);
return Err(trade::Error::ProtocolFeeOnStaticOrder.into());
}
Fee::Static
}
Some(fee) => {
Fee::Dynamic((fee.0.checked_add(protocol_fee).ok_or(Error::Overflow)?).into())
}
Some(fee) => Fee::Dynamic(
(fee.0
.checked_add(protocol_fee)
.ok_or(trade::Error::Overflow)?)
.into(),
),
};

// Reduce the executed amount by the protocol fee. This is because solvers are
Expand All @@ -61,7 +67,7 @@ impl Fulfillment {
self.executed()
.0
.checked_sub(protocol_fee)
.ok_or(Error::Overflow)?,
.ok_or(trade::Error::Overflow)?,
),
};

Expand Down Expand Up @@ -131,69 +137,8 @@ impl Fulfillment {
prices: ClearingPrices,
factor: f64,
) -> Result<eth::U256, Error> {
let executed = self.executed().0;
let executed_sell_amount = match self.order().side {
Side::Buy => {
// How much `sell_token` we need to sell to buy `executed` amount of `buy_token`
executed
.checked_mul(prices.buy)
.ok_or(Error::Overflow)?
.checked_div(prices.sell)
.ok_or(Error::DivisionByZero)?
}
Side::Sell => executed,
};
// Sell slightly more `sell_token` to capture the `surplus_fee`
let executed_sell_amount_with_fee = executed_sell_amount
.checked_add(
// surplus_fee is always expressed in sell token
self.surplus_fee()
.map(|fee| fee.0)
.ok_or(Error::ProtocolFeeOnStaticOrder)?,
)
.ok_or(Error::Overflow)?;
let surplus_in_sell_token = match self.order().side {
Side::Buy => {
// Scale to support partially fillable orders
let limit_sell_amount = sell_amount
.checked_mul(executed)
.ok_or(Error::Overflow)?
.checked_div(buy_amount)
.ok_or(Error::DivisionByZero)?;
// Remaining surplus after fees
// Do not return error if `checked_sub` fails because violated limit prices will
// be caught by simulation
limit_sell_amount
.checked_sub(executed_sell_amount_with_fee)
.unwrap_or(eth::U256::zero())
}
Side::Sell => {
// Scale to support partially fillable orders
let limit_buy_amount = buy_amount
.checked_mul(executed_sell_amount_with_fee)
.ok_or(Error::Overflow)?
.checked_div(sell_amount)
.ok_or(Error::DivisionByZero)?;
// How much `buy_token` we get for `executed` amount of `sell_token`
let executed_buy_amount = executed
.checked_mul(prices.sell)
.ok_or(Error::Overflow)?
.checked_div(prices.buy)
.ok_or(Error::DivisionByZero)?;
// Remaining surplus after fees
// Do not return error if `checked_sub` fails because violated limit prices will
// be caught by simulation
let surplus = executed_buy_amount
.checked_sub(limit_buy_amount)
.unwrap_or(eth::U256::zero());
// surplus in sell token
surplus
.checked_mul(prices.buy)
.ok_or(Error::Overflow)?
.checked_div(prices.sell)
.ok_or(Error::DivisionByZero)?
}
};
let surplus = self.surplus_over_reference_price(sell_amount, buy_amount, prices)?;
let surplus_in_sell_token = self.surplus_in_sell_token(surplus, prices)?;
apply_factor(surplus_in_sell_token, factor)
}

Expand All @@ -204,9 +149,9 @@ impl Fulfillment {
// How much `sell_token` we need to sell to buy `executed` amount of `buy_token`
executed
.checked_mul(prices.buy)
.ok_or(Error::Overflow)?
.ok_or(trade::Error::Overflow)?
.checked_div(prices.sell)
.ok_or(Error::DivisionByZero)?
.ok_or(trade::Error::DivisionByZero)?
}
Side::Sell => executed,
};
Expand All @@ -216,17 +161,17 @@ impl Fulfillment {
// surplus_fee is always expressed in sell token
self.surplus_fee()
.map(|fee| fee.0)
.ok_or(Error::ProtocolFeeOnStaticOrder)?,
.ok_or(trade::Error::ProtocolFeeOnStaticOrder)?,
)
.ok_or(Error::Overflow)?;
.ok_or(trade::Error::Overflow)?;
apply_factor(executed_sell_amount_with_fee, factor)
}
}

fn apply_factor(amount: eth::U256, factor: f64) -> Result<eth::U256, Error> {
Ok(amount
.checked_mul(eth::U256::from_f64_lossy(factor * 10000.))
.ok_or(Error::Overflow)?
.ok_or(trade::Error::Overflow)?
/ 10000)
}

Expand Down Expand Up @@ -259,49 +204,36 @@ fn adjust_quote_to_order_limits(
) -> Result<(eth::U256, eth::U256), Error> {
let quote_sell_amount = quote_sell_amount
.checked_add(quote_fee_amount)
.ok_or(Error::Overflow)?;
.ok_or(trade::Error::Overflow)?;

match order_side {
Side::Sell => {
let scaled_buy_amount = quote_buy_amount
.checked_mul(order_sell_amount)
.ok_or(Error::Overflow)?
.ok_or(trade::Error::Overflow)?
.checked_div(quote_sell_amount)
.ok_or(Error::DivisionByZero)?;
.ok_or(trade::Error::DivisionByZero)?;
let buy_amount = order_buy_amount.max(scaled_buy_amount);
Ok((order_sell_amount, buy_amount))
}
Side::Buy => {
let scaled_sell_amount = quote_sell_amount
.checked_mul(order_buy_amount)
.ok_or(Error::Overflow)?
.ok_or(trade::Error::Overflow)?
.checked_div(quote_buy_amount)
.ok_or(Error::DivisionByZero)?;
.ok_or(trade::Error::DivisionByZero)?;
let sell_amount = order_sell_amount.min(scaled_sell_amount);
Ok((sell_amount, order_buy_amount))
}
}
}

/// Uniform clearing prices at which the trade was executed.
#[derive(Debug, Clone, Copy)]
pub struct ClearingPrices {
pub sell: eth::U256,
pub buy: eth::U256,
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("orders with non solver determined gas cost fees are not supported")]
ProtocolFeeOnStaticOrder,
#[error("multiple fee policies are not supported yet")]
MultipleFeePolicies,
#[error("overflow error while calculating protocol fee")]
Overflow,
#[error("division by zero error while calculating protocol fee")]
DivisionByZero,
#[error(transparent)]
InvalidExecutedAmount(#[from] InvalidExecutedAmount),
Fulfillment(#[from] trade::Error),
}

// todo: should be removed once integration tests are implemented
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
self::fee::ClearingPrices,
self::trade::ClearingPrices,
crate::{
boundary,
domain::{
Expand Down
130 changes: 118 additions & 12 deletions crates/driver/src/domain/competition/solution/trade.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use {
crate::domain::{
competition::{self, order},
competition::{
self,
order::{self, Side},
},
eth,
},
std::collections::HashMap,
Expand Down Expand Up @@ -30,7 +33,7 @@ impl Fulfillment {
order: competition::Order,
executed: order::TargetAmount,
fee: Fee,
) -> Result<Self, InvalidExecutedAmount> {
) -> Result<Self, Error> {
// If the order is partial, the total executed amount can be smaller than
// the target amount. Otherwise, the executed amount must be equal to the target
// amount.
Expand All @@ -43,8 +46,12 @@ impl Fulfillment {
}),
};

let executed_with_fee =
order::TargetAmount(executed.0.checked_add(fee.0).ok_or(InvalidExecutedAmount)?);
let executed_with_fee = order::TargetAmount(
executed
.0
.checked_add(fee.0)
.ok_or(Error::InvalidExecutedAmount)?,
);
match order.partial {
order::Partial::Yes { available } => executed_with_fee <= available,
order::Partial::No => executed_with_fee == order.target(),
Expand All @@ -65,7 +72,7 @@ impl Fulfillment {
fee,
})
} else {
Err(InvalidExecutedAmount)
Err(Error::InvalidExecutedAmount)
}
}

Expand Down Expand Up @@ -127,6 +134,93 @@ impl Fulfillment {
};
Some(eth::TokenAmount(amount))
}

/// Returns the surplus denominated in the surplus token.
///
/// The surplus token is the buy token for a sell order and sell token for a
/// buy order.
pub fn surplus_over_reference_price(
&self,
limit_sell: eth::U256,
limit_buy: eth::U256,
prices: ClearingPrices,
) -> Result<eth::U256, Error> {
let executed = self.executed().0;
let executed_sell_amount = match self.order().side {
Side::Buy => {
// How much `sell_token` we need to sell to buy `executed` amount of `buy_token`
executed
.checked_mul(prices.buy)
.ok_or(Error::Overflow)?
.checked_div(prices.sell)
.ok_or(Error::DivisionByZero)?
}
Side::Sell => executed,
};
// Sell slightly more `sell_token` to capture the `surplus_fee`
let executed_sell_amount_with_fee = executed_sell_amount
.checked_add(
// surplus_fee is always expressed in sell token
self.surplus_fee()
.map(|fee| fee.0)
.ok_or(Error::ProtocolFeeOnStaticOrder)?,
)
.ok_or(Error::Overflow)?;
let surplus = match self.order().side {
Side::Buy => {
// Scale to support partially fillable orders
let limit_sell_amount = limit_sell
.checked_mul(executed)
.ok_or(Error::Overflow)?
.checked_div(limit_buy)
.ok_or(Error::DivisionByZero)?;
// Remaining surplus after fees
// Do not return error if `checked_sub` fails because violated limit prices will
// be caught by simulation
limit_sell_amount
.checked_sub(executed_sell_amount_with_fee)
.unwrap_or(eth::U256::zero())
}
Side::Sell => {
// Scale to support partially fillable orders
let limit_buy_amount = limit_buy
.checked_mul(executed_sell_amount_with_fee)
.ok_or(Error::Overflow)?
.checked_div(limit_sell)
.ok_or(Error::DivisionByZero)?;
// How much `buy_token` we get for `executed` amount of `sell_token`
let executed_buy_amount = executed
.checked_mul(prices.sell)
.ok_or(Error::Overflow)?
.checked_div(prices.buy)
.ok_or(Error::DivisionByZero)?;
// Remaining surplus after fees
// Do not return error if `checked_sub` fails because violated limit prices will
// be caught by simulation
executed_buy_amount
.checked_sub(limit_buy_amount)
.unwrap_or(eth::U256::zero())
}
};
Ok(surplus)
}

/// Returns the surplus denominated in the sell token.
pub fn surplus_in_sell_token(
&self,
surplus: eth::U256,
prices: ClearingPrices,
) -> Result<eth::U256, Error> {
let surplus_in_sell_token = match self.order().side {
Side::Buy => surplus,
Side::Sell => surplus
.checked_mul(prices.buy)
.ok_or(Error::Overflow)?
.checked_div(prices.sell)
.ok_or(Error::DivisionByZero)?,
};
Ok(surplus_in_sell_token)
}
}

/// A fee that is charged for executing an order.
Expand All @@ -140,6 +234,13 @@ pub enum Fee {
Dynamic(order::SellAmount),
}

/// Uniform clearing prices at which the trade was executed.
#[derive(Debug, Clone, Copy)]
pub struct ClearingPrices {
pub sell: eth::U256,
pub buy: eth::U256,
}

/// A trade which adds a JIT order. See [`order::Jit`].
#[derive(Debug, Clone)]
pub struct Jit {
Expand All @@ -152,10 +253,7 @@ pub struct Jit {
}

impl Jit {
pub fn new(
order: order::Jit,
executed: order::TargetAmount,
) -> Result<Self, InvalidExecutedAmount> {
pub fn new(order: order::Jit, executed: order::TargetAmount) -> Result<Self, Error> {
// If the order is partially fillable, the executed amount can be smaller than
// the target amount. Otherwise, the executed amount must be equal to the target
// amount.
Expand All @@ -167,7 +265,7 @@ impl Jit {
if is_valid {
Ok(Self { order, executed })
} else {
Err(InvalidExecutedAmount)
Err(Error::InvalidExecutedAmount)
}
}

Expand All @@ -190,5 +288,13 @@ pub struct Execution {
}

#[derive(Debug, thiserror::Error)]
#[error("invalid executed amount")]
pub struct InvalidExecutedAmount;
pub enum Error {
#[error("orders with non solver determined gas cost fees are not supported")]
ProtocolFeeOnStaticOrder,
#[error("overflow error while calculating protocol fee")]
Overflow,
#[error("division by zero error while calculating protocol fee")]
DivisionByZero,
#[error("invalid executed amount")]
InvalidExecutedAmount,
}
Loading

0 comments on commit 99484cd

Please sign in to comment.