Skip to content

Commit

Permalink
Autopilot refactor 2/3: Build Observation object (#2835)
Browse files Browse the repository at this point in the history
# Description
Ports building observation data which is supposed to be saved to the
database.

Equivalent to:
https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/on_settlement_event_updater.rs#L243-L260

## How to test
Added Observation object printing to `OnSettlementEventUpdater`
  • Loading branch information
sunce86 authored Jul 25, 2024
1 parent 8edb6f2 commit 4238471
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 18 deletions.
12 changes: 12 additions & 0 deletions crates/autopilot/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ impl TokenAmount {
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
pub struct SellTokenAmount(pub U256);

impl From<TokenAmount> for SellTokenAmount {
fn from(value: TokenAmount) -> Self {
Self(value.0)
}
}

impl From<SellTokenAmount> for TokenAmount {
fn from(value: SellTokenAmount) -> Self {
Self(value.0)
}
}

/// Gas amount in gas units.
///
/// The amount of Ether that is paid in transaction fees is proportional to this
Expand Down
30 changes: 26 additions & 4 deletions crates/autopilot/src/domain/settlement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ use {
};

mod auction;
mod observation;
mod solution;
mod transaction;
pub use {auction::Auction, solution::Solution, transaction::Transaction};
pub use {
auction::Auction,
observation::Observation,
solution::Solution,
transaction::Transaction,
};

/// A solution together with the `Auction` for which it was picked as a winner
/// and executed on-chain.
Expand All @@ -20,25 +26,41 @@ pub use {auction::Auction, solution::Solution, transaction::Transaction};
#[derive(Debug)]
pub struct Settlement {
solution: Solution,
transaction: Transaction,
auction: Auction,
}

impl Settlement {
pub async fn new(
tx: &Transaction,
transaction: Transaction,
domain_separator: &eth::DomainSeparator,
persistence: &infra::Persistence,
) -> Result<Self, Error> {
let solution = Solution::new(&tx.input, domain_separator)?;
let solution = Solution::new(&transaction.input, domain_separator)?;
let auction = persistence.get_auction(solution.auction_id()).await?;

Ok(Self { solution, auction })
Ok(Self {
solution,
transaction,
auction,
})
}

/// CIP38 score calculation
pub fn score(&self) -> Result<competition::Score, solution::error::Score> {
self.solution.score(&self.auction)
}

/// Returns the observation of the settlement.
pub fn observation(&self) -> Observation {
Observation {
gas: self.transaction.gas,
gas_price: self.transaction.effective_gas_price,
surplus: self.solution.native_surplus(&self.auction),
fee: self.solution.native_fee(&self.auction.prices),
order_fees: self.solution.fees(),
}
}
}

#[derive(Debug, thiserror::Error)]
Expand Down
25 changes: 25 additions & 0 deletions crates/autopilot/src/domain/settlement/observation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Aggregated type containing all important information about the mined
//! settlement, including the surplus and fees.
//!
//! Observation is a snapshot of the settlement state, which purpose is to save
//! the state of the settlement to the persistence layer.

use {
crate::domain::{self, eth},
std::collections::HashMap,
};

#[derive(Debug, Clone)]
pub struct Observation {
/// The gas used by the settlement.
pub gas: eth::Gas,
/// The effective gas price at the time of settlement.
pub gas_price: eth::EffectiveGasPrice,
/// Total surplus expressed in native token.
pub surplus: eth::Ether,
/// Total fee expressed in native token.
pub fee: eth::Ether,
/// Per order fees denominated in sell token. Contains all orders from the
/// settlement
pub order_fees: HashMap<domain::OrderUid, Option<eth::SellTokenAmount>>,
}
47 changes: 41 additions & 6 deletions crates/autopilot/src/domain/settlement/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use {
mod tokenized;
mod trade;
pub use error::Error;
use {crate::domain, std::collections::HashMap};

/// A solution that was executed on-chain.
///
Expand Down Expand Up @@ -44,20 +45,54 @@ impl Solution {
)?)
}

pub fn native_surplus(&self, auction: &super::Auction) -> Result<eth::Ether, trade::Error> {
/// Total surplus for all trades in the solution.
///
/// Always returns a value, even if some trades have incomplete surplus
/// calculation.
pub fn native_surplus(&self, auction: &super::Auction) -> eth::Ether {
self.trades
.iter()
.map(|trade| trade.native_surplus(auction))
.map(|trade| {
trade.native_surplus(auction).unwrap_or_else(|err| {
tracing::warn!(
?err,
"possible incomplete surplus calculation for trade {}",
trade.order_uid()
);
num::zero()
})
})
.sum()
}

pub fn native_fee(&self, prices: &auction::Prices) -> Result<eth::Ether, trade::Error> {
/// Total fee for all trades in the solution.
///
/// Always returns a value, even if some trades have incomplete fee
/// calculation.
pub fn native_fee(&self, prices: &auction::Prices) -> eth::Ether {
self.trades
.iter()
.map(|trade| trade.native_fee(prices))
.map(|trade| {
trade.native_fee(prices).unwrap_or_else(|err| {
tracing::warn!(
?err,
"possible incomplete fee calculation for trade {}",
trade.order_uid()
);
num::zero()
})
})
.sum()
}

/// Returns fees denominated in sell token for each order in the solution.
pub fn fees(&self) -> HashMap<domain::OrderUid, Option<eth::SellTokenAmount>> {
self.trades
.iter()
.map(|trade| (*trade.order_uid(), trade.fee().ok()))
.collect()
}

pub fn new(
calldata: &eth::Calldata,
domain_separator: &eth::DomainSeparator,
Expand Down Expand Up @@ -269,12 +304,12 @@ mod tests {

// surplus (score) read from https://api.cow.fi/mainnet/api/v1/solver_competition/by_tx_hash/0xc48dc0d43ffb43891d8c3ad7bcf05f11465518a2610869b20b0b4ccb61497634
assert_eq!(
solution.native_surplus(&auction).unwrap().0,
solution.native_surplus(&auction).0,
eth::U256::from(52937525819789126u128)
);
// fee read from "executedSurplusFee" https://api.cow.fi/mainnet/api/v1/orders/0x10dab31217bb6cc2ace0fe601c15d342f7626a1ee5ef0495449800e73156998740a50cf069e992aa4536211b23f286ef88752187ffffffff
assert_eq!(
solution.native_fee(&auction.prices).unwrap().0,
solution.native_fee(&auction.prices).0,
eth::U256::from(6890975030480504u128)
);
}
Expand Down
25 changes: 19 additions & 6 deletions crates/autopilot/src/domain/settlement/solution/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ impl Trade {
}
}

pub fn order_uid(&self) -> &domain::OrderUid {
&self.order_uid
}

/// CIP38 score defined as surplus + protocol fee
///
/// Denominated in NATIVE token
Expand Down Expand Up @@ -130,6 +134,18 @@ impl Trade {
///
/// Denominated in NATIVE token
pub fn native_fee(&self, prices: &auction::Prices) -> Result<eth::Ether, Error> {
let fee = self.fee()?;
let price = prices
.get(&self.sell.token)
.ok_or(Error::MissingPrice(self.sell.token))?;
Ok(price.in_eth(fee.into()))
}

/// Total fee (protocol fee + network fee). Equal to a surplus difference
/// before and after applying the fees.
///
/// Denominated in SELL token
pub fn fee(&self) -> Result<eth::SellTokenAmount, Error> {
let fee = self
.surplus_over_limit_price_before_fee()?
.amount
Expand All @@ -147,12 +163,9 @@ impl Trade {
.ok_or(error::Math::Overflow)?
.checked_div(&self.prices.uniform.sell.into())
.ok_or(error::Math::DivisionByZero)?,
};

let price = prices
.get(&self.sell.token)
.ok_or(Error::MissingPrice(self.sell.token))?;
Ok(price.in_eth(fee_in_sell_token))
}
.into();
Ok(fee_in_sell_token)
}

/// Protocol fees is defined by fee policies attached to the order.
Expand Down
4 changes: 2 additions & 2 deletions crates/autopilot/src/on_settlement_event_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,15 @@ impl Inner {
};
let domain_separator = self.eth.contracts().settlement_domain_separator();
let settlement = domain::settlement::Settlement::new(
&transaction,
transaction,
domain_separator,
&self.persistence,
)
.await;

tracing::info!(
"settlement object {:?}",
settlement.map(|settlement| settlement.score())
settlement.map(|settlement| (settlement.observation(), settlement.score()))
);
}

Expand Down

0 comments on commit 4238471

Please sign in to comment.