diff --git a/crates/database/src/fee_policies.rs b/crates/database/src/fee_policies.rs index 747df38b38..c737c88bf4 100644 --- a/crates/database/src/fee_policies.rs +++ b/crates/database/src/fee_policies.rs @@ -1,6 +1,7 @@ use { crate::{auction::AuctionId, OrderUid}, sqlx::{PgConnection, QueryBuilder}, + std::collections::HashMap, }; #[derive(Debug, Clone, PartialEq, sqlx::FromRow)] @@ -52,24 +53,40 @@ pub async fn insert_batch( query_builder.build().execute(ex).await.map(|_| ()) } -pub async fn fetch( +pub async fn fetch_all( ex: &mut PgConnection, - auction_id: AuctionId, - order_uid: OrderUid, -) -> Result, sqlx::Error> { - const QUERY: &str = r#" - SELECT * FROM fee_policies - WHERE auction_id = $1 AND order_uid = $2 - ORDER BY application_order -"#; - let rows = sqlx::query_as::<_, FeePolicy>(QUERY) - .bind(auction_id) - .bind(order_uid) - .fetch_all(ex) - .await? - .into_iter() - .collect(); - Ok(rows) + keys_filter: &[(AuctionId, OrderUid)], +) -> Result>, sqlx::Error> { + if keys_filter.is_empty() { + return Ok(HashMap::new()); + } + + let mut query_builder = QueryBuilder::new("SELECT * FROM fee_policies WHERE "); + for (i, (auction_id, order_uid)) in keys_filter.iter().enumerate() { + if i > 0 { + query_builder.push(" OR "); + } + query_builder + .push("(") + .push("auction_id = ") + .push_bind(auction_id) + .push(" AND ") + .push("order_uid = ") + .push_bind(order_uid) + .push(")"); + } + + query_builder.push(" ORDER BY application_order"); + + let query = query_builder.build_query_as::(); + let rows = query.fetch_all(ex).await?; + let mut result: HashMap<(AuctionId, OrderUid), Vec> = HashMap::new(); + for row in rows { + let key = (row.auction_id, row.order_uid); + result.entry(key).or_default().push(row); + } + + Ok(result) } #[cfg(test)] @@ -84,12 +101,21 @@ mod tests { crate::clear_DANGER_(&mut db).await.unwrap(); // same primary key for all fee policies - let (auction_id, order_uid) = (1, ByteArray([1; 56])); + let (auction_id_a, order_uid_a) = (1, ByteArray([1; 56])); + let (auction_id_b, order_uid_b) = (2, ByteArray([2; 56])); + + let output = fetch_all( + &mut db, + &[(auction_id_a, order_uid_a), (auction_id_b, order_uid_b)], + ) + .await + .unwrap(); + assert!(output.is_empty()); // surplus fee policy without caps let fee_policy_1 = FeePolicy { - auction_id, - order_uid, + auction_id: auction_id_a, + order_uid: order_uid_a, kind: FeePolicyKind::Surplus, surplus_factor: Some(0.1), surplus_max_volume_factor: Some(0.99999), @@ -99,8 +125,8 @@ mod tests { }; // surplus fee policy with caps let fee_policy_2 = FeePolicy { - auction_id, - order_uid, + auction_id: auction_id_b, + order_uid: order_uid_b, kind: FeePolicyKind::Surplus, surplus_factor: Some(0.2), surplus_max_volume_factor: Some(0.05), @@ -110,8 +136,8 @@ mod tests { }; // volume based fee policy let fee_policy_3 = FeePolicy { - auction_id, - order_uid, + auction_id: auction_id_b, + order_uid: order_uid_b, kind: FeePolicyKind::Volume, surplus_factor: None, surplus_max_volume_factor: None, @@ -121,8 +147,8 @@ mod tests { }; // price improvement fee policy let fee_policy_4 = FeePolicy { - auction_id, - order_uid, + auction_id: auction_id_a, + order_uid: order_uid_a, kind: FeePolicyKind::PriceImprovement, surplus_factor: None, surplus_max_volume_factor: None, @@ -131,11 +157,34 @@ mod tests { price_improvement_max_volume_factor: Some(0.99999), }; - let fee_policies = vec![fee_policy_1, fee_policy_2, fee_policy_3, fee_policy_4]; - + let fee_policies = vec![ + fee_policy_1.clone(), + fee_policy_2.clone(), + fee_policy_3.clone(), + fee_policy_4.clone(), + ]; insert_batch(&mut db, fee_policies.clone()).await.unwrap(); - let output = fetch(&mut db, 1, order_uid).await.unwrap(); - assert_eq!(output, fee_policies); + let mut expected = HashMap::new(); + expected.insert( + (auction_id_a, order_uid_a), + vec![fee_policy_1, fee_policy_4], + ); + let output = fetch_all(&mut db, &[(auction_id_a, order_uid_a)]) + .await + .unwrap(); + assert_eq!(output, expected); + + expected.insert( + (auction_id_b, order_uid_b), + vec![fee_policy_2, fee_policy_3], + ); + let output = fetch_all( + &mut db, + &[(auction_id_a, order_uid_a), (auction_id_b, order_uid_b)], + ) + .await + .unwrap(); + assert_eq!(output, expected); } } diff --git a/crates/database/src/trades.rs b/crates/database/src/trades.rs index ec35cb64a8..35423ee4b6 100644 --- a/crates/database/src/trades.rs +++ b/crates/database/src/trades.rs @@ -1,5 +1,5 @@ use { - crate::{Address, OrderUid, TransactionHash}, + crate::{auction::AuctionId, Address, OrderUid, TransactionHash}, bigdecimal::BigDecimal, futures::stream::BoxStream, sqlx::PgConnection, @@ -17,6 +17,7 @@ pub struct TradesQueryRow { pub buy_token: Address, pub sell_token: Address, pub tx_hash: Option, + pub auction_id: Option, } pub fn trades<'a>( @@ -35,10 +36,11 @@ SELECT o.owner, o.buy_token, o.sell_token, - settlement.tx_hash + settlement.tx_hash, + settlement.auction_id FROM trades t LEFT OUTER JOIN LATERAL ( - SELECT tx_hash FROM settlements s + SELECT tx_hash, auction_id FROM settlements s WHERE s.block_number = t.block_number AND s.log_index > t.log_index ORDER BY s.log_index ASC @@ -94,6 +96,7 @@ mod tests { order_uid: OrderUid, event_index: EventIndex, tx_hash: Option, + auction_id: Option, ) -> TradesQueryRow { crate::events::append( ex, @@ -113,6 +116,7 @@ mod tests { order_uid, owner, tx_hash, + auction_id, ..Default::default() } } @@ -123,6 +127,7 @@ mod tests { order_uid: OrderUid, event_index: EventIndex, tx_hash: Option, + auction_id: Option, ) -> TradesQueryRow { let order = Order { uid: order_uid, @@ -130,7 +135,7 @@ mod tests { ..Default::default() }; crate::orders::insert_order(ex, &order).await.unwrap(); - add_trade(ex, owner, order_uid, event_index, tx_hash).await + add_trade(ex, owner, order_uid, event_index, tx_hash, auction_id).await } async fn assert_trades( @@ -139,10 +144,11 @@ mod tests { order_uid_filter: Option<&OrderUid>, expected: &[TradesQueryRow], ) { - let filtered = trades(db, owner_filter, order_uid_filter) + let mut filtered = trades(db, owner_filter, order_uid_filter) .try_collect::>() .await .unwrap(); + filtered.sort_by_key(|t| (t.block_number, t.log_index)); assert_eq!(filtered, expected); } @@ -161,7 +167,7 @@ mod tests { log_index: 0, }; let trade_a = - add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_a, None).await; + add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_a, None, None).await; assert_trades(&mut db, None, None, &[trade_a.clone()]).await; let event_index_b = EventIndex { @@ -169,8 +175,8 @@ mod tests { log_index: 0, }; let trade_b = - add_order_and_trade(&mut db, owners[0], order_ids[1], event_index_b, None).await; - assert_trades(&mut db, None, None, &[trade_b, trade_a]).await; + add_order_and_trade(&mut db, owners[0], order_ids[1], event_index_b, None, None).await; + assert_trades(&mut db, None, None, &[trade_a, trade_b]).await; } #[tokio::test] @@ -206,7 +212,7 @@ mod tests { ) .await .unwrap(); - add_order_and_trade(&mut db, owner, order_uid, event_index_0, None).await; + add_order_and_trade(&mut db, owner, order_uid, event_index_0, None, None).await; } } @@ -234,14 +240,14 @@ mod tests { log_index: 0, }; let trade_0 = - add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_0, None).await; + add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_0, None, None).await; let event_index_1 = EventIndex { block_number: 0, log_index: 1, }; let trade_1 = - add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_1, None).await; + add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_1, None, None).await; assert_trades(&mut db, Some(&owners[0]), None, &[trade_0.clone()]).await; assert_trades(&mut db, Some(&owners[1]), None, &[trade_1]).await; @@ -258,7 +264,7 @@ mod tests { .unwrap(); assert_trades(&mut db, Some(&owners[3]), None, &[trade_0.clone()]).await; - add_order_and_trade(&mut db, owners[3], order_ids[3], event_index_1, None).await; + add_order_and_trade(&mut db, owners[3], order_ids[3], event_index_1, None, None).await; let onchain_order = OnchainOrderPlacement { order_uid: ByteArray(order_ids[3].0), sender: owners[3], @@ -284,14 +290,14 @@ mod tests { log_index: 0, }; let trade_0 = - add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_0, None).await; + add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_0, None, None).await; let event_index_1 = EventIndex { block_number: 0, log_index: 1, }; let trade_1 = - add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_1, None).await; + add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_1, None, None).await; assert_trades(&mut db, None, Some(&order_ids[0]), &[trade_0]).await; assert_trades(&mut db, None, Some(&order_ids[1]), &[trade_1]).await; @@ -311,7 +317,7 @@ mod tests { block_number: 0, log_index: 0, }; - add_trade(&mut db, owners[0], order_ids[0], event_index, None).await; + add_trade(&mut db, owners[0], order_ids[0], event_index, None, None).await; // Trade exists in DB but no matching order assert_trades(&mut db, None, Some(&order_ids[0]), &[]).await; assert_trades(&mut db, Some(&owners[0]), None, &[]).await; @@ -323,23 +329,24 @@ mod tests { event_index: EventIndex, solver: Address, transaction_hash: TransactionHash, + auction_id: AuctionId, ) -> Settlement { - crate::events::append( + let settlement = Settlement { + solver, + transaction_hash, + }; + crate::events::append(ex, &[(event_index, Event::Settlement(settlement))]) + .await + .unwrap(); + crate::settlements::update_settlement_auction( ex, - &[( - event_index, - Event::Settlement(Settlement { - solver, - transaction_hash, - }), - )], + event_index.block_number, + event_index.log_index, + auction_id, ) .await .unwrap(); - Settlement { - solver, - transaction_hash, - } + settlement } #[tokio::test] @@ -360,6 +367,7 @@ mod tests { }, Default::default(), Default::default(), + 1, ) .await; @@ -372,6 +380,7 @@ mod tests { log_index: 0, }, Some(settlement.transaction_hash), + Some(1), ) .await; assert_trades(&mut db, None, None, &[trade_a.clone()]).await; @@ -385,6 +394,7 @@ mod tests { log_index: 1, }, Some(settlement.transaction_hash), + Some(1), ) .await; assert_trades(&mut db, None, None, &[trade_a, trade_b]).await; @@ -408,6 +418,7 @@ mod tests { }, Default::default(), Default::default(), + 1, ) .await; @@ -420,6 +431,7 @@ mod tests { log_index: 0, }, Some(settlement.transaction_hash), + Some(1), ) .await; @@ -432,6 +444,7 @@ mod tests { log_index: 1, }, Some(settlement.transaction_hash), + Some(1), ) .await; // Trades query returns nothing when there are no corresponding orders. @@ -456,6 +469,7 @@ mod tests { }, Default::default(), Default::default(), + 1, ) .await; let settlement_b = add_settlement( @@ -466,6 +480,7 @@ mod tests { }, Default::default(), ByteArray([2; 32]), + 1, ) .await; @@ -478,6 +493,7 @@ mod tests { log_index: 0, }, Some(settlement_a.transaction_hash), + Some(1), ) .await; assert_trades(&mut db, None, None, &[trade_a.clone()]).await; @@ -491,6 +507,7 @@ mod tests { log_index: 2, }, Some(settlement_b.transaction_hash), + Some(1), ) .await; assert_trades(&mut db, None, None, &[trade_a, trade_b]).await; diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 247f2c18f9..fb16affecb 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -21,7 +21,7 @@ ethcontract = { workspace = true } ethrpc = { path = "../ethrpc" } hex = { workspace = true } hex-literal = { workspace = true } -model = { path = "../model" } +model = { path = "../model", features = ["e2e"] } number = { path = "../number" } observe = { path = "../observe" } orderbook = { path = "../orderbook" } diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index 6a80161da8..ae220f93e9 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -32,3 +32,6 @@ web3 = { workspace = true, features = ["signing"] } serde_json = { workspace = true } shared = { path = "../shared" } maplit = { workspace = true } + +[features] +e2e = [] diff --git a/crates/model/src/fee_policy.rs b/crates/model/src/fee_policy.rs new file mode 100644 index 0000000000..f503d37116 --- /dev/null +++ b/crates/model/src/fee_policy.rs @@ -0,0 +1,36 @@ +use { + number::serialization::HexOrDecimalU256, + primitive_types::U256, + serde::Serialize, + serde_with::serde_as, +}; + +#[serde_as] +#[derive(PartialEq, Clone, Debug, Serialize)] +#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))] +#[serde(rename_all = "camelCase")] +pub enum FeePolicy { + #[serde(rename_all = "camelCase")] + Surplus { factor: f64, max_volume_factor: f64 }, + #[serde(rename_all = "camelCase")] + Volume { factor: f64 }, + #[serde(rename_all = "camelCase")] + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: Quote, + }, +} + +#[serde_as] +#[derive(PartialEq, Clone, Debug, Serialize)] +#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))] +#[serde(rename_all = "camelCase")] +pub struct Quote { + #[serde_as(as = "HexOrDecimalU256")] + pub sell_amount: U256, + #[serde_as(as = "HexOrDecimalU256")] + pub buy_amount: U256, + #[serde_as(as = "HexOrDecimalU256")] + pub fee: U256, +} diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index b30cb82028..4817639a99 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -1,6 +1,7 @@ //! Contains models that are shared between the orderbook and the solver. pub mod auction; +pub mod fee_policy; mod format; pub mod interaction; pub mod order; diff --git a/crates/model/src/trade.rs b/crates/model/src/trade.rs index 48b23aba99..a0337adaae 100644 --- a/crates/model/src/trade.rs +++ b/crates/model/src/trade.rs @@ -2,15 +2,16 @@ //! as described by the openapi documentation. use { - crate::order::OrderUid, + crate::{fee_policy::FeePolicy, order::OrderUid}, num::BigUint, primitive_types::{H160, H256}, - serde::{Deserialize, Serialize}, + serde::Serialize, serde_with::{serde_as, DisplayFromStr}, }; #[serde_as] -#[derive(Eq, PartialEq, Clone, Debug, Default, Deserialize, Serialize)] +#[derive(PartialEq, Clone, Debug, Default, Serialize)] +#[cfg_attr(any(test, feature = "e2e"), derive(serde::Deserialize))] #[serde(rename_all = "camelCase")] pub struct Trade { pub block_number: u64, @@ -28,11 +29,19 @@ pub struct Trade { pub sell_token: H160, // Settlement Data pub tx_hash: Option, + // Fee Policy Data + pub fee_policies: Vec, } #[cfg(test)] mod tests { - use {super::*, serde_json::json, shared::assert_json_matches}; + use { + super::*, + crate::fee_policy::Quote, + primitive_types::U256, + serde_json::json, + shared::assert_json_matches, + }; #[test] fn deserialization_and_back() { @@ -47,7 +56,31 @@ mod tests { "owner": "0x0000000000000000000000000000000000000001", "sellToken": "0x000000000000000000000000000000000000000a", "buyToken": "0x0000000000000000000000000000000000000009", - "txHash": "0x0000000000000000000000000000000000000000000000000000000000000040" + "txHash": "0x0000000000000000000000000000000000000000000000000000000000000040", + "feePolicies": [ + { + "surplus": { + "factor": 1.1, + "maxVolumeFactor": 2.2 + } + }, + { + "volume": { + "factor": 0.9 + } + }, + { + "priceImprovement": { + "factor": 1.2, + "maxVolumeFactor": 1.5, + "quote": { + "sellAmount": "100", + "buyAmount": "150", + "fee": "5" + } + } + } + ] }); let expected = Trade { block_number: 1337u64, @@ -60,6 +93,22 @@ mod tests { buy_token: H160::from_low_u64_be(9), sell_token: H160::from_low_u64_be(10), tx_hash: Some(H256::from_low_u64_be(64)), + fee_policies: vec![ + FeePolicy::Surplus { + factor: 1.1, + max_volume_factor: 2.2, + }, + FeePolicy::Volume { factor: 0.9 }, + FeePolicy::PriceImprovement { + factor: 1.2, + max_volume_factor: 1.5, + quote: Quote { + sell_amount: U256::from(100u64), + buy_amount: U256::from(150u64), + fee: U256::from(5u64), + }, + }, + ], }; let deserialized: Trade = serde_json::from_value(value.clone()).unwrap(); diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index 091a0cfa1b..f13e1b0be9 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -1126,6 +1126,12 @@ components: allOf: - $ref: "#/components/schemas/TransactionHash" nullable: true + feePolicies: + description: | + The fee policies that were used to compute the fees for this trade. Listed in the order they got applied. + type: array + items: + $ref: "#/components/schemas/FeePolicy" required: - blockNumber - logIndex diff --git a/crates/orderbook/src/database.rs b/crates/orderbook/src/database.rs index 03c4c4fe72..c19a6435d8 100644 --- a/crates/orderbook/src/database.rs +++ b/crates/orderbook/src/database.rs @@ -1,5 +1,6 @@ pub mod app_data; pub mod auctions; +mod fee_policies; pub mod orders; pub mod quotes; pub mod solver_competition; diff --git a/crates/orderbook/src/database/fee_policies.rs b/crates/orderbook/src/database/fee_policies.rs new file mode 100644 index 0000000000..b9b8698eff --- /dev/null +++ b/crates/orderbook/src/database/fee_policies.rs @@ -0,0 +1,117 @@ +use { + anyhow::Context, + bigdecimal::{num_traits::CheckedDiv, FromPrimitive}, + database::{auction::AuctionId, OrderUid}, + model::fee_policy::{FeePolicy, Quote}, + num::BigRational, + number::conversions::{big_decimal_to_u256, big_rational_to_u256}, + std::{collections::HashMap, ops::Mul}, +}; + +impl super::Postgres { + pub async fn fee_policies( + &self, + keys_filter: &[(AuctionId, OrderUid)], + ) -> anyhow::Result>> { + let mut ex = self.pool.acquire().await?; + + let timer = super::Metrics::get() + .database_queries + .with_label_values(&["fee_policies"]) + .start_timer(); + let fee_policies = database::fee_policies::fetch_all(&mut ex, keys_filter).await?; + timer.stop_and_record(); + + let quote_order_uids = fee_policies + .iter() + .filter_map(|((_, order_uid), policies)| { + policies + .iter() + .any(|policy| { + matches!( + policy.kind, + database::fee_policies::FeePolicyKind::PriceImprovement + ) + }) + .then_some(*order_uid) + }) + .collect::>(); + + let timer = super::Metrics::get() + .database_queries + .with_label_values(&["order_quotes"]) + .start_timer(); + let quotes = database::orders::read_quotes(&mut ex, quote_order_uids.as_slice()) + .await? + .into_iter() + .map(|quote| (quote.order_uid, quote)) + .collect::>(); + timer.stop_and_record(); + + fee_policies + .into_iter() + .map(|((auction_id, order_uid), policies)| { + policies + .into_iter() + .map(|policy| fee_policy_from(policy, quotes.get(&order_uid), order_uid)) + .collect::>>() + .map(|policies| ((auction_id, order_uid), policies)) + }) + .collect::>>() + } +} + +fn fee_policy_from( + db_fee_policy: database::fee_policies::FeePolicy, + quote: Option<&database::orders::Quote>, + order_uid: OrderUid, +) -> anyhow::Result { + Ok(match db_fee_policy.kind { + database::fee_policies::FeePolicyKind::Surplus => FeePolicy::Surplus { + factor: db_fee_policy + .surplus_factor + .context("missing surplus factor")?, + max_volume_factor: db_fee_policy + .surplus_max_volume_factor + .context("missing surplus max volume factor")?, + }, + database::fee_policies::FeePolicyKind::Volume => FeePolicy::Volume { + factor: db_fee_policy + .volume_factor + .context("missing volume factor")?, + }, + database::fee_policies::FeePolicyKind::PriceImprovement => { + let quote = quote.context(format!( + "missing price improvement quote for order '{:?}'", + order_uid + ))?; + let gas_amount = + BigRational::from_f64(quote.gas_amount).context("invalid quote gas amount")?; + let gas_price = + BigRational::from_f64(quote.gas_price).context("invalid quote gas price")?; + let sell_token_price = BigRational::from_f64(quote.sell_token_price) + .context("invalid quote sell token price")?; + let fee = big_rational_to_u256( + &gas_amount + .mul(gas_price) + .checked_div(&sell_token_price) + .context("invalid price improvement quote fee value")?, + )?; + FeePolicy::PriceImprovement { + factor: db_fee_policy + .price_improvement_factor + .context("missing price improvement factor")?, + max_volume_factor: db_fee_policy + .price_improvement_max_volume_factor + .context("missing price improvement max volume factor")?, + quote: Quote { + sell_amount: big_decimal_to_u256("e.sell_amount) + .context("invalid price improvement quote sell amount value")?, + buy_amount: big_decimal_to_u256("e.buy_amount) + .context("invalid price improvement quote buy amount value")?, + fee, + }, + } + } + }) +} diff --git a/crates/orderbook/src/database/trades.rs b/crates/orderbook/src/database/trades.rs index f7330aa0ab..321fc33b6d 100644 --- a/crates/orderbook/src/database/trades.rs +++ b/crates/orderbook/src/database/trades.rs @@ -3,8 +3,8 @@ use { anyhow::{Context, Result}, database::{byte_array::ByteArray, trades::TradesQueryRow}, ethcontract::H160, - futures::{stream::TryStreamExt, StreamExt}, - model::{order::OrderUid, trade::Trade}, + futures::stream::TryStreamExt, + model::{fee_policy::FeePolicy, order::OrderUid, trade::Trade}, number::conversions::big_decimal_to_big_uint, primitive_types::H256, std::convert::TryInto, @@ -25,27 +25,47 @@ pub struct TradeFilter { #[async_trait::async_trait] impl TradeRetrieving for Postgres { async fn trades(&self, filter: &TradeFilter) -> Result> { - let _timer = super::Metrics::get() + let timer = super::Metrics::get() .database_queries .with_label_values(&["trades"]) .start_timer(); let mut ex = self.pool.acquire().await?; - database::trades::trades( + let trades = database::trades::trades( &mut ex, filter.owner.map(|owner| ByteArray(owner.0)).as_ref(), filter.order_uid.map(|uid| ByteArray(uid.0)).as_ref(), ) - .map(|result| match result { - Ok(row) => trade_from(row), - Err(err) => Err(anyhow::Error::from(err)), - }) - .try_collect() - .await + .map_err(anyhow::Error::from) + .try_collect::>() + .await?; + timer.stop_and_record(); + + let auction_order_uids = trades + .iter() + .filter_map(|t| t.auction_id.map(|auction_id| (auction_id, t.order_uid))) + .collect::>(); + let fee_policies = self.fee_policies(auction_order_uids.as_slice()).await?; + + trades + .into_iter() + .map(|trade| { + let fee_policies = trade + .auction_id + .map(|auction_id| { + fee_policies + .get(&(auction_id, trade.order_uid)) + .cloned() + .unwrap_or_default() + }) + .unwrap_or_default(); + trade_from(trade, fee_policies) + }) + .collect::>>() } } -fn trade_from(row: TradesQueryRow) -> Result { +fn trade_from(row: TradesQueryRow, fee_policies: Vec) -> Result { let block_number = row .block_number .try_into() @@ -73,6 +93,7 @@ fn trade_from(row: TradesQueryRow) -> Result { buy_token, sell_token, tx_hash, + fee_policies, }) } @@ -82,6 +103,6 @@ mod tests { #[test] fn convert_trade() { - trade_from(TradesQueryRow::default()).unwrap(); + trade_from(TradesQueryRow::default(), vec![]).unwrap(); } }