Skip to content

Commit

Permalink
Use external prices for conversion of fees in autopilot (#2863)
Browse files Browse the repository at this point in the history
# Description
Related to discussion in the issue:
#2862 (comment)

Use external price vector to convert "fee in surplus token" into "fee in
sell token" (instead of uniform clearing prices).

This is important so that we make sure that it's always true:

`total_fee = executed_surplus_fee + protocol fees`

If we use uniform clearing prices for `executed_surplus_fee` and
external prices for `protocol fees` I think above equation won't stand.

## How to test
Existing unit test
  • Loading branch information
sunce86 authored Aug 7, 2024
1 parent e95ce8b commit 01b9761
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 23 deletions.
2 changes: 1 addition & 1 deletion crates/autopilot/src/domain/settlement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl Settlement {
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(),
order_fees: self.solution.fees(&self.auction.prices),
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions crates/autopilot/src/domain/settlement/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,13 @@ impl Solution {
}

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

Expand Down Expand Up @@ -307,9 +310,15 @@ mod tests {
eth::U256::from(52937525819789126u128)
);
// fee read from "executedSurplusFee" https://api.cow.fi/mainnet/api/v1/orders/0x10dab31217bb6cc2ace0fe601c15d342f7626a1ee5ef0495449800e73156998740a50cf069e992aa4536211b23f286ef88752187ffffffff
// "executedSurplusFee" and native fee are equal because the sell token is ETH
assert_eq!(
solution.native_fee(&auction.prices).0,
eth::U256::from(6890975030480504u128)
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))))])
);
}
}
56 changes: 37 additions & 19 deletions crates/autopilot/src/domain/settlement/solution/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,36 +136,54 @@ impl Trade {
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()))
.get(&fee.token)
.ok_or(Error::MissingPrice(fee.token))?;
Ok(price.in_eth(fee.amount))
}

/// 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> {
pub fn fee_in_sell_token(
&self,
prices: &auction::Prices,
) -> Result<eth::SellTokenAmount, Error> {
let fee = self.fee()?;
let fee_in_sell_token = match self.side {
order::Side::Buy => fee.amount,
order::Side::Sell => {
let buy_price = prices
.get(&self.buy.token)
.ok_or(Error::MissingPrice(self.buy.token))?;
let sell_price = prices
.get(&self.sell.token)
.ok_or(Error::MissingPrice(self.sell.token))?;
fee.amount
.checked_mul(&buy_price.get().0.into())
.ok_or(error::Math::Overflow)?
.checked_div(&sell_price.get().0.into())
.ok_or(error::Math::DivisionByZero)?
}
}
.into();
Ok(fee_in_sell_token)
}

/// Total fee (protocol fee + network fee). Equal to a surplus difference
/// before and after applying the fees.
///
/// Denominated in SURPLUS token
fn fee(&self) -> Result<eth::Asset, Error> {
let fee = self
.surplus_over_limit_price_before_fee()?
.amount
.checked_sub(&self.surplus_over_limit_price()?.amount)
.ok_or(error::Math::Negative)?;
// We don't have to convert the fee to the sell token, we can just use the fee
// in surplus token. This is done just because it was done like this in
// the previous implementation
//
// https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/decoded_settlement.rs#L345
let fee_in_sell_token = match self.side {
order::Side::Buy => fee,
order::Side::Sell => fee
.checked_mul(&self.prices.uniform.buy.into())
.ok_or(error::Math::Overflow)?
.checked_div(&self.prices.uniform.sell.into())
.ok_or(error::Math::DivisionByZero)?,
}
.into();
Ok(fee_in_sell_token)
Ok(eth::Asset {
token: self.surplus_token(),
amount: fee,
})
}

/// Protocol fees is defined by fee policies attached to the order.
Expand Down

0 comments on commit 01b9761

Please sign in to comment.