-
Notifications
You must be signed in to change notification settings - Fork 176
Add onchain gas cost to trades and orders APIs #4540
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,8 +18,34 @@ pub struct TradesQueryRow { | |
| pub sell_token: Address, | ||
| pub tx_hash: Option<TransactionHash>, | ||
| pub auction_id: Option<AuctionId>, | ||
| /// This trade's share of the settlement transaction's gas cost in native | ||
| /// token wei (`gas_used * effective_gas_price / trades_in_settlement`). | ||
| /// `NULL` for settlements observed before gas was persisted (see V111). | ||
| pub gas_cost: Option<BigDecimal>, | ||
| } | ||
|
|
||
| /// SQL expression computing a single trade's share of its settlement's on-chain | ||
| /// gas cost: the settlement's total cost (`gas_used * effective_gas_price`) | ||
| /// divided equally across all trades settled in the same transaction. Expects | ||
| /// the settlement row to be aliased `s`; selected as the column `gas_cost`, | ||
| /// which is `NULL` for settlements whose gas was not persisted (see migration | ||
| /// V111). Shared by [`trades`] and [`order_gas_costs`] so the two cannot drift. | ||
| const GAS_COST_EXPR: &str = r#"FLOOR( | ||
| (s.gas_used * s.effective_gas_price) | ||
| / NULLIF(( | ||
| SELECT COUNT(*) | ||
| FROM trades tc | ||
| WHERE tc.block_number = s.block_number | ||
| AND tc.log_index < s.log_index | ||
| AND tc.log_index > COALESCE(( | ||
| SELECT MAX(sp.log_index) | ||
| FROM settlements sp | ||
| WHERE sp.block_number = s.block_number | ||
| AND sp.log_index < s.log_index | ||
| ), -1) | ||
| ), 0) | ||
| ) AS gas_cost"#; | ||
|
|
||
| pub fn trades<'a>( | ||
| ex: &'a mut PgConnection, | ||
| owner_filter: Option<&'a Address>, | ||
|
|
@@ -37,24 +63,37 @@ SELECT | |
| t.sell_amount - t.fee_amount as sell_amount_before_fees, | ||
| o.owner, | ||
| o.buy_token, | ||
| o.sell_token, | ||
| settlement.tx_hash, | ||
| settlement.auction_id"#; | ||
|
|
||
| const SETTLEMENT_JOIN: &str = r#" | ||
| o.sell_token"#; | ||
|
|
||
| // Resolves the settlement that included each returned trade (the first | ||
| // settlement event in the same block after the trade) and computes that | ||
| // trade's share of the settlement's on-chain gas cost. Joined onto the | ||
| // already-paginated `page` CTE rather than inside the UNION branches, so the | ||
| // settlement lookup and the (relatively expensive) gas computation run only | ||
| // for the rows actually returned instead of for every candidate row across | ||
| // all three branches. | ||
| const GAS_JOIN: &str = const_format::concatcp!( | ||
| r#" | ||
| LEFT OUTER JOIN LATERAL ( | ||
| SELECT tx_hash, auction_id FROM settlements s | ||
| WHERE s.block_number = t.block_number | ||
| AND s.log_index > t.log_index | ||
| SELECT | ||
| s.tx_hash, | ||
| s.auction_id, | ||
| "#, | ||
| GAS_COST_EXPR, | ||
| r#" | ||
| FROM settlements s | ||
| WHERE s.block_number = page.block_number | ||
| AND s.log_index > page.log_index | ||
|
Comment on lines
+85
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think deciding to not store the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what it's worth. Same-block different-tx case actually works fine imo. the The case that does mis-attribute is one tx calling No solver does this afaik, but |
||
| ORDER BY s.log_index ASC | ||
| LIMIT 1 | ||
| ) AS settlement ON true"#; | ||
| ) AS settlement ON true"#, | ||
| ); | ||
|
|
||
| const QUERY: &str = const_format::concatcp!( | ||
| "WITH page AS (", | ||
| "(", | ||
| SELECT, | ||
| " FROM trades t", | ||
| SETTLEMENT_JOIN, | ||
| " JOIN orders o ON o.uid = t.order_uid", | ||
| // the uid already contains the owner address and we have | ||
| // an index on this expression so this is very efficient | ||
|
|
@@ -67,7 +106,6 @@ LEFT OUTER JOIN LATERAL ( | |
| "(", | ||
| SELECT, | ||
| " FROM trades t", | ||
| SETTLEMENT_JOIN, | ||
| " JOIN orders o ON o.uid = t.order_uid", | ||
| " JOIN onchain_placed_orders onchain_o", | ||
| " ON onchain_o.uid = t.order_uid", | ||
|
|
@@ -101,13 +139,30 @@ LEFT OUTER JOIN LATERAL ( | |
| SELECT, | ||
| " FROM jit o", | ||
| " JOIN trades t ON o.uid = t.order_uid", | ||
| SETTLEMENT_JOIN, | ||
| " ORDER BY t.block_number DESC, t.log_index DESC", | ||
| " LIMIT $3 + $4", | ||
| ")", | ||
| " ORDER BY block_number DESC, log_index DESC", | ||
| " LIMIT $3", | ||
| " OFFSET $4", | ||
| ")", | ||
| r#" | ||
| SELECT | ||
| page.block_number, | ||
| page.log_index, | ||
| page.order_uid, | ||
| page.buy_amount, | ||
| page.sell_amount, | ||
| page.sell_amount_before_fees, | ||
| page.owner, | ||
| page.buy_token, | ||
| page.sell_token, | ||
| settlement.tx_hash, | ||
| settlement.auction_id, | ||
| settlement.gas_cost | ||
| FROM page"#, | ||
| GAS_JOIN, | ||
| " ORDER BY page.block_number DESC, page.log_index DESC", | ||
| ); | ||
|
|
||
| sqlx::query_as(QUERY) | ||
|
|
@@ -119,6 +174,41 @@ LEFT OUTER JOIN LATERAL ( | |
| .instrument(info_span!("trades")) | ||
| } | ||
|
|
||
| /// Returns, per order, the total on-chain gas cost (native token wei) | ||
| /// attributed to the order across all of its trades. Each trade contributes its | ||
| /// share of its settlement's gas cost (see [`GAS_COST_EXPR`]). Orders whose | ||
| /// settlements have no persisted gas (see V111) are absent from the result. | ||
| #[instrument(skip_all)] | ||
| pub async fn order_gas_costs( | ||
| ex: &mut PgConnection, | ||
| order_uids: &[OrderUid], | ||
| ) -> Result<Vec<(OrderUid, BigDecimal)>, sqlx::Error> { | ||
|
Comment on lines
+182
to
+185
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it be covered with a small DB test to pin it down: one block, two settlements in different txs, a few trades each, then assert each trade's share and the per-order sum. |
||
| if order_uids.is_empty() { | ||
| return Ok(vec![]); | ||
| } | ||
|
|
||
| const QUERY: &str = const_format::concatcp!( | ||
| r#" | ||
| SELECT t.order_uid, FLOOR(SUM(settlement.gas_cost)) AS gas_cost | ||
| FROM trades t | ||
| JOIN LATERAL ( | ||
| SELECT "#, | ||
| GAS_COST_EXPR, | ||
| r#" | ||
| FROM settlements s | ||
| WHERE s.block_number = t.block_number | ||
| AND s.log_index > t.log_index | ||
| ORDER BY s.log_index ASC | ||
| LIMIT 1 | ||
| ) AS settlement ON true | ||
| WHERE settlement.gas_cost IS NOT NULL | ||
| AND t.order_uid = ANY($1) | ||
| GROUP BY t.order_uid"#, | ||
| ); | ||
|
|
||
| sqlx::query_as(QUERY).bind(order_uids).fetch_all(ex).await | ||
| } | ||
|
|
||
| #[derive(Clone, Debug, Default, Eq, PartialEq, sqlx::FromRow)] | ||
| pub struct TradeEvent { | ||
| pub block_number: i64, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -712,6 +712,12 @@ pub struct OrderMetadata { | |||||||||||||||||||||||||||||||
| #[serde_as(as = "HexOrDecimalU256")] | ||||||||||||||||||||||||||||||||
| pub executed_fee: U256, | ||||||||||||||||||||||||||||||||
| pub executed_fee_token: Address, | ||||||||||||||||||||||||||||||||
| /// Estimated network gas cost (in native token wei) of executing this | ||||||||||||||||||||||||||||||||
| /// order, derived from its quote (`gas_amount * gas_price`). `None` when | ||||||||||||||||||||||||||||||||
| /// the order has no stored quote. | ||||||||||||||||||||||||||||||||
|
Comment on lines
+715
to
+717
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doc comment is incorrect and contradicts both the implementation and the OpenAPI spec. The value populated into
Suggested change
|
||||||||||||||||||||||||||||||||
| #[serde_as(as = "Option<HexOrDecimalU256>")] | ||||||||||||||||||||||||||||||||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||||||||||||||||||||||||||||||||
| pub gas_cost: Option<U256>, | ||||||||||||||||||||||||||||||||
|
Comment on lines
+715
to
+720
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring for
Suggested change
|
||||||||||||||||||||||||||||||||
| pub invalidated: bool, | ||||||||||||||||||||||||||||||||
| pub status: OrderStatus, | ||||||||||||||||||||||||||||||||
| #[serde(flatten)] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,8 +3,9 @@ | |||||||||||||||||
|
|
||||||||||||||||||
| use { | ||||||||||||||||||
| crate::{fee_policy::ExecutedProtocolFee, order::OrderUid}, | ||||||||||||||||||
| alloy_primitives::{Address, B256}, | ||||||||||||||||||
| alloy_primitives::{Address, B256, U256}, | ||||||||||||||||||
| num::BigUint, | ||||||||||||||||||
| number::serialization::HexOrDecimalU256, | ||||||||||||||||||
| serde::Serialize, | ||||||||||||||||||
| serde_with::{DisplayFromStr, serde_as}, | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
@@ -30,6 +31,12 @@ pub struct Trade { | |||||||||||||||||
| // Settlement Data | ||||||||||||||||||
| pub tx_hash: Option<B256>, | ||||||||||||||||||
| pub executed_protocol_fees: Vec<ExecutedProtocolFee>, | ||||||||||||||||||
| /// Estimated network gas cost (in native token wei) of executing this | ||||||||||||||||||
| /// trade, derived from the order's quote (`gas_amount * gas_price`). | ||||||||||||||||||
| /// `None` when the order has no stored quote (e.g. JIT orders). | ||||||||||||||||||
|
Comment on lines
+34
to
+36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same issue as the
Suggested change
|
||||||||||||||||||
| #[serde_as(as = "Option<HexOrDecimalU256>")] | ||||||||||||||||||
| #[serde(default, skip_serializing_if = "Option::is_none")] | ||||||||||||||||||
| pub gas_cost: Option<U256>, | ||||||||||||||||||
|
Comment on lines
+34
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring for /// Actual on-chain gas cost (in native token wei) attributed to this
/// trade. Computed as this trade's share of the settlement transaction's
/// gas cost (gas_used * effective_gas_price), split equally across all
/// trades settled in the same transaction. None for trades settled
/// before this data started being recorded.
#[serde_as(as = "Option<HexOrDecimalU256>")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gas_cost: Option<U256>, |
||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[cfg(test)] | ||||||||||||||||||
|
|
@@ -56,6 +63,7 @@ mod tests { | |||||||||||||||||
| "sellToken": "0x000000000000000000000000000000000000000a", | ||||||||||||||||||
| "buyToken": "0x0000000000000000000000000000000000000009", | ||||||||||||||||||
| "txHash": "0x0000000000000000000000000000000000000000000000000000000000000040", | ||||||||||||||||||
| "gasCost": "3000000", | ||||||||||||||||||
| "executedProtocolFees": [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "amount": "5", | ||||||||||||||||||
|
|
@@ -104,6 +112,7 @@ mod tests { | |||||||||||||||||
| buy_token: Address::with_last_byte(9), | ||||||||||||||||||
| sell_token: Address::with_last_byte(10), | ||||||||||||||||||
| tx_hash: Some(B256::with_last_byte(64)), | ||||||||||||||||||
| gas_cost: Some(U256::from(3_000_000u64)), | ||||||||||||||||||
| executed_protocol_fees: vec![ | ||||||||||||||||||
| ExecutedProtocolFee { | ||||||||||||||||||
| amount: U256::from(5u64), | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1336,6 +1336,17 @@ components: | |
| allOf: | ||
| - $ref: "#/components/schemas/Address" | ||
| nullable: false | ||
| gasCost: | ||
| description: > | ||
| Actual on-chain gas cost attributed to this order, in native token | ||
| wei. Computed as the order's share of the gas cost | ||
|
Comment on lines
+1341
to
+1342
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This exposes a ton of implementation details the user doesn't care about, no?
Same applies to the other openapi field |
||
| (`gas_used * effective_gas_price`) of the settlement transaction(s) | ||
| that executed it, split equally across all trades settled in the same | ||
| transaction, and summed across the order's fills. `null` for orders | ||
| settled before this data started being recorded, or not yet settled. | ||
| allOf: | ||
| - $ref: "#/components/schemas/BigUint" | ||
| nullable: true | ||
| fullAppData: | ||
| description: > | ||
| Full `appData`, which the contract-level `appData` is a hash of. See | ||
|
|
@@ -1706,6 +1717,16 @@ components: | |
| type: array | ||
| items: | ||
| $ref: "#/components/schemas/ExecutedProtocolFee" | ||
| gasCost: | ||
| description: > | ||
| Actual on-chain gas cost attributed to this trade, in native token | ||
| wei. Computed as this trade's share of the settlement transaction's | ||
| gas cost (`gas_used * effective_gas_price`), split equally across all | ||
| trades settled in the same transaction. `null` for trades settled | ||
| before this data started being recorded. | ||
| allOf: | ||
| - $ref: "#/components/schemas/BigUint" | ||
| nullable: true | ||
| required: | ||
| - blockNumber | ||
| - logIndex | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be wildly inaccurate because a super complicated order can be batched together with a simple order. I don't think there is a way to make this more accurate with reasonable effort - just wanted to flag this in case you weren't aware.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I knew all this could be innacurate, the idea is to give an estimate (slightly better than "solver reported" as they can simply write a different value)