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

Breakdown fees protocol 3/3: DB field and returning on /get_trades #2910

Merged
merged 32 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
03d92da
Initial commit
sunce86 Aug 15, 2024
d1b74ad
Add executed protocol fees to get_trades
sunce86 Aug 15, 2024
12c2cfd
self cr
sunce86 Aug 15, 2024
c3c059b
unit tests
sunce86 Aug 15, 2024
4734065
update db docs
sunce86 Aug 19, 2024
5062b98
update openapi
sunce86 Aug 19, 2024
dac52c7
optional executed fees
sunce86 Aug 19, 2024
92b8929
update tests
sunce86 Aug 19, 2024
917522c
revert optionallity
sunce86 Aug 19, 2024
bee9fda
Merge branch 'main' into breakdown-fees-protocol
sunce86 Sep 3, 2024
15385d0
merge conflict
sunce86 Sep 3, 2024
7e22acb
refactor scheme
sunce86 Sep 3, 2024
6d25a2e
small refactor
sunce86 Sep 3, 2024
b6027ed
remove option
sunce86 Sep 3, 2024
c2e15d6
save protocol fee
sunce86 Sep 3, 2024
2508c1f
fix tests
sunce86 Sep 3, 2024
6e91f19
revert ignore
sunce86 Sep 3, 2024
f2853c6
Merge branch 'main' into breakdown-fees-protocol
sunce86 Sep 4, 2024
f8ae556
protocol fees are in surplus token
sunce86 Sep 4, 2024
0910437
refactor
sunce86 Sep 4, 2024
f3f2f8b
ExecutedProtocolFee object
sunce86 Sep 4, 2024
2dc555d
docs update
sunce86 Sep 4, 2024
6dcb6ae
add comment for ordering
sunce86 Sep 4, 2024
ab183c9
fix e2e test
sunce86 Sep 4, 2024
fa6fe2a
fmt
sunce86 Sep 4, 2024
0baf80c
small fixes
sunce86 Sep 4, 2024
adaf2e0
martin's review comments
sunce86 Sep 5, 2024
43d809f
cr fixes
sunce86 Sep 6, 2024
1dc1960
remove comment
sunce86 Sep 6, 2024
24cb713
Merge branch 'main' into breakdown-fees-protocol
sunce86 Sep 6, 2024
1236347
fix conflict
sunce86 Sep 6, 2024
a2a2c6b
fix conflicts
sunce86 Sep 6, 2024
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
17 changes: 2 additions & 15 deletions crates/autopilot/src/domain/settlement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

use {
crate::{domain, domain::eth, infra},
num::Saturating,
std::collections::HashMap,
};

Expand Down Expand Up @@ -85,22 +84,10 @@ impl Settlement {
}

/// Per order fees breakdown. Contains all orders from the settlement
pub fn order_fees(&self) -> HashMap<domain::OrderUid, Option<trade::ExecutedFee>> {
pub fn fee_breakdown(&self) -> HashMap<domain::OrderUid, Option<trade::FeeBreakdown>> {
self.trades
.iter()
.map(|trade| {
let total = trade.fee_in_sell_token();
let protocol = trade.protocol_fees_in_sell_token(&self.auction);
let fee = match (total, protocol) {
(Ok(total), Ok(protocol)) => {
let network =
total.saturating_sub(protocol.iter().map(|(fee, _)| *fee).sum());
Some(trade::ExecutedFee { protocol, network })
}
_ => None,
};
(*trade.uid(), fee)
})
.map(|trade| (*trade.uid(), trade.fee_breakdown(&self.auction).ok()))
.collect()
}

Expand Down
27 changes: 10 additions & 17 deletions crates/autopilot/src/domain/settlement/trade/math.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use error::Error;
use {
super::ExecutedProtocolFee,
crate::{
domain::{
self,
Expand Down Expand Up @@ -154,24 +155,13 @@ impl Trade {
})
}

/// Protocol fees are defined by fee policies attached to the order.
pub fn protocol_fees_in_sell_token(
&self,
auction: &settlement::Auction,
) -> Result<Vec<(eth::SellTokenAmount, fee::Policy)>, Error> {
self.protocol_fees(auction)?
.into_iter()
.map(|(fee, policy)| Ok((self.fee_into_sell_token(fee.amount)?, policy)))
.collect()
}

/// Protocol fees are defined by fee policies attached to the order.
///
/// Denominated in SURPLUS token
fn protocol_fees(
pub fn protocol_fees(
&self,
auction: &settlement::Auction,
) -> Result<Vec<(eth::Asset, fee::Policy)>, Error> {
) -> Result<Vec<ExecutedProtocolFee>, Error> {
let policies = auction
.orders
.get(&self.uid)
Expand All @@ -180,11 +170,14 @@ impl Trade {
let mut current_trade = self.clone();
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)?;
for (i, policy) in policies.iter().enumerate().rev() {
let fee = current_trade.protocol_fee(policy)?;
// 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
fees.push((fee, *protocol_fee));
fees.push(ExecutedProtocolFee {
policy: *policy,
fee,
});
total += fee.amount;
if !i.is_zero() {
current_trade.prices.custom = self.calculate_custom_prices(total)?;
Expand Down Expand Up @@ -426,7 +419,7 @@ impl Trade {
fn protocol_fee_in_ether(&self, auction: &settlement::Auction) -> Result<eth::Ether, Error> {
self.protocol_fees(auction)?
.into_iter()
.map(|(fee, _)| {
.map(|ExecutedProtocolFee { fee, policy: _ }| {
let price = auction
.prices
.get(&fee.token)
Expand Down
38 changes: 17 additions & 21 deletions crates/autopilot/src/domain/settlement/trade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,12 @@ impl Trade {
math::Trade::from(self).fee_in_ether(prices)
}

/// Total fee (protocol fee + network fee). Equal to a surplus difference
/// before and after applying the fees.
pub fn fee_in_sell_token(&self) -> Result<eth::SellTokenAmount, math::Error> {
math::Trade::from(self).fee_in_sell_token()
}

/// Protocol fees are defined by fee policies attached to the order.
pub fn protocol_fees_in_sell_token(
&self,
auction: &super::Auction,
) -> Result<Vec<(eth::SellTokenAmount, fee::Policy)>, math::Error> {
math::Trade::from(self).protocol_fees_in_sell_token(auction)
/// All fees broke down into protocol fees per policy and total fee.
pub fn fee_breakdown(&self, auction: &super::Auction) -> Result<FeeBreakdown, math::Error> {
let trade = math::Trade::from(self);
let total = trade.fee_in_sell_token()?;
let protocol = trade.protocol_fees(auction)?;
Ok(FeeBreakdown { total, protocol })
}

pub fn new(trade: transaction::EncodedTrade, auction: &super::Auction, created: u32) -> Self {
Expand Down Expand Up @@ -154,17 +148,19 @@ pub struct Jit {
/// Fee per trade in a solution. These fees are taken for the execution of the
/// trade.
#[derive(Debug, Clone)]
pub struct ExecutedFee {
/// Gas fee spent to bring the order onchain
pub network: eth::SellTokenAmount,
pub struct FeeBreakdown {
/// Total fee spent to bring the order onchain (network fee + protocol fee)
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
// TODO: express in surplus token
pub total: eth::SellTokenAmount,
/// Breakdown of protocol fees. Executed protocol fees are in the same order
/// as policies are defined for an order.
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
pub protocol: Vec<(eth::SellTokenAmount, fee::Policy)>,
pub protocol: Vec<ExecutedProtocolFee>,
Copy link
Contributor Author

@sunce86 sunce86 Sep 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to fallback to <total, protocol> since now the values are in different tokens.

willing to revert once the "network" is switched to surplus token as well, since this is a final goal and agreement with solver and frontend team. Doing it in this PR increases significantly the scope.

}

impl ExecutedFee {
/// Total fee paid for the trade.
pub fn total(&self) -> eth::SellTokenAmount {
self.network + self.protocol.iter().map(|(fee, _)| *fee).sum()
}
#[derive(Debug, Clone)]
pub struct ExecutedProtocolFee {
/// Policy that was used to calculate the fee.
pub policy: fee::Policy,
/// Fee that was taken for the trade, in surplus token.
pub fee: eth::Asset,
}
29 changes: 22 additions & 7 deletions crates/autopilot/src/infra/persistence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use {
chrono::Utc,
database::{
order_events::OrderEventLabel,
order_execution::FeeAsset,
orders::{
BuyTokenDestination as DbBuyTokenDestination,
SellTokenSource as DbSellTokenSource,
Expand Down Expand Up @@ -451,7 +452,7 @@ impl Persistence {
let gas_price = settlement.gas_price();
let surplus = settlement.surplus_in_ether();
let fee = settlement.fee_in_ether();
let order_fees = settlement.order_fees();
let fee_breakdown = settlement.fee_breakdown();
let jit_orders = settlement.jit_orders();

tracing::debug!(
Expand All @@ -461,7 +462,7 @@ impl Persistence {
?gas_price,
?surplus,
?fee,
?order_fees,
?fee_breakdown,
?jit_orders,
"settlement update",
);
Expand All @@ -481,21 +482,35 @@ impl Persistence {

store_order_events(
&mut ex,
order_fees.keys().cloned().collect(),
fee_breakdown.keys().cloned().collect(),
OrderEventLabel::Traded,
Utc::now(),
)
.await;

for (order, executed_fee) in order_fees {
for (order, order_fee) in fee_breakdown {
let executed_fee = order_fee
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
.as_ref()
.map(|fee| u256_to_big_decimal(&fee.total.0))
.unwrap_or_default();
let executed_protocol_fees = order_fee
.map(|fee| {
fee.protocol
.into_iter()
.map(|executed| FeeAsset {
token: ByteArray(executed.fee.token.0 .0),
amount: u256_to_big_decimal(&executed.fee.amount.0),
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
database::order_execution::save(
&mut ex,
&ByteArray(order.0),
auction_id,
block_number,
&u256_to_big_decimal(
&executed_fee.map(|fee| fee.total()).unwrap_or_default().0,
),
&executed_fee,
&executed_protocol_fees,
)
.await?;
}
Expand Down
112 changes: 106 additions & 6 deletions crates/database/src/order_execution.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,99 @@
use {
crate::{auction::AuctionId, OrderUid},
crate::{auction::AuctionId, Address, OrderUid},
bigdecimal::BigDecimal,
sqlx::PgConnection,
sqlx::{PgConnection, QueryBuilder},
std::collections::HashMap,
};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FeeAsset {
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
pub amount: BigDecimal,
pub token: Address,
}

pub async fn save(
ex: &mut PgConnection,
order: &OrderUid,
auction: AuctionId,
block_number: i64,
executed_fee: &BigDecimal,
executed_protocol_fees: &[FeeAsset],
) -> Result<(), sqlx::Error> {
let (protocol_fee_tokens, protocol_fee_amounts): (Vec<_>, Vec<_>) = executed_protocol_fees
.iter()
.map(|entry| (entry.token, entry.amount.clone()))
.unzip();

const QUERY: &str = r#"
INSERT INTO order_execution (order_uid, auction_id, reward, surplus_fee, block_number)
VALUES ($1, $2, $3, $4, $5)
INSERT INTO order_execution (order_uid, auction_id, reward, surplus_fee, block_number, protocol_fee_tokens, protocol_fee_amounts)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (order_uid, auction_id)
DO UPDATE SET reward = $3, surplus_fee = $4, block_number = $5
DO UPDATE SET reward = $3, surplus_fee = $4, block_number = $5, protocol_fee_tokens = $6, protocol_fee_amounts = $7
;"#;
sqlx::query(QUERY)
.bind(order)
.bind(auction)
.bind(0.) // reward is deprecated but saved for historical analysis
.bind(Some(executed_fee))
.bind(block_number)
.bind(protocol_fee_tokens)
.bind(protocol_fee_amounts)
.execute(ex)
.await?;
Ok(())
}

/// Fetch protocol fees for all keys in the filter
pub async fn executed_protocol_fees(
ex: &mut PgConnection,
keys_filter: &[(AuctionId, OrderUid)],
sunce86 marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<HashMap<(AuctionId, OrderUid), Vec<FeeAsset>>, sqlx::Error> {
if keys_filter.is_empty() {
return Ok(HashMap::new());
}

let mut query_builder = QueryBuilder::new(
"SELECT order_uid, auction_id, protocol_fee_tokens, protocol_fee_amounts FROM \
order_execution WHERE ",
);

for (i, (auction_id, order_uid)) in keys_filter.iter().enumerate() {
if i > 0 {
query_builder.push(" OR ");
}
query_builder
.push("(order_uid = ")
.push_bind(order_uid)
.push(" AND auction_id = ")
.push_bind(auction_id)
.push(")");
}

#[derive(Clone, Debug, Eq, PartialEq, sqlx::Type, sqlx::FromRow)]
struct ProtocolFees {
pub order_uid: OrderUid,
pub auction_id: AuctionId,
pub protocol_fee_tokens: Vec<Address>,
pub protocol_fee_amounts: Vec<BigDecimal>,
Comment on lines +78 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it guarantee in the query that we are retrieving the token and its corresponding amount in the same order? it could be dangerous in case the first token doesn't correspond with the first amount, etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caller is responsible for storing the values in proper order.

}
let query = query_builder.build_query_as::<ProtocolFees>();
let rows: Vec<ProtocolFees> = query.fetch_all(ex).await?;

let mut fees = HashMap::new();
for row in rows {
fees.insert(
(row.auction_id, row.order_uid),
row.protocol_fee_tokens
.into_iter()
.zip(row.protocol_fee_amounts)
.map(|(token, amount)| FeeAsset { token, amount })
.collect(),
);
}

Ok(fees)
}

#[cfg(test)]
mod tests {
use {super::*, sqlx::Connection};
Expand All @@ -39,8 +105,42 @@ mod tests {
let mut db = db.begin().await.unwrap();
crate::clear_DANGER_(&mut db).await.unwrap();

save(&mut db, &Default::default(), 1, 0, &Default::default())
// save entry with protocol fees
let protocol_fees = vec![
FeeAsset {
amount: BigDecimal::from(1),
token: Default::default(),
},
FeeAsset {
amount: BigDecimal::from(2),
token: Default::default(),
},
];
save(
&mut db,
&Default::default(),
1,
0,
&Default::default(),
protocol_fees.as_slice(),
)
.await
.unwrap();

// save entry without protocol fees (simulate case when we are still not
// calculating them)
save(&mut db, &Default::default(), 2, 0, &Default::default(), &[])
.await
.unwrap();

let keys: Vec<(AuctionId, OrderUid)> = vec![
(1, Default::default()),
(2, Default::default()),
(3, Default::default()),
];

let read_protocol_fees = executed_protocol_fees(&mut db, &keys).await.unwrap();
assert_eq!(read_protocol_fees.len(), 2);
assert_eq!(read_protocol_fees[&(1, Default::default())], protocol_fees);
}
}
2 changes: 1 addition & 1 deletion crates/database/src/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1854,7 +1854,7 @@ mod tests {
assert_eq!(order.executed_surplus_fee, 0.into());

let fee: BigDecimal = 1.into();
crate::order_execution::save(&mut db, &order_uid, 1, 0, &fee)
crate::order_execution::save(&mut db, &order_uid, 1, 0, &fee, &[])
.await
.unwrap();

Expand Down
17 changes: 14 additions & 3 deletions crates/e2e/tests/e2e/limit_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,11 +819,22 @@ async fn no_liquidity_limit_order(web3: Web3) {
assert!(balance_after.checked_sub(balance_before).unwrap() >= to_wei(5));

let trades = services.get_trades(&order_id).await.unwrap();
let executed_protocol_fee = trades
.first()
.unwrap()
.executed_protocol_fees
.first()
.unwrap();
assert_eq!(
trades.first().unwrap().fee_policies,
vec![model::fee_policy::FeePolicy::Surplus {
executed_protocol_fee.policy,
model::fee_policy::FeePolicy::Surplus {
factor: 0.5,
max_volume_factor: 0.01
}],
}
);
assert_eq!(
executed_protocol_fee.token,
onchain.contracts().weth.address()
);
assert!(executed_protocol_fee.amount > U256::zero());
}
Loading
Loading