Skip to content

Commit

Permalink
Move competition::Solution to domain (#2725)
Browse files Browse the repository at this point in the history
# Description
A preparation PR for task
#2718

Moves `Solution` to `domain` and refactors the rest of the code to reuse
this object.

## How to test
existing tests
  • Loading branch information
sunce86 authored May 13, 2024
1 parent a80da50 commit ed2e67d
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 53 deletions.
66 changes: 66 additions & 0 deletions crates/autopilot/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use {
super::{auction, eth},
crate::domain,
number::nonzero::U256 as NonZeroU256,
std::collections::HashMap,
};

type SolutionId = u64;

pub struct Solution {
id: SolutionId,
account: eth::Address,
score: NonZeroU256,
orders: HashMap<domain::OrderUid, TradedAmounts>,
// uniform prices for all tokens
prices: HashMap<eth::TokenAddress, auction::Price>,
}

impl Solution {
pub fn new(
id: SolutionId,
account: eth::Address,
score: NonZeroU256,
orders: HashMap<domain::OrderUid, TradedAmounts>,
prices: HashMap<eth::TokenAddress, auction::Price>,
) -> Self {
Self {
id,
account,
score,
orders,
prices,
}
}

pub fn id(&self) -> SolutionId {
self.id
}

pub fn account(&self) -> eth::Address {
self.account
}

pub fn score(&self) -> NonZeroU256 {
self.score
}

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

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

pub fn prices(&self) -> &HashMap<eth::TokenAddress, auction::Price> {
&self.prices
}
}

pub struct TradedAmounts {
/// The effective amount that left the user's wallet including all fees.
pub sell: eth::TokenAmount,
/// The effective amount the user received after all fees.
pub buy: eth::TokenAmount,
}
4 changes: 4 additions & 0 deletions crates/autopilot/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use derive_more::{From, Into};
pub use primitive_types::{H160, U256};

/// An address. Can be an EOA or a smart contract address.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
pub struct Address(pub H160);

/// An ERC20 token address.
///
/// https://eips.ethereum.org/EIPS/eip-20
Expand Down
1 change: 1 addition & 0 deletions crates/autopilot/src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod auction;
pub mod competition;
pub mod eth;
pub mod fee;
pub mod quote;
Expand Down
120 changes: 67 additions & 53 deletions crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use {
crate::{
database::competition::Competition,
domain::{self, auction::order::Class, OrderUid},
domain::{
self,
auction::{self, order::Class, InvalidPrice},
competition::{self},
OrderUid,
},
infra::{
self,
solvers::dto::{reveal, settle, solve},
Expand All @@ -20,7 +25,7 @@ use {
SolverSettlement,
},
number::nonzero::U256 as NonZeroU256,
primitive_types::{H160, H256, U256},
primitive_types::{H160, H256},
rand::seq::SliceRandom,
shared::token_list::AutoUpdatingTokenList,
std::{
Expand Down Expand Up @@ -121,16 +126,16 @@ impl RunLoop {

// Shuffle so that sorting randomly splits ties.
solutions.shuffle(&mut rand::thread_rng());
solutions.sort_unstable_by_key(|participant| participant.solution.score);
solutions.sort_unstable_by_key(|participant| participant.solution.score());
solutions
};
let competition_simulation_block = self.eth.current_block().borrow().number;

// TODO: Keep going with other solutions until some deadline.
if let Some(Participant { driver, solution }) = solutions.last() {
tracing::info!(driver = %driver.name, solution = %solution.id, "winner");
tracing::info!(driver = %driver.name, solution = %solution.id(), "winner");

let revealed = match self.reveal(driver, auction_id, solution.id).await {
let revealed = match self.reveal(driver, auction_id, solution.id()).await {
Ok(result) => {
Metrics::reveal_ok(driver);
result
Expand All @@ -146,16 +151,16 @@ impl RunLoop {
self.persistence
.store_order_events(order_uids, OrderEventLabel::Considered);

let winner = solution.account;
let winning_score = solution.score.get();
let winner = solution.account().into();
let winning_score = solution.score().get();
let reference_score = solutions
.iter()
.nth_back(1)
.map(|participant| participant.solution.score.get())
.map(|participant| participant.solution.score().get())
.unwrap_or_default();
let participants = solutions
.iter()
.map(|participant| participant.solution.account)
.map(|participant| participant.solution.account().into())
.collect::<HashSet<_>>();

let mut prices = BTreeMap::new();
Expand Down Expand Up @@ -215,24 +220,24 @@ impl RunLoop {
let is_winner = solutions.len() - index == 1;
let mut settlement = SolverSettlement {
solver: participant.driver.name.clone(),
solver_address: participant.solution.account,
score: Some(Score::Solver(participant.solution.score.get())),
solver_address: participant.solution.account().0,
score: Some(Score::Solver(participant.solution.score().get())),
ranking: solutions.len() - index,
orders: participant
.solution
.orders()
.iter()
.map(|(id, order)| Order::Colocated {
id: (*id).into(),
sell_amount: order.sell_amount,
buy_amount: order.buy_amount,
sell_amount: order.sell.into(),
buy_amount: order.buy.into(),
})
.collect(),
clearing_prices: participant
.solution
.clearing_prices
.prices()
.iter()
.map(|(token, price)| (*token, *price))
.map(|(token, price)| (token.0, price.get().into()))
.collect(),
call_data: None,
uninternalized_call_data: None,
Expand Down Expand Up @@ -285,10 +290,11 @@ impl RunLoop {
tracing::warn!(?err, driver = %driver.name, "settlement failed");
}
}
let solution_uids = solution.order_ids().copied().collect::<HashSet<_>>();
let unsettled_orders: HashSet<_> = solutions
.iter()
.flat_map(|p| p.solution.orders.keys())
.filter(|uid| !solution.orders.contains_key(uid))
.flat_map(|p| p.solution.order_ids())
.filter(|uid| !solution_uids.contains(uid))
.collect();
Metrics::matched_unsettled(driver, unsettled_orders);
}
Expand Down Expand Up @@ -355,7 +361,7 @@ impl RunLoop {
&self,
driver: &infra::Driver,
request: &solve::Request,
) -> Result<Vec<Result<Solution, ZeroScoreError>>, SolveError> {
) -> Result<Vec<Result<competition::Solution, SolutionError>>, SolveError> {
let response = tokio::time::timeout(self.solve_deadline, driver.solve(request))
.await
.map_err(|_| SolveError::Timeout)?
Expand All @@ -368,17 +374,31 @@ impl RunLoop {
.solutions
.into_iter()
.map(|solution| {
Ok(Solution {
id: solution.solution_id,
account: solution.submission_address,
score: NonZeroU256::new(solution.score).ok_or(ZeroScoreError)?,
orders: solution
.orders
.into_iter()
.map(|(o, amounts)| (o.into(), amounts))
.collect(),
clearing_prices: solution.clearing_prices,
})
let mut prices = HashMap::new();
for (token, price) in solution.clearing_prices.into_iter() {
prices.insert(token.into(), auction::Price::new(price.into())?);
}
let orders = solution
.orders
.into_iter()
.map(|(o, amounts)| {
(
o.into(),
competition::TradedAmounts {
sell: amounts.sell_amount.into(),
buy: amounts.buy_amount.into(),
},
)
})
.collect();

Ok(competition::Solution::new(
solution.solution_id,
solution.submission_address.into(),
NonZeroU256::new(solution.score).ok_or(ZeroScoreError)?,
orders,
prices,
))
})
.collect())
}
Expand Down Expand Up @@ -410,22 +430,22 @@ impl RunLoop {
async fn settle(
&self,
driver: &infra::Driver,
solved: &Solution,
solved: &competition::Solution,
auction_id: i64,
) -> Result<(), SettleError> {
let order_ids = solved.order_ids().copied().collect();
self.persistence
.store_order_events(order_ids, OrderEventLabel::Executing);

let request = settle::Request {
solution_id: solved.id,
solution_id: solved.id(),
};
let tx_hash = self
.wait_for_settlement(driver, auction_id, request)
.await?;
*self.in_flight_orders.lock().await = Some(InFlightOrders {
tx_hash,
orders: solved.orders.keys().copied().collect(),
orders: solved.order_ids().copied().collect(),
});
tracing::debug!(?tx_hash, "solution settled");

Expand Down Expand Up @@ -533,25 +553,7 @@ pub struct InFlightOrders {

struct Participant<'a> {
driver: &'a infra::Driver,
solution: Solution,
}

struct Solution {
id: u64,
account: H160,
score: NonZeroU256,
orders: HashMap<domain::OrderUid, solve::TradedAmounts>,
clearing_prices: HashMap<H160, U256>,
}

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

pub fn orders(&self) -> &HashMap<domain::OrderUid, solve::TradedAmounts> {
&self.orders
}
solution: competition::Solution,
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -564,6 +566,14 @@ enum SolveError {
Failure(anyhow::Error),
}

#[derive(Debug, thiserror::Error)]
enum SolutionError {
#[error(transparent)]
ZeroScore(#[from] ZeroScoreError),
#[error(transparent)]
InvalidPrice(#[from] InvalidPrice),
}

#[derive(Debug, thiserror::Error)]
#[error("the solver proposed a 0-score solution")]
struct ZeroScoreError;
Expand Down Expand Up @@ -654,10 +664,14 @@ impl Metrics {
.inc();
}

fn solution_err(driver: &infra::Driver, _: &ZeroScoreError) {
fn solution_err(driver: &infra::Driver, err: &SolutionError) {
let label = match err {
SolutionError::ZeroScore(_) => "zero_score",
SolutionError::InvalidPrice(_) => "invalid_price",
};
Self::get()
.solutions
.with_label_values(&[&driver.name, "zero_score"])
.with_label_values(&[&driver.name, label])
.inc();
}

Expand Down

0 comments on commit ed2e67d

Please sign in to comment.