Skip to content

Commit

Permalink
Reveal settled order already in /solve Response (#1975)
Browse files Browse the repository at this point in the history
# Description
Implements only the part

> orders they intend to execute and their in/out amounts

from #1949

Orders are no longer sent as a reponse to `/reveal` but as a response to
`/solve`. I also added in/out amounts for each order.

---------

Co-authored-by: Felix Leupold <[email protected]>
  • Loading branch information
sunce86 and fleupold authored Dec 8, 2023
1 parent b146193 commit bd466e5
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 143 deletions.
17 changes: 15 additions & 2 deletions crates/autopilot/src/driver_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub mod solve {
primitive_types::{H160, U256},
serde::{Deserialize, Serialize},
serde_with::{serde_as, DisplayFromStr},
std::collections::HashMap,
};

#[serde_as]
Expand Down Expand Up @@ -136,6 +137,18 @@ pub mod solve {
pub call_data: Vec<u8>,
}

#[serde_as]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TradedAmounts {
/// The effective amount that left the user's wallet including all fees.
#[serde_as(as = "HexOrDecimalU256")]
pub sell_amount: U256,
/// The effective amount the user received after all fees.
#[serde_as(as = "HexOrDecimalU256")]
pub buy_amount: U256,
}

#[serde_as]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand All @@ -148,6 +161,7 @@ pub mod solve {
pub score: U256,
/// Address used by the driver to submit the settlement onchain.
pub submission_address: H160,
pub orders: HashMap<OrderUid, TradedAmounts>,
}

#[derive(Clone, Debug, Default, Deserialize)]
Expand All @@ -159,7 +173,7 @@ pub mod solve {

pub mod reveal {
use {
model::{bytes_hex, order::OrderUid},
model::bytes_hex,
serde::{Deserialize, Serialize},
serde_with::serde_as,
};
Expand All @@ -186,7 +200,6 @@ pub mod reveal {
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Response {
pub orders: Vec<OrderUid>,
pub calldata: Calldata,
}
}
Expand Down
64 changes: 34 additions & 30 deletions crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use {
driver_model::{
reveal::{self, Request},
settle,
solve::{self, Class},
solve::{self, Class, TradedAmounts},
},
solvable_orders::SolvableOrdersCache,
},
Expand All @@ -34,7 +34,7 @@ use {
rand::seq::SliceRandom,
shared::{remaining_amounts, token_list::AutoUpdatingTokenList},
std::{
collections::{BTreeMap, HashSet},
collections::{BTreeMap, HashMap, HashSet},
sync::{Arc, Mutex},
time::{Duration, Instant},
},
Expand Down Expand Up @@ -149,9 +149,8 @@ impl RunLoop {
}
};

let events = revealed
.orders
.iter()
let events = solution
.order_ids()
.map(|o| (*o, OrderEventLabel::Considered))
.collect::<Vec<_>>();
self.database.store_order_events(&events).await;
Expand All @@ -178,7 +177,7 @@ impl RunLoop {
// Save order executions for all orders in the solution. Surplus fees for
// limit orders will be saved after settling the order onchain.
let mut order_executions = vec![];
for order_id in &revealed.orders {
for order_id in solution.order_ids() {
let auction_order = auction
.orders
.iter()
Expand Down Expand Up @@ -239,22 +238,21 @@ impl RunLoop {
solver_address: participant.solution.account,
score: Some(Score::Solver(participant.solution.score.get())),
ranking: Some(solutions.len() - index),
orders: participant
.solution
.orders()
.iter()
.map(|(id, order)| Order::Colocated {
id: *id,
sell_amount: order.sell_amount,
buy_amount: order.buy_amount,
})
.collect(),
// TODO: revisit once colocation is enabled (remove not populated
// fields) Not all fields can be populated in the colocated world
..Default::default()
};
if is_winner {
settlement.orders = revealed
.orders
.iter()
.map(|o| Order {
id: *o,
// TODO: revisit once colocation is enabled (remove not
// populated fields) Not all
// fields can be populated in the colocated world
..Default::default()
})
.collect();
settlement.call_data = revealed.calldata.internalized.clone();
settlement.uninternalized_call_data =
Some(revealed.calldata.uninternalized.clone());
Expand Down Expand Up @@ -288,7 +286,7 @@ impl RunLoop {
}

tracing::info!(driver = %driver.name, "settling");
match self.settle(driver, solution, &revealed).await {
match self.settle(driver, solution).await {
Ok(()) => Metrics::settle_ok(driver),
Err(err) => {
Metrics::settle_err(driver, &err);
Expand Down Expand Up @@ -385,6 +383,7 @@ impl RunLoop {
id: solution.solution_id,
account: solution.submission_address,
score: NonZeroU256::new(solution.score).ok_or(ZeroScoreError)?,
orders: solution.orders,
})
})
.collect())
Expand Down Expand Up @@ -414,15 +413,9 @@ impl RunLoop {

/// Execute the solver's solution. Returns Ok when the corresponding
/// transaction has been mined.
async fn settle(
&self,
driver: &Driver,
solved: &Solution,
revealed: &reveal::Response,
) -> Result<(), SettleError> {
let events = revealed
.orders
.iter()
async fn settle(&self, driver: &Driver, solved: &Solution) -> Result<(), SettleError> {
let events = solved
.order_ids()
.map(|uid| (*uid, OrderEventLabel::Executing))
.collect_vec();
self.database.store_order_events(&events).await;
Expand All @@ -439,12 +432,12 @@ impl RunLoop {

*self.in_flight_orders.lock().unwrap() = InFlightOrders {
tx_hash,
orders: revealed.orders.iter().cloned().collect(),
orders: solved.orders.keys().copied().collect(),
};

let events = revealed
let events = solved
.orders
.iter()
.keys()
.map(|uid| (*uid, OrderEventLabel::Traded))
.collect_vec();
self.database.store_order_events(&events).await;
Expand Down Expand Up @@ -583,6 +576,17 @@ struct Solution {
id: u64,
account: H160,
score: NonZeroU256,
orders: HashMap<OrderUid, TradedAmounts>,
}

impl Solution {
pub fn order_ids(&self) -> impl Iterator<Item = &OrderUid> {
self.orders.keys()
}

pub fn orders(&self) -> &HashMap<OrderUid, TradedAmounts> {
&self.orders
}
}

#[derive(Debug, thiserror::Error)]
Expand Down
25 changes: 20 additions & 5 deletions crates/driver/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use {
},
futures::{stream::FuturesUnordered, Stream, StreamExt},
itertools::Itertools,
std::{collections::HashSet, sync::Mutex},
std::{
collections::{HashMap, HashSet},
sync::Mutex,
},
tap::TapFallible,
};

Expand Down Expand Up @@ -171,7 +174,15 @@ impl Competition {
let (mut score, settlement) = scores
.into_iter()
.max_by_key(|(score, _)| score.to_owned())
.map(|(score, settlement)| (Solved { score }, settlement))
.map(|(score, settlement)| {
(
Solved {
score,
trades: settlement.orders(),
},
settlement,
)
})
.unzip();

*self.settlement.lock().unwrap() = settlement.clone();
Expand Down Expand Up @@ -217,7 +228,6 @@ impl Competition {
.cloned()
.ok_or(Error::SolutionNotAvailable)?;
Ok(Revealed {
orders: settlement.orders(),
internalized_calldata: settlement
.calldata(
self.eth.contracts().settlement(),
Expand Down Expand Up @@ -335,15 +345,20 @@ async fn merge_settlements(
#[derive(Debug)]
pub struct Solved {
pub score: Score,
pub trades: HashMap<order::Uid, Amounts>,
}

#[derive(Debug, Default)]
pub struct Amounts {
pub sell: eth::TokenAmount,
pub buy: eth::TokenAmount,
}

/// Winning solution information revealed to the protocol by the driver before
/// the onchain settlement happens. Calldata is first time revealed at this
/// point.
#[derive(Debug)]
pub struct Revealed {
/// The orders solved by this solution.
pub orders: HashSet<order::Uid>,
/// The internalized calldata is the final calldata that appears onchain.
pub internalized_calldata: Bytes<Vec<u8>>,
/// The uninternalized calldata must be known so that the CoW solver team
Expand Down
24 changes: 20 additions & 4 deletions crates/driver/src/domain/competition/solution/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,28 @@ impl Settlement {
self.boundary.solver
}

/// The settled user orders.
pub fn orders(&self) -> HashSet<order::Uid> {
/// The settled user orders with their in/out amounts.
pub fn orders(&self) -> HashMap<order::Uid, competition::Amounts> {
self.solutions
.values()
.flat_map(|solution| solution.user_trades().map(|trade| trade.order().uid))
.collect()
.fold(Default::default(), |mut acc, solution| {
for trade in solution.user_trades() {
let order = acc.entry(trade.order().uid).or_default();
order.sell = trade.sell_amount(&solution.prices).unwrap_or_else(|| {
// This should never happen, returning 0 is better than panicking, but we
// should still alert.
tracing::error!(uid = ?trade.order().uid, "could not compute sell_amount");
0.into()
});
order.buy = trade.buy_amount(&solution.prices).unwrap_or_else(|| {
// This should never happen, returning 0 is better than panicking, but we
// should still alert.
tracing::error!(uid = ?trade.order().uid, "could not compute buy_amount");
0.into()
});
}
acc
})
}

/// Settlements have valid notify ID only if they are originated from a
Expand Down
43 changes: 40 additions & 3 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},
eth,
use {
crate::domain::{
competition::{self, order},
eth,
},
std::collections::HashMap,
};

/// A trade which executes an order as part of this solution.
Expand Down Expand Up @@ -84,6 +87,40 @@ impl Fulfillment {
Fee::Dynamic(fee) => fee,
}
}

/// The effective amount that left the user's wallet including all fees.
pub fn sell_amount(
&self,
prices: &HashMap<eth::TokenAddress, eth::U256>,
) -> Option<eth::TokenAmount> {
let before_fee = match self.order.side {
order::Side::Sell => self.executed.0,
order::Side::Buy => self
.executed
.0
.checked_mul(*prices.get(&self.order.buy.token)?)?
.checked_div(*prices.get(&self.order.sell.token)?)?,
};
Some(eth::TokenAmount(
before_fee.checked_add(self.solver_fee().0)?,
))
}

/// The effective amount the user received after all fees.
pub fn buy_amount(
&self,
prices: &HashMap<eth::TokenAddress, eth::U256>,
) -> Option<eth::TokenAmount> {
let amount = match self.order.side {
order::Side::Buy => self.executed.0,
order::Side::Sell => self
.executed
.0
.checked_mul(*prices.get(&self.order.sell.token)?)?
.checked_div(*prices.get(&self.order.buy.token)?)?,
};
Some(eth::TokenAmount(amount))
}
}

/// A fee that is charged for executing an order.
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl TokenAddress {
/// An ERC20 token amount.
///
/// https://eips.ethereum.org/EIPS/eip-20
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TokenAmount(pub U256);

impl From<U256> for TokenAmount {
Expand Down
8 changes: 1 addition & 7 deletions crates/driver/src/infra/api/routes/reveal/dto/revealed.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use {
crate::{
domain::{competition, competition::order},
util::serialize,
},
crate::{domain::competition, util::serialize},
serde::Serialize,
serde_with::serde_as,
};

impl Revealed {
pub fn new(reveal: competition::Revealed) -> Self {
Self {
orders: reveal.orders.into_iter().map(Into::into).collect(),
calldata: Calldata {
internalized: reveal.internalized_calldata.into(),
uninternalized: reveal.uninternalized_calldata.into(),
Expand All @@ -23,8 +19,6 @@ impl Revealed {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Revealed {
#[serde_as(as = "Vec<serialize::Hex>")]
orders: Vec<[u8; order::UID_LEN]>,
calldata: Calldata,
}

Expand Down
Loading

0 comments on commit bd466e5

Please sign in to comment.