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

Protocol fees breakdown #2879

Merged
merged 8 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
8 changes: 4 additions & 4 deletions crates/autopilot/src/domain/settlement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! A winning solution becomes a [`Settlement`] once it is executed on-chain.

use {
self::solution::OrderFee,
crate::{domain, domain::eth, infra},
std::collections::HashMap,
};
Expand Down Expand Up @@ -102,10 +103,9 @@ impl Settlement {
self.solution.native_fee(&self.auction.prices)
}

/// Per order fees denominated in sell token. Contains all orders from the
/// settlement
pub fn order_fees(&self) -> HashMap<domain::OrderUid, Option<eth::SellTokenAmount>> {
self.solution.fees(&self.auction.prices)
/// Per order fees breakdown. Contains all orders from the settlement
pub fn order_fees(&self) -> HashMap<domain::OrderUid, Option<OrderFee>> {
self.solution.fees(&self.auction)
}
}

Expand Down
35 changes: 25 additions & 10 deletions crates/autopilot/src/domain/settlement/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,20 @@ impl Solution {
.sum()
}

/// Returns fees denominated in sell token for each order in the solution.
pub fn fees(
&self,
prices: &auction::Prices,
) -> HashMap<domain::OrderUid, Option<eth::SellTokenAmount>> {
/// Returns fees breakdown for each order in the solution.
pub fn fees(&self, auction: &super::Auction) -> HashMap<domain::OrderUid, Option<OrderFee>> {
self.trades
.iter()
.map(|trade| (*trade.order_uid(), trade.fee_in_sell_token(prices).ok()))
.map(|trade| {
(*trade.order_uid(), {
let total = trade.fee_in_sell_token(&auction.prices);
let protocol = trade.protocol_fees(auction);
match (total, protocol) {
(Ok(total), Ok(protocol)) => Some(OrderFee { total, protocol }),
_ => None,
}
})
})
.collect()
}

Expand Down Expand Up @@ -194,6 +200,16 @@ pub mod error {
}
}

/// Fee per trade in a solution. Contains breakdown of protocol fees and total
/// fee.
#[derive(Debug, Clone)]
pub struct OrderFee {
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
/// Gas fee + protocol fees. Total fee taken for the execution of the order.
pub total: eth::SellTokenAmount,
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
/// Breakdown of protocol fees.
pub protocol: Vec<eth::Asset>,
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
}

#[cfg(test)]
mod tests {
use {
Expand Down Expand Up @@ -325,10 +341,9 @@ mod tests {
eth::U256::from(6752697350740628u128)
);
// fee read from "executedSurplusFee" https://api.cow.fi/mainnet/api/v1/orders/0x10dab31217bb6cc2ace0fe601c15d342f7626a1ee5ef0495449800e73156998740a50cf069e992aa4536211b23f286ef88752187ffffffff
assert_eq!(
solution.fees(&auction.prices),
HashMap::from([(domain::OrderUid(hex!("10dab31217bb6cc2ace0fe601c15d342f7626a1ee5ef0495449800e73156998740a50cf069e992aa4536211b23f286ef88752187ffffffff")), Some(eth::SellTokenAmount(eth::U256::from(6752697350740628u128))))])
);
let order_fees = solution.fees(&auction);
let order_fee = order_fees.get(&domain::OrderUid(hex!("10dab31217bb6cc2ace0fe601c15d342f7626a1ee5ef0495449800e73156998740a50cf069e992aa4536211b23f286ef88752187ffffffff"))).unwrap().clone().unwrap();
assert_eq!(order_fee.total.0, eth::U256::from(6752697350740628u128));
}

// https://etherscan.io/tx/0x688508eb59bd20dc8c0d7c0c0b01200865822c889f0fcef10113e28202783243
Expand Down
69 changes: 36 additions & 33 deletions crates/autopilot/src/domain/settlement/solution/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,26 +191,30 @@ impl Trade {
})
}

/// Protocol fees is defined by fee policies attached to the order.
/// Protocol fees are defined by fee policies attached to the order.
///
/// Denominated in SURPLUS token
fn protocol_fees(&self, policies: &[fee::Policy]) -> Result<eth::Asset, Error> {
pub fn protocol_fees(&self, auction: &settlement::Auction) -> Result<Vec<eth::Asset>, Error> {
let policies = auction
.orders
.get(&self.order_uid)
.map(|value| value.as_slice())
.unwrap_or_default();
let mut current_trade = self.clone();
let mut amount = eth::TokenAmount::default();
let mut total = eth::TokenAmount::default();
let mut fees = vec![];
for (i, protocol_fee) in policies.iter().enumerate().rev() {
let fee = current_trade.protocol_fee(protocol_fee)?;
// Do not need to calculate the last custom prices because in the last iteration
// the prices are not used anymore to calculate the protocol fee
amount += fee;
fees.push(fee);
total += fee.amount;
if !i.is_zero() {
current_trade.prices.custom = self.calculate_custom_prices(amount)?;
current_trade.prices.custom = self.calculate_custom_prices(total)?;
}
}

Ok(eth::Asset {
token: self.surplus_token(),
amount,
})
// Reverse the fees to have them in the same order as the policies
Ok(fees.into_iter().rev().collect())
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
}

/// The effective amount that left the user's wallet including all fees.
Expand Down Expand Up @@ -280,34 +284,36 @@ impl Trade {
/// Protocol fee is defined by a fee policy attached to the order.
///
/// Denominated in SURPLUS token
fn protocol_fee(&self, fee_policy: &fee::Policy) -> Result<eth::TokenAmount, Error> {
match fee_policy {
fn protocol_fee(&self, fee_policy: &fee::Policy) -> Result<eth::Asset, Error> {
let amount = match fee_policy {
fee::Policy::Surplus {
factor,
max_volume_factor,
} => {
let surplus = self.surplus_over_limit_price()?;
let fee = std::cmp::min(
std::cmp::min(
self.surplus_fee(surplus, (*factor).into())?.amount,
self.volume_fee((*max_volume_factor).into())?.amount,
);
Ok::<eth::TokenAmount, Error>(fee)
)
}
fee::Policy::PriceImprovement {
factor,
max_volume_factor,
quote,
} => {
let price_improvement = self.price_improvement(quote)?;
let fee = std::cmp::min(
std::cmp::min(
self.surplus_fee(price_improvement, (*factor).into())?
.amount,
self.volume_fee((*max_volume_factor).into())?.amount,
);
Ok(fee)
)
}
fee::Policy::Volume { factor } => Ok(self.volume_fee((*factor).into())?.amount),
}
fee::Policy::Volume { factor } => self.volume_fee((*factor).into())?.amount,
};
Ok(eth::Asset {
token: self.surplus_token(),
amount,
})
}

fn price_improvement(&self, quote: &domain::fee::Quote) -> Result<eth::Asset, Error> {
Expand Down Expand Up @@ -455,19 +461,16 @@ impl Trade {
///
/// Denominated in NATIVE token
fn native_protocol_fee(&self, auction: &settlement::Auction) -> Result<eth::Ether, Error> {
let protocol_fee = self.protocol_fees(
auction
.orders
.get(&self.order_uid)
.map(|value| value.as_slice())
.unwrap_or_default(),
)?;
let price = auction
.prices
.get(&protocol_fee.token)
.ok_or(Error::MissingPrice(protocol_fee.token))?;

Ok(price.in_eth(protocol_fee.amount))
self.protocol_fees(auction)?
.into_iter()
.map(|fee| {
let price = auction
.prices
.get(&fee.token)
.ok_or(Error::MissingPrice(fee.token))?;
Ok(price.in_eth(fee.amount))
})
.sum()
}

fn surplus_token(&self) -> eth::TokenAddress {
Expand Down
4 changes: 3 additions & 1 deletion crates/autopilot/src/on_settlement_event_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ impl Inner {
"automatic check error: order_fees missing"
);
} else {
let settlement_fee = order_fees[&domain::OrderUid(fee.0 .0)];
let settlement_fee = order_fees[&domain::OrderUid(fee.0 .0)]
.clone()
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
.map(|fee| fee.total);
if settlement_fee.unwrap_or_default().0 != fee.1 {
tracing::warn!(
?auction_id,
Expand Down
60 changes: 32 additions & 28 deletions crates/driver/src/domain/competition/solution/scoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use {
},
PriceLimits,
},
eth::{self, TokenAmount},
eth::{self},
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
},
util::conv::u256::U256Ext,
},
Expand Down Expand Up @@ -169,26 +169,25 @@ impl Trade {
Ok(price.in_eth(surplus.amount))
}

/// Protocol fees is defined by fee policies attached to the order.
/// Protocol fees are defined by fee policies attached to the order.
///
/// Denominated in SURPLUS token
fn protocol_fees(&self) -> Result<eth::Asset, Error> {
fn protocol_fees(&self) -> Result<Vec<eth::Asset>, Error> {
let mut current_trade = self.clone();
let mut amount = TokenAmount::default();
let mut total = eth::TokenAmount::default();
let mut fees = vec![];
for (i, protocol_fee) in self.policies.iter().enumerate().rev() {
let fee = current_trade.protocol_fee(protocol_fee)?;
// Do not need to calculate the last custom prices because in the last iteration
// the prices are not used anymore to calculate the protocol fee
amount += fee;
fees.push(fee);
total += fee.amount;
if !i.is_zero() {
current_trade.custom_price = self.calculate_custom_prices(amount)?;
current_trade.custom_price = self.calculate_custom_prices(total)?;
}
}

Ok(eth::Asset {
token: self.surplus_token(),
amount,
})
// Reverse the fees to have them in the same order as the policies
Ok(fees.into_iter().rev().collect())
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
}

/// The effective amount that left the user's wallet including all fees.
Expand Down Expand Up @@ -233,7 +232,7 @@ impl Trade {
/// Note how the custom prices are expressed over actual traded amounts.
pub fn calculate_custom_prices(
&self,
protocol_fee: TokenAmount,
protocol_fee: eth::TokenAmount,
) -> Result<CustomClearingPrices, error::Math> {
Ok(CustomClearingPrices {
sell: match self.side {
Expand All @@ -258,33 +257,35 @@ impl Trade {
/// Protocol fee is defined by a fee policy attached to the order.
///
/// Denominated in SURPLUS token
fn protocol_fee(&self, fee_policy: &FeePolicy) -> Result<TokenAmount, Error> {
match fee_policy {
fn protocol_fee(&self, fee_policy: &FeePolicy) -> Result<eth::Asset, Error> {
let amount = match fee_policy {
FeePolicy::Surplus {
factor,
max_volume_factor,
} => {
let surplus = self.surplus_over_limit_price()?;
let fee = std::cmp::min(
std::cmp::min(
self.surplus_fee(surplus, *factor)?.amount,
self.volume_fee(*max_volume_factor)?.amount,
);
Ok::<TokenAmount, Error>(fee)
)
}
FeePolicy::PriceImprovement {
factor,
max_volume_factor,
quote,
} => {
let price_improvement = self.price_improvement(quote)?;
let fee = std::cmp::min(
std::cmp::min(
self.surplus_fee(price_improvement, *factor)?.amount,
self.volume_fee(*max_volume_factor)?.amount,
);
Ok(fee)
)
}
FeePolicy::Volume { factor } => Ok(self.volume_fee(*factor)?.amount),
}
FeePolicy::Volume { factor } => self.volume_fee(*factor)?.amount,
};
Ok(eth::Asset {
token: self.surplus_token(),
amount,
})
}

fn price_improvement(&self, quote: &Quote) -> Result<eth::Asset, Error> {
Expand Down Expand Up @@ -416,12 +417,15 @@ impl Trade {
///
/// Denominated in NATIVE token
fn native_protocol_fee(&self, prices: &auction::Prices) -> Result<eth::Ether, Error> {
let protocol_fee = self.protocol_fees()?;
let price = prices
.get(&protocol_fee.token)
.ok_or(Error::MissingPrice(protocol_fee.token))?;

Ok(price.in_eth(protocol_fee.amount))
self.protocol_fees()?
.into_iter()
.map(|fee| {
let price = prices
.get(&fee.token)
.ok_or(Error::MissingPrice(fee.token))?;
Ok(price.in_eth(fee.amount))
})
.sum()
}

fn surplus_token(&self) -> eth::TokenAddress {
Expand Down
Loading