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

Driver: Include non-surplus-capturing-jit-orders in the driver /solve #3048

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/driver/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ components:
enum: ["erc20", "internal"]
class:
type: string
enum: ["market", "limit", "liquidity"]
enum: ["market", "limit"]
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
appData:
description: 32 bytes encoded as hex with `0x` prefix.
type: string
Expand Down
72 changes: 48 additions & 24 deletions crates/driver/src/domain/competition/solution/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use {
super::{encoding, trade::ClearingPrices, Error, Solution},
crate::{
domain::{
competition::{self, auction, order, solution},
competition::{
self,
auction,
order::{self},
solution::{self, error, Trade},
},
eth,
},
infra::{blockchain::Ethereum, observe, solver::ManageNativeToken, Simulator},
Expand Down Expand Up @@ -254,30 +259,49 @@ impl Settlement {

/// The settled user orders with their in/out amounts.
pub fn orders(&self) -> HashMap<order::Uid, competition::Amounts> {
let log_err = |trade: &Trade, err: error::Math, kind: &str| -> eth::TokenAmount {
// This should never happen, returning 0 is better than panicking, but we
// should still alert.
let msg = format!("could not compute {kind}");
tracing::error!(?trade, prices=?self.solution.prices, ?err, msg);
0.into()
};
let mut acc: HashMap<order::Uid, competition::Amounts> = HashMap::new();
for trade in self.solution.user_trades() {
let prices = ClearingPrices {
sell: self.solution.prices[&trade.order().sell.token.wrap(self.solution.weth)],
buy: self.solution.prices[&trade.order().buy.token.wrap(self.solution.weth)],
};
let order = competition::Amounts {
side: trade.order().side,
sell: trade.order().sell,
buy: trade.order().buy,
executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| {
// This should never happen, returning 0 is better than panicking, but we
// should still alert.
tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount");
0.into()
}),
executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| {
// This should never happen, returning 0 is better than panicking, but we
// should still alert.
tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount");
0.into()
}),
};
acc.insert(trade.order().uid, order);
for trade in &self.solution.trades {
match trade {
Trade::Fulfillment(_) => {
let prices = ClearingPrices {
sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)],
buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)],
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
};
let order = competition::Amounts {
side: trade.side(),
sell: trade.sell(),
buy: trade.buy(),
executed_sell: trade
.sell_amount(&prices)
.unwrap_or_else(|err| log_err(trade, err, "sell_amount")),
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
executed_buy: trade
.buy_amount(&prices)
.unwrap_or_else(|err| log_err(trade, err, "buy_amount")),
};
acc.insert(trade.uid(), order);
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
}
Trade::Jit(jit) => {
let order = competition::Amounts {
side: trade.side(),
sell: trade.sell(),
buy: trade.buy(),
executed_sell: jit
.executed_sell()
.unwrap_or_else(|err| log_err(trade, err, "sell_amount")),
executed_buy: jit
.executed_buy()
.unwrap_or_else(|err| log_err(trade, err, "buy_amount")),
};
acc.insert(trade.uid(), order);
}
}
}
acc
}
Expand Down
54 changes: 51 additions & 3 deletions crates/driver/src/domain/competition/solution/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
domain::{
competition::{
self,
order::{self, FeePolicy, SellAmount, Side, TargetAmount},
order::{self, FeePolicy, SellAmount, Side, TargetAmount, Uid},
solution::error::{self, Math},
},
eth::{self, Asset},
Expand All @@ -19,6 +19,13 @@ pub enum Trade {
}

impl Trade {
pub fn uid(&self) -> Uid {
match self {
Trade::Fulfillment(fulfillment) => fulfillment.order().uid,
Trade::Jit(jit) => jit.order().uid,
}
}

pub fn side(&self) -> Side {
match self {
Trade::Fulfillment(fulfillment) => fulfillment.order().side,
Expand Down Expand Up @@ -62,7 +69,10 @@ impl Trade {
}

/// The effective amount that left the user's wallet including all fees.
fn sell_amount(&self, prices: &ClearingPrices) -> Result<eth::TokenAmount, error::Math> {
pub(crate) fn sell_amount(
&self,
prices: &ClearingPrices,
) -> Result<eth::TokenAmount, error::Math> {
let before_fee = match self.side() {
order::Side::Sell => self.executed().0,
order::Side::Buy => self
Expand All @@ -81,7 +91,10 @@ impl Trade {
/// The effective amount the user received after all fees.
///
/// Settlement contract uses `ceil` division for buy amount calculation.
fn buy_amount(&self, prices: &ClearingPrices) -> Result<eth::TokenAmount, error::Math> {
pub(crate) fn buy_amount(
&self,
prices: &ClearingPrices,
) -> Result<eth::TokenAmount, error::Math> {
let amount = match self.side() {
order::Side::Buy => self.executed().0,
order::Side::Sell => self
Expand Down Expand Up @@ -409,6 +422,41 @@ impl Jit {
self.executed
}

pub fn executed_buy(&self) -> Result<eth::TokenAmount, Math> {
Ok(match self.order().side {
Side::Buy => self.executed().into(),
Side::Sell => (self
.executed()
.0
.checked_add(self.fee().0)
.ok_or(Math::Overflow)?)
.checked_mul(self.order.buy.amount.0)
.ok_or(Math::Overflow)?
.checked_ceil_div(&self.order.sell.amount.0)
.ok_or(Math::DivisionByZero)?
.into(),
})
}

pub fn executed_sell(&self) -> Result<eth::TokenAmount, Math> {
Ok(match self.order().side {
Side::Buy => self
.executed()
.0
.checked_mul(self.order.sell.amount.0)
.ok_or(Math::Overflow)?
.checked_div(self.order.buy.amount.0)
.ok_or(Math::DivisionByZero)?
.into(),
Side::Sell => self
.executed()
.0
.checked_add(self.fee().0)
.ok_or(Math::Overflow)?
.into(),
})
}

pub fn fee(&self) -> order::SellAmount {
self.fee
}
Expand Down
4 changes: 3 additions & 1 deletion crates/driver/src/tests/cases/jit_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ struct TestCase {

#[cfg(test)]
async fn protocol_fee_test_case(test_case: TestCase) {
let test_name = format!("JIT Order: {:?}", test_case.order.side);
let test_name = format!("JIT Order: {:?}", test_case.solution.jit_order.order.side);
// Adjust liquidity pools so that the order is executable at the amounts
// expected from the solver.
let quote = ab_liquidity_quote()
Expand Down Expand Up @@ -83,6 +83,7 @@ async fn protocol_fee_test_case(test_case: TestCase) {
.no_surplus();

let solver = test_solver();

let test: Test = tests::setup()
.name(test_name)
.pool(pool)
Expand All @@ -104,6 +105,7 @@ async fn protocol_fee_test_case(test_case: TestCase) {
result.score(),
test_case.solution.expected_score,
));
result.jit_orders(&[jit_order]);
}

#[tokio::test]
Expand Down
35 changes: 35 additions & 0 deletions crates/driver/src/tests/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,41 @@ impl<'a> SolveOk<'a> {
assert!(self.solutions().is_empty());
}

/// Check that the solution contains the expected JIT orders.
pub fn jit_orders(self, jit_orders: &[JitOrder]) -> Self {
let solution = self.solution();
assert!(solution.get("orders").is_some());
let trades = serde_json::from_value::<HashMap<String, serde_json::Value>>(
solution.get("orders").unwrap().clone(),
)
.unwrap();

// Since JIT orders don't have UID at creation time, we need to search for
// matching token pair
for expected in jit_orders.iter() {
for trade in trades.values() {
let u256 = |value: &serde_json::Value| {
eth::U256::from_dec_str(value.as_str().unwrap()).unwrap()
};
let sell_token = trade.get("sellToken").unwrap().to_string();
let buy_token = trade.get("buyToken").unwrap().to_string();

if sell_token == expected.order.sell_token && buy_token == expected.order.buy_token
{
assert!(is_approximately_equal(
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
expected.order.sell_amount,
u256(trade.get("executedSell").unwrap())
));
assert!(is_approximately_equal(
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
expected.order.buy_amount.unwrap(),
u256(trade.get("executedBuy").unwrap())
));
}
}
}
self
}

/// Check that the solution contains the expected orders.
pub fn orders(self, orders: &[Order]) -> Self {
let solution = self.solution();
Expand Down
31 changes: 19 additions & 12 deletions crates/driver/src/tests/setup/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,18 +270,25 @@ impl Solver {
}).collect_vec(),
})
}));
prices_json.insert(
config
.blockchain
.get_token_wrapped(jit.quoted_order.order.sell_token),
jit.execution.buy.to_string(),
);
prices_json.insert(
config
.blockchain
.get_token_wrapped(jit.quoted_order.order.buy_token),
(jit.execution.sell - jit.quoted_order.order.surplus_fee()).to_string(),
);
// Skipping the prices for JIT orders (non-surplus-capturing)
if config
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
.expected_surplus_capturing_jit_order_owners
.contains(&jit.quoted_order.order.owner)
{
prices_json.insert(
m-lord-renkse marked this conversation as resolved.
Show resolved Hide resolved
config
.blockchain
.get_token_wrapped(jit.quoted_order.order.sell_token),
jit.execution.buy.to_string(),
);
prices_json.insert(
config
.blockchain
.get_token_wrapped(jit.quoted_order.order.buy_token),
(jit.execution.sell - jit.quoted_order.order.surplus_fee())
.to_string(),
);
}
{
let executed_amount = match jit.quoted_order.order.executed {
Some(executed) => executed.to_string(),
Expand Down
Loading