From c1a60c0548b4d8233b069777c870f3d511a00ac5 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 12 Jan 2024 17:49:47 +0000 Subject: [PATCH 01/24] PriceImprovement policy in autopilot --- crates/autopilot/src/arguments.rs | 24 +++++++++-- crates/autopilot/src/domain/fee/mod.rs | 40 +++++++++++++++---- .../src/infra/persistence/dto/order.rs | 38 ++++++++++++++++++ crates/autopilot/src/run.rs | 2 +- crates/driver/openapi.yml | 26 ++++++++++++ 5 files changed, 117 insertions(+), 13 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index d4a4395cf4..6e8c8be941 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -353,6 +353,12 @@ pub struct FeePolicy { /// - Surplus with cap: /// surplus:0.5:0.06 /// + /// - Price improvement with cap: + /// price_improvement:0.5:1.0 + /// + /// - Price improvement without cap: + /// price_improvement:0.5:0.06 + /// /// - Volume based: /// volume:0.1 #[clap(long, env, default_value = "surplus:0.0:1.0")] @@ -366,16 +372,24 @@ pub struct FeePolicy { } impl FeePolicy { - pub fn to_domain(self) -> domain::fee::Policy { + pub fn to_domain(&self, quote: Option<&domain::Quote>) -> Option { match self.fee_policy_kind { FeePolicyKind::Surplus { factor, max_volume_factor, - } => domain::fee::Policy::Surplus { + } => Some(domain::fee::Policy::Surplus { + factor, + max_volume_factor, + }), + FeePolicyKind::PriceImprovement { + factor, + max_volume_factor, + } => quote.map(|q| domain::fee::Policy::PriceImprovement { factor, max_volume_factor, - }, - FeePolicyKind::Volume { factor } => domain::fee::Policy::Volume { factor }, + quote: q.clone().into(), + }), + FeePolicyKind::Volume { factor } => Some(domain::fee::Policy::Volume { factor }), } } } @@ -384,6 +398,8 @@ impl FeePolicy { pub enum FeePolicyKind { /// How much of the order's surplus should be taken as a protocol fee. Surplus { factor: f64, max_volume_factor: f64 }, + /// How the price improvement should be calculated. + PriceImprovement { factor: f64, max_volume_factor: f64 }, /// How much of the order's volume should be taken as a protocol fee. Volume { factor: f64 }, } diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 0d994dae18..b97018f69e 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -4,20 +4,24 @@ //! we define the way to calculate the protocol fee based on the configuration //! parameters. -use crate::{ - boundary::{self}, - domain, +use { + crate::{ + arguments, + boundary::{self}, + domain, + }, + primitive_types::U256, }; /// Constructs fee policies based on the current configuration. #[derive(Debug)] pub struct ProtocolFee { - policy: Policy, + policy: arguments::FeePolicy, fee_policy_skip_market_orders: bool, } impl ProtocolFee { - pub fn new(policy: Policy, fee_policy_skip_market_orders: bool) -> Self { + pub fn new(policy: arguments::FeePolicy, fee_policy_skip_market_orders: bool) -> Self { Self { policy, fee_policy_skip_market_orders, @@ -31,13 +35,13 @@ impl ProtocolFee { if self.fee_policy_skip_market_orders { vec![] } else { - vec![self.policy] + self.policy.to_domain(quote).into_iter().collect() } } boundary::OrderClass::Liquidity => vec![], boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return vec![self.policy]; + return self.policy.to_domain(quote).into_iter().collect(); } // if the quote is missing, we can't determine if the order is outside the @@ -52,7 +56,7 @@ impl ProtocolFee { "e.buy_amount, "e.sell_amount, ) { - vec![self.policy] + self.policy.to_domain(Some(quote)).into_iter().collect() } else { vec![] } @@ -78,6 +82,11 @@ pub enum Policy { /// Cap protocol fee with a percentage of the order's volume. max_volume_factor: f64, }, + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: Quote, + }, /// How much of the order's volume should be taken as a protocol fee. /// The fee is taken in `sell` token for `sell` orders and in `buy` /// token for `buy` orders. @@ -87,3 +96,18 @@ pub enum Policy { factor: f64, }, } + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Quote { + pub sell_amount: U256, + pub buy_amount: U256, +} + +impl From for Quote { + fn from(value: domain::Quote) -> Self { + Self { + sell_amount: value.sell_amount, + buy_amount: value.buy_amount, + } + } +} diff --git a/crates/autopilot/src/infra/persistence/dto/order.rs b/crates/autopilot/src/infra/persistence/dto/order.rs index be99951138..266de08a28 100644 --- a/crates/autopilot/src/infra/persistence/dto/order.rs +++ b/crates/autopilot/src/infra/persistence/dto/order.rs @@ -272,9 +272,23 @@ pub enum FeePolicy { #[serde(rename_all = "camelCase")] Surplus { factor: f64, max_volume_factor: f64 }, #[serde(rename_all = "camelCase")] + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: Quote, + }, + #[serde(rename_all = "camelCase")] Volume { factor: f64 }, } +#[serde_as] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Quote { + pub sell_amount: U256, + pub buy_amount: U256, +} + impl From for FeePolicy { fn from(policy: domain::fee::Policy) -> Self { match policy { @@ -285,6 +299,18 @@ impl From for FeePolicy { factor, max_volume_factor, }, + domain::fee::Policy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => Self::PriceImprovement { + factor, + max_volume_factor, + quote: Quote { + sell_amount: quote.sell_amount, + buy_amount: quote.buy_amount, + }, + }, domain::fee::Policy::Volume { factor } => Self::Volume { factor }, } } @@ -300,6 +326,18 @@ impl From for domain::fee::Policy { factor, max_volume_factor, }, + FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => Self::PriceImprovement { + factor, + max_volume_factor, + quote: domain::fee::Quote { + sell_amount: quote.sell_amount, + buy_amount: quote.buy_amount, + }, + }, FeePolicy::Volume { factor } => Self::Volume { factor }, } } diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index b918151880..ca79681c2e 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -541,7 +541,7 @@ pub async fn run(args: Arguments) { .try_into() .expect("limit order price factor can't be converted to BigDecimal"), domain::ProtocolFee::new( - args.fee_policy.clone().to_domain(), + args.fee_policy.clone(), args.fee_policy.fee_policy_skip_market_orders, ), ); diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 97f76d60a2..2cc6747030 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -419,6 +419,7 @@ components: type: object oneOf: - $ref: "#/components/schemas/SurplusFee" + - $ref: "#/components/schemas/PriceImprovement" - $ref: "#/components/schemas/VolumeFee" SurplusFee: description: | @@ -436,6 +437,24 @@ components: description: The factor of the user surplus that the protocol will request from the solver after settling the order type: number example: 0.5 + PriceImprovement: + description: | + If the order out of market, pay the protocol a factor of the difference. + type: object + properties: + kind: + type: string + enum: [ "priceImprovement" ] + maxVolumeFactor: + description: Never charge more than that percentage of the order volume. + type: number + example: 0.1 + factor: + description: The factor of the user surplus that the protocol will request from the solver after settling the order + type: number + example: 0.5 + quote: + $ref: "#/components/schemas/Quote" VolumeFee: type: object properties: @@ -446,6 +465,13 @@ components: description: The fraction of the order's volume that the protocol will request from the solver after settling the order. type: number example: 0.5 + Quote: + type: object + properties: + sell_amount: + $ref: "#/components/schemas/TokenAmount" + buy_amount: + $ref: "#/components/schemas/TokenAmount" Error: description: Response on API errors. type: object From d68cfbdf71921fc388d4ccee6f0e06b14dd9fd7a Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 12 Jan 2024 18:06:53 +0000 Subject: [PATCH 02/24] PriceImprovement policy in driver --- .../src/domain/competition/order/fees.rs | 13 ++++++++++ .../src/domain/competition/solution/fee.rs | 7 ++++++ .../src/infra/api/routes/solve/dto/auction.rs | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/crates/driver/src/domain/competition/order/fees.rs b/crates/driver/src/domain/competition/order/fees.rs index 0419a35c69..32648786be 100644 --- a/crates/driver/src/domain/competition/order/fees.rs +++ b/crates/driver/src/domain/competition/order/fees.rs @@ -1,3 +1,5 @@ +use crate::domain::eth; + #[derive(Clone, Debug)] pub enum FeePolicy { /// If the order receives more than limit price, take the protocol fee as a @@ -15,6 +17,11 @@ pub enum FeePolicy { /// Cap protocol fee with a percentage of the order's volume. max_volume_factor: f64, }, + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: Quote, + }, /// How much of the order's volume should be taken as a protocol fee. /// The fee is taken in `sell` token for `sell` orders and in `buy` /// token for `buy` orders. @@ -24,3 +31,9 @@ pub enum FeePolicy { factor: f64, }, } + +#[derive(Clone, Debug)] +pub struct Quote { + pub sell_amount: eth::U256, + pub buy_amount: eth::U256, +} diff --git a/crates/driver/src/domain/competition/solution/fee.rs b/crates/driver/src/domain/competition/solution/fee.rs index 441f65fbb6..d0e7a19bd9 100644 --- a/crates/driver/src/domain/competition/solution/fee.rs +++ b/crates/driver/src/domain/competition/solution/fee.rs @@ -85,6 +85,13 @@ impl Fulfillment { tracing::debug!(uid=?self.order().uid, fee_from_surplus=?fee_from_surplus, fee_from_volume=?fee_from_volume, protocol_fee=?(std::cmp::min(fee_from_surplus, fee_from_volume)), executed=?self.executed(), surplus_fee=?self.surplus_fee(), "calculated protocol fee"); Ok(std::cmp::min(fee_from_surplus, fee_from_volume)) } + Some(FeePolicy::PriceImprovement { + factor: _, + max_volume_factor: _, + quote: _, + }) => { + todo!() + } Some(FeePolicy::Volume { factor }) => self.fee_from_volume(prices, *factor), None => Ok(0.into()), } diff --git a/crates/driver/src/infra/api/routes/solve/dto/auction.rs b/crates/driver/src/infra/api/routes/solve/dto/auction.rs index 2bca940a27..7a2651d2de 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/auction.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/auction.rs @@ -126,6 +126,18 @@ impl Auction { factor, max_volume_factor, }, + FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => competition::order::FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote: competition::order::fees::Quote { + sell_amount: quote.sell_amount, + buy_amount: quote.buy_amount, + }, + }, FeePolicy::Volume { factor } => { competition::order::FeePolicy::Volume { factor } } @@ -311,5 +323,18 @@ enum FeePolicy { #[serde(rename_all = "camelCase")] Surplus { factor: f64, max_volume_factor: f64 }, #[serde(rename_all = "camelCase")] + PriceImprovement { + factor: f64, + max_volume_factor: f64, + quote: Quote, + }, + #[serde(rename_all = "camelCase")] Volume { factor: f64 }, } + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Quote { + pub sell_amount: eth::U256, + pub buy_amount: eth::U256, +} From ce00a5b68ac2fee371e26e8c163a8f97f710edd5 Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 12 Jan 2024 18:13:05 +0000 Subject: [PATCH 03/24] todo --- crates/driver/src/domain/competition/order/fees.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/driver/src/domain/competition/order/fees.rs b/crates/driver/src/domain/competition/order/fees.rs index 32648786be..09a09bf34c 100644 --- a/crates/driver/src/domain/competition/order/fees.rs +++ b/crates/driver/src/domain/competition/order/fees.rs @@ -17,6 +17,7 @@ pub enum FeePolicy { /// Cap protocol fee with a percentage of the order's volume. max_volume_factor: f64, }, + /// todo: add some meaningful description PriceImprovement { factor: f64, max_volume_factor: f64, From e901812cf2b46ce7a512928d8501a722f548e2a7 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Jan 2024 19:09:31 +0000 Subject: [PATCH 04/24] Update crates/autopilot/src/arguments.rs Co-authored-by: Felix Leupold --- crates/autopilot/src/arguments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 6e8c8be941..609d0e98dc 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -398,7 +398,7 @@ impl FeePolicy { pub enum FeePolicyKind { /// How much of the order's surplus should be taken as a protocol fee. Surplus { factor: f64, max_volume_factor: f64 }, - /// How the price improvement should be calculated. + /// How much of the order's price improvement should be taken as a protocol fee. PriceImprovement { factor: f64, max_volume_factor: f64 }, /// How much of the order's volume should be taken as a protocol fee. Volume { factor: f64 }, From 540680786c58b8bd390fffff660e6c55a63f2a1b Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Jan 2024 19:09:40 +0000 Subject: [PATCH 05/24] Update crates/driver/openapi.yml Co-authored-by: Martin Beckmann --- crates/driver/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 2cc6747030..22a537bc1b 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -439,7 +439,7 @@ components: example: 0.5 PriceImprovement: description: | - If the order out of market, pay the protocol a factor of the difference. + If the order was out of market during creation, pay the protocol a factor of the difference. type: object properties: kind: From ab004412fe4b98f232c0b4f82c68f3b3983cab23 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Jan 2024 19:34:29 +0000 Subject: [PATCH 06/24] Minor review fixes --- crates/autopilot/src/arguments.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 609d0e98dc..aa0f20658c 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -353,10 +353,10 @@ pub struct FeePolicy { /// - Surplus with cap: /// surplus:0.5:0.06 /// - /// - Price improvement with cap: + /// - Price improvement without cap: /// price_improvement:0.5:1.0 /// - /// - Price improvement without cap: + /// - Price improvement with cap: /// price_improvement:0.5:0.06 /// /// - Volume based: From ebf8dd90919f2f188d165198d66056ebf3bd45ce Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Jan 2024 19:35:10 +0000 Subject: [PATCH 07/24] Formatting --- crates/autopilot/src/arguments.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index aa0f20658c..9ae390b51c 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -398,7 +398,8 @@ impl FeePolicy { pub enum FeePolicyKind { /// How much of the order's surplus should be taken as a protocol fee. Surplus { factor: f64, max_volume_factor: f64 }, - /// How much of the order's price improvement should be taken as a protocol fee. + /// How much of the order's price improvement should be taken as a protocol + /// fee. PriceImprovement { factor: f64, max_volume_factor: f64 }, /// How much of the order's volume should be taken as a protocol fee. Volume { factor: f64 }, From 4a0e5ff93621d5601937b0d67bc6e703c1e6a27f Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Jan 2024 20:14:56 +0000 Subject: [PATCH 08/24] Implement price improvement fee calculation --- .../src/domain/competition/solution/fee.rs | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/fee.rs b/crates/driver/src/domain/competition/solution/fee.rs index d0e7a19bd9..f67c8e8a09 100644 --- a/crates/driver/src/domain/competition/solution/fee.rs +++ b/crates/driver/src/domain/competition/solution/fee.rs @@ -78,28 +78,51 @@ impl Fulfillment { Some(FeePolicy::Surplus { factor, max_volume_factor, - }) => { - let fee_from_surplus = self.fee_from_surplus(prices, *factor)?; - let fee_from_volume = self.fee_from_volume(prices, *max_volume_factor)?; - // take the smaller of the two - tracing::debug!(uid=?self.order().uid, fee_from_surplus=?fee_from_surplus, fee_from_volume=?fee_from_volume, protocol_fee=?(std::cmp::min(fee_from_surplus, fee_from_volume)), executed=?self.executed(), surplus_fee=?self.surplus_fee(), "calculated protocol fee"); - Ok(std::cmp::min(fee_from_surplus, fee_from_volume)) - } + }) => self.calculate_fee( + self.order().sell.amount.0, + self.order().buy.amount.0, + prices, + *factor, + *max_volume_factor, + ), Some(FeePolicy::PriceImprovement { - factor: _, - max_volume_factor: _, - quote: _, - }) => { - todo!() - } + factor, + max_volume_factor, + quote, + }) => self.calculate_fee( + quote.sell_amount, + quote.buy_amount, + prices, + *factor, + *max_volume_factor, + ), Some(FeePolicy::Volume { factor }) => self.fee_from_volume(prices, *factor), None => Ok(0.into()), } } - fn fee_from_surplus(&self, prices: ClearingPrices, factor: f64) -> Result { - let sell_amount = self.order().sell.amount.0; - let buy_amount = self.order().buy.amount.0; + fn calculate_fee( + &self, + sell_amount: eth::U256, + buy_amount: eth::U256, + prices: ClearingPrices, + factor: f64, + max_volume_factor: f64, + ) -> Result { + let fee_from_surplus = self.fee_from_surplus(sell_amount, buy_amount, prices, factor)?; + let fee_from_volume = self.fee_from_volume(prices, max_volume_factor)?; + // take the smaller of the two + tracing::debug!(uid=?self.order().uid, fee_from_surplus=?fee_from_surplus, fee_from_volume=?fee_from_volume, protocol_fee=?(std::cmp::min(fee_from_surplus, fee_from_volume)), executed=?self.executed(), surplus_fee=?self.surplus_fee(), "calculated protocol fee"); + Ok(std::cmp::min(fee_from_surplus, fee_from_volume)) + } + + fn fee_from_surplus( + &self, + sell_amount: eth::U256, + buy_amount: eth::U256, + prices: ClearingPrices, + factor: f64, + ) -> Result { let executed = self.executed().0; let executed_sell_amount = match self.order().side { Side::Buy => { From 0cbbb009657804c748f451acb4d6709c0ba2b07f Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 16 Jan 2024 10:32:32 +0000 Subject: [PATCH 09/24] DB persistence --- crates/autopilot/src/database/fee_policies.rs | 31 +++++++++++++-- .../src/infra/persistence/dto/fee_policy.rs | 39 ++++++++++++++++++- .../src/domain/competition/solution/fee.rs | 9 +++-- crates/orderbook/src/dto/order.rs | 16 ++++++++ 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/crates/autopilot/src/database/fee_policies.rs b/crates/autopilot/src/database/fee_policies.rs index 0796d6ba6c..bd23c98db9 100644 --- a/crates/autopilot/src/database/fee_policies.rs +++ b/crates/autopilot/src/database/fee_policies.rs @@ -9,7 +9,7 @@ pub async fn insert_batch( ) -> Result<(), sqlx::Error> { let mut query_builder = QueryBuilder::new( "INSERT INTO fee_policies (auction_id, order_uid, kind, surplus_factor, \ - max_volume_factor, volume_factor) ", + max_volume_factor, volume_factor, sell_amount, buy_amount) ", ); query_builder.push_values(fee_policies, |mut b, fee_policy| { @@ -18,7 +18,9 @@ pub async fn insert_batch( .push_bind(fee_policy.kind) .push_bind(fee_policy.surplus_factor) .push_bind(fee_policy.max_volume_factor) - .push_bind(fee_policy.volume_factor); + .push_bind(fee_policy.volume_factor) + .push_bind(fee_policy.sell_amount) + .push_bind(fee_policy.buy_amount); }); query_builder.build().execute(ex).await.map(|_| ()) @@ -46,7 +48,7 @@ pub async fn fetch( #[cfg(test)] mod tests { - use {super::*, database::byte_array::ByteArray, sqlx::Connection}; + use {super::*, bigdecimal::BigDecimal, database::byte_array::ByteArray, sqlx::Connection}; #[tokio::test] #[ignore] @@ -66,6 +68,8 @@ mod tests { surplus_factor: Some(0.1), max_volume_factor: Some(1.0), volume_factor: None, + sell_amount: None, + buy_amount: None, }; // surplus fee policy with caps let fee_policy_2 = dto::FeePolicy { @@ -75,6 +79,8 @@ mod tests { surplus_factor: Some(0.2), max_volume_factor: Some(0.05), volume_factor: None, + sell_amount: None, + buy_amount: None, }; // volume based fee policy let fee_policy_3 = dto::FeePolicy { @@ -84,6 +90,19 @@ mod tests { surplus_factor: None, max_volume_factor: None, volume_factor: Some(0.06), + sell_amount: None, + buy_amount: None, + }; + // price improvement fee policy + let fee_policy_4 = dto::FeePolicy { + auction_id, + order_uid, + kind: dto::fee_policy::FeePolicyKind::PriceImprovement, + surplus_factor: Some(0.3), + max_volume_factor: Some(0.07), + volume_factor: None, + sell_amount: Some(BigDecimal::new(10.into(), 3)), + buy_amount: Some(BigDecimal::new(5.into(), 2)), }; insert_batch( &mut db, @@ -91,12 +110,16 @@ mod tests { fee_policy_1.clone(), fee_policy_2.clone(), fee_policy_3.clone(), + fee_policy_4.clone(), ], ) .await .unwrap(); let output = fetch(&mut db, 1, order_uid).await.unwrap(); - assert_eq!(output, vec![fee_policy_1, fee_policy_2, fee_policy_3]); + assert_eq!( + output, + vec![fee_policy_1, fee_policy_2, fee_policy_3, fee_policy_4] + ); } } diff --git a/crates/autopilot/src/infra/persistence/dto/fee_policy.rs b/crates/autopilot/src/infra/persistence/dto/fee_policy.rs index bfbd52ec2e..74d344147f 100644 --- a/crates/autopilot/src/infra/persistence/dto/fee_policy.rs +++ b/crates/autopilot/src/infra/persistence/dto/fee_policy.rs @@ -1,4 +1,8 @@ -use crate::{boundary, domain}; +use { + crate::{boundary, domain}, + bigdecimal::BigDecimal, + number::conversions::{big_decimal_to_u256, u256_to_big_decimal}, +}; #[derive(Debug, Clone, PartialEq, sqlx::FromRow)] pub struct FeePolicy { @@ -8,6 +12,8 @@ pub struct FeePolicy { pub surplus_factor: Option, pub max_volume_factor: Option, pub volume_factor: Option, + pub sell_amount: Option, + pub buy_amount: Option, } impl FeePolicy { @@ -27,6 +33,8 @@ impl FeePolicy { surplus_factor: Some(factor), max_volume_factor: Some(max_volume_factor), volume_factor: None, + sell_amount: None, + buy_amount: None, }, domain::fee::Policy::Volume { factor } => Self { auction_id, @@ -35,6 +43,22 @@ impl FeePolicy { surplus_factor: None, max_volume_factor: None, volume_factor: Some(factor), + sell_amount: None, + buy_amount: None, + }, + domain::fee::Policy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => Self { + auction_id, + order_uid: boundary::database::byte_array::ByteArray(order_uid.0), + kind: FeePolicyKind::Surplus, + surplus_factor: Some(factor), + max_volume_factor: Some(max_volume_factor), + volume_factor: None, + sell_amount: Some(u256_to_big_decimal("e.sell_amount)), + buy_amount: Some(u256_to_big_decimal("e.buy_amount)), }, } } @@ -50,6 +74,18 @@ impl From for domain::fee::Policy { FeePolicyKind::Volume => domain::fee::Policy::Volume { factor: row.volume_factor.expect("missing volume factor"), }, + FeePolicyKind::PriceImprovement => domain::fee::Policy::PriceImprovement { + factor: row.surplus_factor.expect("missing surplus factor"), + max_volume_factor: row.max_volume_factor.expect("missing max volume factor"), + quote: domain::fee::Quote { + sell_amount: big_decimal_to_u256( + &row.sell_amount.expect("missing sell amount"), + ) + .expect("sell amount is not a valid eth::U256"), + buy_amount: big_decimal_to_u256(&row.buy_amount.expect("missing buy amount")) + .expect("buy amount is not a valid eth::U256"), + }, + }, } } } @@ -59,4 +95,5 @@ impl From for domain::fee::Policy { pub enum FeePolicyKind { Surplus, Volume, + PriceImprovement, } diff --git a/crates/driver/src/domain/competition/solution/fee.rs b/crates/driver/src/domain/competition/solution/fee.rs index fcc433caa2..3175ec8b39 100644 --- a/crates/driver/src/domain/competition/solution/fee.rs +++ b/crates/driver/src/domain/competition/solution/fee.rs @@ -101,15 +101,18 @@ impl Fulfillment { } } + /// Computes protocol fee compared to the given reference amounts taken from + /// the order or a quote. fn calculate_fee( &self, - sell_amount: eth::U256, - buy_amount: eth::U256, + reference_sell_amount: eth::U256, + reference_buy_amount: eth::U256, prices: ClearingPrices, factor: f64, max_volume_factor: f64, ) -> Result { - let fee_from_surplus = self.fee_from_surplus(sell_amount, buy_amount, prices, factor)?; + let fee_from_surplus = + self.fee_from_surplus(reference_sell_amount, reference_buy_amount, prices, factor)?; let fee_from_volume = self.fee_from_volume(prices, max_volume_factor)?; // take the smaller of the two tracing::debug!(uid=?self.order().uid, fee_from_surplus=?fee_from_surplus, fee_from_volume=?fee_from_volume, protocol_fee=?(std::cmp::min(fee_from_surplus, fee_from_volume)), executed=?self.executed(), surplus_fee=?self.surplus_fee(), "calculated protocol fee"); diff --git a/crates/orderbook/src/dto/order.rs b/crates/orderbook/src/dto/order.rs index 76fcce13ee..7301abbdb5 100644 --- a/crates/orderbook/src/dto/order.rs +++ b/crates/orderbook/src/dto/order.rs @@ -53,4 +53,20 @@ pub enum FeePolicy { 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(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Quote { + #[serde_as(as = "HexOrDecimalU256")] + pub sell_amount: U256, + #[serde_as(as = "HexOrDecimalU256")] + pub buy_amount: U256, } From 6bc3de6cc361247483dd8d91982cee17eb48b703 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 16 Jan 2024 10:40:34 +0000 Subject: [PATCH 10/24] Orderbook OpenAPI --- crates/orderbook/openapi.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/orderbook/openapi.yml b/crates/orderbook/openapi.yml index 9e928ecb66..eb188e65ce 100644 --- a/crates/orderbook/openapi.yml +++ b/crates/orderbook/openapi.yml @@ -1569,6 +1569,13 @@ components: items: $ref: "#/components/schemas/CallData" description: The call data to be used for the interaction. + Quote: + type: object + properties: + sell_amount: + type: number + buy_amount: + type: number Surplus: description: The protocol fee is taken as a percent of the surplus. type: object @@ -1580,6 +1587,20 @@ components: required: - factor - max_volume_factor + PriceImprovement: + description: The protocol fee is taken as a percent of the surplus which is based on a quote. + type: object + properties: + factor: + type: number + max_volume_factor: + type: number + quote: + $ref: "#/components/schemas/Quote" + required: + - factor + - max_volume_factor + - quote Volume: description: The protocol fee is taken as a percent of the order volume. type: object @@ -1592,4 +1613,5 @@ components: description: Defines the ways to calculate the protocol fee. oneOf: - $ref: '#/components/schemas/Surplus' + - $ref: '#/components/schemas/PriceImprovement' - $ref: '#/components/schemas/Volume' From 5d4e32dd82dd9e02a2cea52923c5f9ae42c97748 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 16 Jan 2024 10:44:44 +0000 Subject: [PATCH 11/24] Missing files --- database/README.md | 20 ++++++++++--------- ..._add_price_improvement_fee_policy_kind.sql | 7 +++++++ 2 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 database/sql/V058__add_price_improvement_fee_policy_kind.sql diff --git a/database/README.md b/database/README.md index f92a9f811e..40656ec4d5 100644 --- a/database/README.md +++ b/database/README.md @@ -252,15 +252,17 @@ Indexes: Contains all relevant data of fee policies applied to orders during auctions. -Column | Type | Nullable | Details ---------------------------|------------------------------|----------|-------- - auction_id | bigint | not null | unique identifier for the auction - order_uid | bytea | not null | 56 bytes identifier linking to the order in the `orders` table - application_order | serial | not null | the order in which the fee policies are inserted and applied - kind | [PolicyKind](#policykind) | not null | type of the fee policy, defined in the PolicyKind enum - surplus_factor | double precision | | percentage of the surplus for fee calculation; value is between 0 and 1 - max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1 - volume_factor | double precision | | fee percentage of the order volume; value is between 0 and 1 +Column | Type | Nullable | Details +--------------------------|---------------------------|----------|-------- + auction_id | bigint | not null | unique identifier for the auction + order_uid | bytea | not null | 56 bytes identifier linking to the order in the `orders` table + application_order | serial | not null | the order in which the fee policies are inserted and applied + kind | [PolicyKind](#policykind) | not null | type of the fee policy, defined in the PolicyKind enum + surplus_factor | double precision | | percentage of the surplus for fee calculation; value is between 0 and 1 + max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1 + volume_factor | double precision | | fee percentage of the order volume; value is between 0 and 1 + sell_amount | numeric | | quote's sell amount + buy_amount | numeric | | quote's buy amount Indexes: - PRIMARY KEY: composite key(`auction_id`, `order_uid`, `application_order`) diff --git a/database/sql/V058__add_price_improvement_fee_policy_kind.sql b/database/sql/V058__add_price_improvement_fee_policy_kind.sql new file mode 100644 index 0000000000..64809e3875 --- /dev/null +++ b/database/sql/V058__add_price_improvement_fee_policy_kind.sql @@ -0,0 +1,7 @@ +ALTER TYPE PolicyKind ADD VALUE 'priceimprovement'; + +ALTER TABLE fee_policies + -- quote's sell amount + ADD COLUMN sell_amount numeric(78,0), + -- quote's buy amount + ADD COLUMN buy_amount numeric(78,0); From b4e4b5a35e04dc003f27b5c0edb70adfa4551db2 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 16 Jan 2024 10:58:27 +0000 Subject: [PATCH 12/24] Test fix --- crates/autopilot/src/database/fee_policies.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/autopilot/src/database/fee_policies.rs b/crates/autopilot/src/database/fee_policies.rs index bd23c98db9..81a8c613a2 100644 --- a/crates/autopilot/src/database/fee_policies.rs +++ b/crates/autopilot/src/database/fee_policies.rs @@ -101,8 +101,8 @@ mod tests { surplus_factor: Some(0.3), max_volume_factor: Some(0.07), volume_factor: None, - sell_amount: Some(BigDecimal::new(10.into(), 3)), - buy_amount: Some(BigDecimal::new(5.into(), 2)), + sell_amount: Some(BigDecimal::new(100.into(), 1)), + buy_amount: Some(BigDecimal::new(200.into(), 1)), }; insert_batch( &mut db, From 69a229704daee39cd6d1a2ca323a44891499fd4e Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 17 Jan 2024 19:41:46 +0000 Subject: [PATCH 13/24] Warn about skipped order --- crates/autopilot/src/arguments.rs | 20 ++++++++++++-------- crates/autopilot/src/domain/fee/mod.rs | 20 ++++++++++++-------- crates/autopilot/src/solvable_orders.rs | 11 ++++++++--- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index d988606341..399934df17 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -1,5 +1,6 @@ use { crate::{domain, infra}, + anyhow::anyhow, primitive_types::{H160, U256}, shared::{ arguments::{display_list, display_option, ExternalSolver}, @@ -368,24 +369,27 @@ pub struct FeePolicy { } impl FeePolicy { - pub fn to_domain(&self, quote: Option<&domain::Quote>) -> Option { + pub fn to_domain(&self, quote: Option<&domain::Quote>) -> anyhow::Result { match self.fee_policy_kind { FeePolicyKind::Surplus { factor, max_volume_factor, - } => Some(domain::fee::Policy::Surplus { + } => Ok(domain::fee::Policy::Surplus { factor, max_volume_factor, }), FeePolicyKind::PriceImprovement { factor, max_volume_factor, - } => quote.map(|q| domain::fee::Policy::PriceImprovement { - factor, - max_volume_factor, - quote: q.clone().into(), - }), - FeePolicyKind::Volume { factor } => Some(domain::fee::Policy::Volume { factor }), + } => { + let quote = quote.ok_or(anyhow!("missing quote for price improvement policy"))?; + Ok(domain::fee::Policy::PriceImprovement { + factor, + max_volume_factor, + quote: quote.clone().into(), + }) + } + FeePolicyKind::Volume { factor } => Ok(domain::fee::Policy::Volume { factor }), } } } diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index b97018f69e..216c830497 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -29,25 +29,29 @@ impl ProtocolFee { } /// Get policies for order. - pub fn get(&self, order: &boundary::Order, quote: Option<&domain::Quote>) -> Vec { + pub fn get( + &self, + order: &boundary::Order, + quote: Option<&domain::Quote>, + ) -> anyhow::Result> { match order.metadata.class { boundary::OrderClass::Market => { if self.fee_policy_skip_market_orders { - vec![] + Ok(vec![]) } else { - self.policy.to_domain(quote).into_iter().collect() + self.policy.to_domain(quote).map(|p| vec![p]) } } - boundary::OrderClass::Liquidity => vec![], + boundary::OrderClass::Liquidity => Ok(vec![]), boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return self.policy.to_domain(quote).into_iter().collect(); + return self.policy.to_domain(quote).map(|p| vec![p]); } // if the quote is missing, we can't determine if the order is outside the // market price so we protect the user and not charge a fee let Some(quote) = quote else { - return vec![]; + return Ok(vec![]); }; if boundary::is_order_outside_market_price( @@ -56,9 +60,9 @@ impl ProtocolFee { "e.buy_amount, "e.sell_amount, ) { - self.policy.to_domain(Some(quote)).into_iter().collect() + self.policy.to_domain(Some(quote)).map(|p| vec![p]) } else { - vec![] + Ok(vec![]) } } } diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 9a76d11ff8..72711aacdb 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -238,10 +238,15 @@ impl SolvableOrdersCache { latest_settlement_block: db_solvable_orders.latest_settlement_block, orders: orders .into_iter() - .map(|order| { + .filter_map(|order| { let quote = db_solvable_orders.quotes.get(&order.metadata.uid.into()); - let protocol_fees = self.protocol_fee.get(&order, quote); - boundary::order::to_domain(order, protocol_fees) + match self.protocol_fee.get(&order, quote) { + Ok(protocol_fees) => Some(boundary::order::to_domain(order, protocol_fees)), + Err(err) => { + tracing::warn!(order_uid = %order.metadata.uid, ?err, "order is skipped, failed to compute protocol fees due to error"); + None + } + } }) .collect(), prices, From a1444b184156d041fb78e383349861010ed2581a Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 17 Jan 2024 20:02:49 +0000 Subject: [PATCH 14/24] PolicyRaw dto --- crates/autopilot/src/arguments.rs | 21 +++++------- crates/autopilot/src/domain/fee/mod.rs | 45 ++++++++++++++++++++++---- crates/autopilot/src/run.rs | 2 +- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 399934df17..a4ef1e089d 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -1,6 +1,5 @@ use { crate::{domain, infra}, - anyhow::anyhow, primitive_types::{H160, U256}, shared::{ arguments::{display_list, display_option, ExternalSolver}, @@ -369,27 +368,23 @@ pub struct FeePolicy { } impl FeePolicy { - pub fn to_domain(&self, quote: Option<&domain::Quote>) -> anyhow::Result { + pub fn to_domain_raw(&self) -> domain::fee::PolicyRaw { match self.fee_policy_kind { FeePolicyKind::Surplus { factor, max_volume_factor, - } => Ok(domain::fee::Policy::Surplus { + } => domain::fee::PolicyRaw::Surplus { factor, max_volume_factor, - }), + }, FeePolicyKind::PriceImprovement { factor, max_volume_factor, - } => { - let quote = quote.ok_or(anyhow!("missing quote for price improvement policy"))?; - Ok(domain::fee::Policy::PriceImprovement { - factor, - max_volume_factor, - quote: quote.clone().into(), - }) - } - FeePolicyKind::Volume { factor } => Ok(domain::fee::Policy::Volume { factor }), + } => domain::fee::PolicyRaw::PriceImprovement { + factor, + max_volume_factor, + }, + FeePolicyKind::Volume { factor } => domain::fee::PolicyRaw::Volume { factor }, } } } diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 216c830497..d797ca5c54 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -6,22 +6,22 @@ use { crate::{ - arguments, boundary::{self}, domain, }, + anyhow::anyhow, primitive_types::U256, }; /// Constructs fee policies based on the current configuration. #[derive(Debug)] pub struct ProtocolFee { - policy: arguments::FeePolicy, + policy: PolicyRaw, fee_policy_skip_market_orders: bool, } impl ProtocolFee { - pub fn new(policy: arguments::FeePolicy, fee_policy_skip_market_orders: bool) -> Self { + pub fn new(policy: PolicyRaw, fee_policy_skip_market_orders: bool) -> Self { Self { policy, fee_policy_skip_market_orders, @@ -39,13 +39,13 @@ impl ProtocolFee { if self.fee_policy_skip_market_orders { Ok(vec![]) } else { - self.policy.to_domain(quote).map(|p| vec![p]) + self.policy.try_with_quote(quote).map(|p| vec![p]) } } boundary::OrderClass::Liquidity => Ok(vec![]), boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return self.policy.to_domain(quote).map(|p| vec![p]); + return self.policy.try_with_quote(quote).map(|p| vec![p]); } // if the quote is missing, we can't determine if the order is outside the @@ -60,7 +60,7 @@ impl ProtocolFee { "e.buy_amount, "e.sell_amount, ) { - self.policy.to_domain(Some(quote)).map(|p| vec![p]) + self.policy.try_with_quote(Some(quote)).map(|p| vec![p]) } else { Ok(vec![]) } @@ -101,6 +101,39 @@ pub enum Policy { }, } +#[derive(Debug)] +pub enum PolicyRaw { + Surplus { factor: f64, max_volume_factor: f64 }, + PriceImprovement { factor: f64, max_volume_factor: f64 }, + Volume { factor: f64 }, +} + +impl PolicyRaw { + pub fn try_with_quote(&self, quote: Option<&domain::Quote>) -> anyhow::Result { + match self { + PolicyRaw::Surplus { + factor, + max_volume_factor, + } => Ok(Policy::Surplus { + factor: *factor, + max_volume_factor: *max_volume_factor, + }), + PolicyRaw::PriceImprovement { + factor, + max_volume_factor, + } => { + let quote = quote.ok_or(anyhow!("missing quote for price improvement policy"))?; + Ok(Policy::PriceImprovement { + factor: *factor, + max_volume_factor: *max_volume_factor, + quote: quote.clone().into(), + }) + } + PolicyRaw::Volume { factor } => Ok(Policy::Volume { factor: *factor }), + } + } +} + #[derive(Debug, Copy, Clone, PartialEq)] pub struct Quote { pub sell_amount: U256, diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index a7236a1bff..9db251729e 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -558,7 +558,7 @@ pub async fn run(args: Arguments) { .try_into() .expect("limit order price factor can't be converted to BigDecimal"), domain::ProtocolFee::new( - args.fee_policy.clone(), + args.fee_policy.clone().to_domain_raw(), args.fee_policy.fee_policy_skip_market_orders, ), ); From 7f1a7c5874c8482600c5ed88edb2b281af5d83f4 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 23 Jan 2024 12:28:13 +0000 Subject: [PATCH 15/24] Make quote mandatory --- crates/autopilot/src/domain/fee/mod.rs | 46 +++++++++---------------- crates/autopilot/src/solvable_orders.rs | 13 ++++--- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index d797ca5c54..276075f9e5 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -9,7 +9,6 @@ use { boundary::{self}, domain, }, - anyhow::anyhow, primitive_types::U256, }; @@ -29,40 +28,30 @@ impl ProtocolFee { } /// Get policies for order. - pub fn get( - &self, - order: &boundary::Order, - quote: Option<&domain::Quote>, - ) -> anyhow::Result> { + pub fn get(&self, order: &boundary::Order, quote: &domain::Quote) -> Vec { match order.metadata.class { boundary::OrderClass::Market => { if self.fee_policy_skip_market_orders { - Ok(vec![]) + vec![] } else { - self.policy.try_with_quote(quote).map(|p| vec![p]) + vec![self.policy.to_domain(quote)] } } - boundary::OrderClass::Liquidity => Ok(vec![]), + boundary::OrderClass::Liquidity => vec![], boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return self.policy.try_with_quote(quote).map(|p| vec![p]); + return vec![self.policy.to_domain(quote)]; } - // if the quote is missing, we can't determine if the order is outside the - // market price so we protect the user and not charge a fee - let Some(quote) = quote else { - return Ok(vec![]); - }; - if boundary::is_order_outside_market_price( &order.data.sell_amount, &order.data.buy_amount, "e.buy_amount, "e.sell_amount, ) { - self.policy.try_with_quote(Some(quote)).map(|p| vec![p]) + vec![self.policy.to_domain(quote)] } else { - Ok(vec![]) + vec![] } } } @@ -109,27 +98,24 @@ pub enum PolicyRaw { } impl PolicyRaw { - pub fn try_with_quote(&self, quote: Option<&domain::Quote>) -> anyhow::Result { + pub fn to_domain(&self, quote: &domain::Quote) -> Policy { match self { PolicyRaw::Surplus { factor, max_volume_factor, - } => Ok(Policy::Surplus { + } => Policy::Surplus { factor: *factor, max_volume_factor: *max_volume_factor, - }), + }, PolicyRaw::PriceImprovement { factor, max_volume_factor, - } => { - let quote = quote.ok_or(anyhow!("missing quote for price improvement policy"))?; - Ok(Policy::PriceImprovement { - factor: *factor, - max_volume_factor: *max_volume_factor, - quote: quote.clone().into(), - }) - } - PolicyRaw::Volume { factor } => Ok(Policy::Volume { factor: *factor }), + } => Policy::PriceImprovement { + factor: *factor, + max_volume_factor: *max_volume_factor, + quote: quote.clone().into(), + }, + PolicyRaw::Volume { factor } => Policy::Volume { factor: *factor }, } } } diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 72711aacdb..17873565ee 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -239,13 +239,12 @@ impl SolvableOrdersCache { orders: orders .into_iter() .filter_map(|order| { - let quote = db_solvable_orders.quotes.get(&order.metadata.uid.into()); - match self.protocol_fee.get(&order, quote) { - Ok(protocol_fees) => Some(boundary::order::to_domain(order, protocol_fees)), - Err(err) => { - tracing::warn!(order_uid = %order.metadata.uid, ?err, "order is skipped, failed to compute protocol fees due to error"); - None - } + if let Some(quote) = db_solvable_orders.quotes.get(&order.metadata.uid.into()) { + let protocol_fees = self.protocol_fee.get(&order, quote); + Some(boundary::order::to_domain(order, protocol_fees)) + } else { + tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing"); + None } }) .collect(), From 50520ae67bd71411e1bc61d7d425781eb1c7f6ba Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 23 Jan 2024 12:36:50 +0000 Subject: [PATCH 16/24] Naming and docs --- crates/autopilot/src/database/fee_policies.rs | 22 ++++++++--------- crates/autopilot/src/domain/fee/mod.rs | 3 +++ .../src/infra/persistence/dto/fee_policy.rs | 24 ++++++++++--------- crates/driver/openapi.yml | 2 +- .../src/domain/competition/order/fees.rs | 4 +++- ..._add_price_improvement_fee_policy_kind.sql | 4 ++-- 6 files changed, 33 insertions(+), 26 deletions(-) diff --git a/crates/autopilot/src/database/fee_policies.rs b/crates/autopilot/src/database/fee_policies.rs index 81a8c613a2..a06259e353 100644 --- a/crates/autopilot/src/database/fee_policies.rs +++ b/crates/autopilot/src/database/fee_policies.rs @@ -9,7 +9,7 @@ pub async fn insert_batch( ) -> Result<(), sqlx::Error> { let mut query_builder = QueryBuilder::new( "INSERT INTO fee_policies (auction_id, order_uid, kind, surplus_factor, \ - max_volume_factor, volume_factor, sell_amount, buy_amount) ", + max_volume_factor, volume_factor, quote_sell_amount, quote_buy_amount) ", ); query_builder.push_values(fee_policies, |mut b, fee_policy| { @@ -19,8 +19,8 @@ pub async fn insert_batch( .push_bind(fee_policy.surplus_factor) .push_bind(fee_policy.max_volume_factor) .push_bind(fee_policy.volume_factor) - .push_bind(fee_policy.sell_amount) - .push_bind(fee_policy.buy_amount); + .push_bind(fee_policy.quote_sell_amount) + .push_bind(fee_policy.quote_buy_amount); }); query_builder.build().execute(ex).await.map(|_| ()) @@ -68,8 +68,8 @@ mod tests { surplus_factor: Some(0.1), max_volume_factor: Some(1.0), volume_factor: None, - sell_amount: None, - buy_amount: None, + quote_sell_amount: None, + quote_buy_amount: None, }; // surplus fee policy with caps let fee_policy_2 = dto::FeePolicy { @@ -79,8 +79,8 @@ mod tests { surplus_factor: Some(0.2), max_volume_factor: Some(0.05), volume_factor: None, - sell_amount: None, - buy_amount: None, + quote_sell_amount: None, + quote_buy_amount: None, }; // volume based fee policy let fee_policy_3 = dto::FeePolicy { @@ -90,8 +90,8 @@ mod tests { surplus_factor: None, max_volume_factor: None, volume_factor: Some(0.06), - sell_amount: None, - buy_amount: None, + quote_sell_amount: None, + quote_buy_amount: None, }; // price improvement fee policy let fee_policy_4 = dto::FeePolicy { @@ -101,8 +101,8 @@ mod tests { surplus_factor: Some(0.3), max_volume_factor: Some(0.07), volume_factor: None, - sell_amount: Some(BigDecimal::new(100.into(), 1)), - buy_amount: Some(BigDecimal::new(200.into(), 1)), + quote_sell_amount: Some(BigDecimal::new(100.into(), 1)), + quote_buy_amount: Some(BigDecimal::new(200.into(), 1)), }; insert_batch( &mut db, diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 276075f9e5..80c4427b17 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -75,6 +75,9 @@ pub enum Policy { /// Cap protocol fee with a percentage of the order's volume. max_volume_factor: f64, }, + /// A price improvement corresponds to a situation where the order is + /// executed at a better price than the top quote. The protocol fee in such + /// case is calculated from a cut of this price improvement. PriceImprovement { factor: f64, max_volume_factor: f64, diff --git a/crates/autopilot/src/infra/persistence/dto/fee_policy.rs b/crates/autopilot/src/infra/persistence/dto/fee_policy.rs index 74d344147f..16c0e3e3dc 100644 --- a/crates/autopilot/src/infra/persistence/dto/fee_policy.rs +++ b/crates/autopilot/src/infra/persistence/dto/fee_policy.rs @@ -12,8 +12,8 @@ pub struct FeePolicy { pub surplus_factor: Option, pub max_volume_factor: Option, pub volume_factor: Option, - pub sell_amount: Option, - pub buy_amount: Option, + pub quote_sell_amount: Option, + pub quote_buy_amount: Option, } impl FeePolicy { @@ -33,8 +33,8 @@ impl FeePolicy { surplus_factor: Some(factor), max_volume_factor: Some(max_volume_factor), volume_factor: None, - sell_amount: None, - buy_amount: None, + quote_sell_amount: None, + quote_buy_amount: None, }, domain::fee::Policy::Volume { factor } => Self { auction_id, @@ -43,8 +43,8 @@ impl FeePolicy { surplus_factor: None, max_volume_factor: None, volume_factor: Some(factor), - sell_amount: None, - buy_amount: None, + quote_sell_amount: None, + quote_buy_amount: None, }, domain::fee::Policy::PriceImprovement { factor, @@ -57,8 +57,8 @@ impl FeePolicy { surplus_factor: Some(factor), max_volume_factor: Some(max_volume_factor), volume_factor: None, - sell_amount: Some(u256_to_big_decimal("e.sell_amount)), - buy_amount: Some(u256_to_big_decimal("e.buy_amount)), + quote_sell_amount: Some(u256_to_big_decimal("e.sell_amount)), + quote_buy_amount: Some(u256_to_big_decimal("e.buy_amount)), }, } } @@ -79,11 +79,13 @@ impl From for domain::fee::Policy { max_volume_factor: row.max_volume_factor.expect("missing max volume factor"), quote: domain::fee::Quote { sell_amount: big_decimal_to_u256( - &row.sell_amount.expect("missing sell amount"), + &row.quote_sell_amount.expect("missing sell amount"), ) .expect("sell amount is not a valid eth::U256"), - buy_amount: big_decimal_to_u256(&row.buy_amount.expect("missing buy amount")) - .expect("buy amount is not a valid eth::U256"), + buy_amount: big_decimal_to_u256( + &row.quote_buy_amount.expect("missing buy amount"), + ) + .expect("buy amount is not a valid eth::U256"), }, }, } diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 7c10b09862..cbdaa68169 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -439,7 +439,7 @@ components: example: 0.5 PriceImprovement: description: | - If the order was out of market during creation, pay the protocol a factor of the difference. + A cut from the price improvement over the best quote is taken as a protocol fee. type: object properties: kind: diff --git a/crates/driver/src/domain/competition/order/fees.rs b/crates/driver/src/domain/competition/order/fees.rs index 09a09bf34c..d745b5a7d9 100644 --- a/crates/driver/src/domain/competition/order/fees.rs +++ b/crates/driver/src/domain/competition/order/fees.rs @@ -17,7 +17,9 @@ pub enum FeePolicy { /// Cap protocol fee with a percentage of the order's volume. max_volume_factor: f64, }, - /// todo: add some meaningful description + /// A price improvement corresponds to a situation where the order is + /// executed at a better price than the top quote. The protocol fee in such + /// case is calculated from a cut of this price improvement. PriceImprovement { factor: f64, max_volume_factor: f64, diff --git a/database/sql/V058__add_price_improvement_fee_policy_kind.sql b/database/sql/V058__add_price_improvement_fee_policy_kind.sql index 64809e3875..7c9f130118 100644 --- a/database/sql/V058__add_price_improvement_fee_policy_kind.sql +++ b/database/sql/V058__add_price_improvement_fee_policy_kind.sql @@ -2,6 +2,6 @@ ALTER TYPE PolicyKind ADD VALUE 'priceimprovement'; ALTER TABLE fee_policies -- quote's sell amount - ADD COLUMN sell_amount numeric(78,0), + ADD COLUMN quote_sell_amount numeric(78,0), -- quote's buy amount - ADD COLUMN buy_amount numeric(78,0); + ADD COLUMN quote_buy_amount numeric(78,0); From eb08cf45305d7ff0ba3bcd0a6522d1e85e4e1207 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 24 Jan 2024 18:52:30 +0000 Subject: [PATCH 17/24] PolicyRaw -> PolicyBuilder --- crates/autopilot/src/arguments.rs | 8 ++++---- crates/autopilot/src/domain/fee/mod.rs | 26 +++++++++++++------------- crates/autopilot/src/run.rs | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index a4ef1e089d..748ce9d845 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -368,23 +368,23 @@ pub struct FeePolicy { } impl FeePolicy { - pub fn to_domain_raw(&self) -> domain::fee::PolicyRaw { + pub fn to_policy_builder(&self) -> domain::fee::PolicyBuilder { match self.fee_policy_kind { FeePolicyKind::Surplus { factor, max_volume_factor, - } => domain::fee::PolicyRaw::Surplus { + } => domain::fee::PolicyBuilder::Surplus { factor, max_volume_factor, }, FeePolicyKind::PriceImprovement { factor, max_volume_factor, - } => domain::fee::PolicyRaw::PriceImprovement { + } => domain::fee::PolicyBuilder::PriceImprovement { factor, max_volume_factor, }, - FeePolicyKind::Volume { factor } => domain::fee::PolicyRaw::Volume { factor }, + FeePolicyKind::Volume { factor } => domain::fee::PolicyBuilder::Volume { factor }, } } } diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 6a0d92155c..54d29fae80 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -15,14 +15,14 @@ use { /// Constructs fee policies based on the current configuration. #[derive(Debug)] pub struct ProtocolFee { - policy: PolicyRaw, + policy_builder: PolicyBuilder, fee_policy_skip_market_orders: bool, } impl ProtocolFee { - pub fn new(policy: PolicyRaw, fee_policy_skip_market_orders: bool) -> Self { + pub fn new(policy_builder: PolicyBuilder, fee_policy_skip_market_orders: bool) -> Self { Self { - policy, + policy_builder, fee_policy_skip_market_orders, } } @@ -34,16 +34,16 @@ impl ProtocolFee { if self.fee_policy_skip_market_orders { vec![] } else { - vec![self.policy.to_domain(quote)] + vec![self.policy_builder.build_with(quote)] } } boundary::OrderClass::Liquidity => vec![], boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return vec![self.policy.to_domain(quote)]; + return vec![self.policy_builder.build_with(quote)]; } - tracing::debug!(?order.metadata.uid, ?self.policy, ?order.data.sell_amount, ?order.data.buy_amount, ?quote, "checking if order is outside market price"); + tracing::debug!(?order.metadata.uid, ?self.policy_builder, ?order.data.sell_amount, ?order.data.buy_amount, ?quote, "checking if order is outside market price"); if boundary::is_order_outside_market_price( &order.data.sell_amount, &order.data.buy_amount, @@ -52,7 +52,7 @@ impl ProtocolFee { "e.sell_amount, "e.fee, ) { - vec![self.policy.to_domain(quote)] + vec![self.policy_builder.build_with(quote)] } else { vec![] } @@ -97,23 +97,23 @@ pub enum Policy { } #[derive(Debug)] -pub enum PolicyRaw { +pub enum PolicyBuilder { Surplus { factor: f64, max_volume_factor: f64 }, PriceImprovement { factor: f64, max_volume_factor: f64 }, Volume { factor: f64 }, } -impl PolicyRaw { - pub fn to_domain(&self, quote: &domain::Quote) -> Policy { +impl PolicyBuilder { + pub fn build_with(&self, quote: &domain::Quote) -> Policy { match self { - PolicyRaw::Surplus { + PolicyBuilder::Surplus { factor, max_volume_factor, } => Policy::Surplus { factor: *factor, max_volume_factor: *max_volume_factor, }, - PolicyRaw::PriceImprovement { + PolicyBuilder::PriceImprovement { factor, max_volume_factor, } => Policy::PriceImprovement { @@ -121,7 +121,7 @@ impl PolicyRaw { max_volume_factor: *max_volume_factor, quote: quote.clone().into(), }, - PolicyRaw::Volume { factor } => Policy::Volume { factor: *factor }, + PolicyBuilder::Volume { factor } => Policy::Volume { factor: *factor }, } } } diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index c9713ea494..3dab16a7f7 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -562,7 +562,7 @@ pub async fn run(args: Arguments) { .try_into() .expect("limit order price factor can't be converted to BigDecimal"), domain::ProtocolFee::new( - args.fee_policy.clone().to_domain_raw(), + args.fee_policy.clone().to_policy_builder(), args.fee_policy.fee_policy_skip_market_orders, ), ); From f629e6138270ce124f3cbd043d3d2008012ec4ca Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 24 Jan 2024 19:01:30 +0000 Subject: [PATCH 18/24] Fee calculation fix --- crates/driver/src/domain/competition/solution/fee.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/fee.rs b/crates/driver/src/domain/competition/solution/fee.rs index 3175ec8b39..644195f419 100644 --- a/crates/driver/src/domain/competition/solution/fee.rs +++ b/crates/driver/src/domain/competition/solution/fee.rs @@ -89,13 +89,11 @@ impl Fulfillment { factor, max_volume_factor, quote, - }) => self.calculate_fee( - quote.sell_amount, - quote.buy_amount, - prices, - *factor, - *max_volume_factor, - ), + }) => { + let sell_amount = quote.sell_amount.max(self.order().sell.amount.0); + let buy_amount = quote.buy_amount.min(self.order().buy.amount.0); + self.calculate_fee(sell_amount, buy_amount, prices, *factor, *max_volume_factor) + } Some(FeePolicy::Volume { factor }) => self.fee_from_volume(prices, *factor), None => Ok(0.into()), } From e3a27a09663f608ff3f9fc5845a399c00b7cc100 Mon Sep 17 00:00:00 2001 From: ilya Date: Wed, 24 Jan 2024 19:31:23 +0000 Subject: [PATCH 19/24] Construct domain::Order inside the ProtocolFee --- crates/autopilot/src/domain/fee/mod.rs | 40 +++++++++++++------------ crates/autopilot/src/solvable_orders.rs | 5 ++-- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 54d29fae80..48c98407ab 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -20,16 +20,17 @@ pub struct ProtocolFee { } impl ProtocolFee { - pub fn new(policy_builder: PolicyBuilder, fee_policy_skip_market_orders: bool) -> Self { + pub fn new(policy: PolicyBuilder, fee_policy_skip_market_orders: bool) -> Self { Self { - policy_builder, + policy_builder: policy, fee_policy_skip_market_orders, } } - /// Get policies for order. - pub fn get(&self, order: &boundary::Order, quote: &domain::Quote) -> Vec { - match order.metadata.class { + /// Converts an order from the boundary layer to the domain layer, applying + /// protocol fees if necessary. + pub fn to_order(&self, order: boundary::Order, quote: &domain::Quote) -> domain::Order { + let protocol_fees = match order.metadata.class { boundary::OrderClass::Market => { if self.fee_policy_skip_market_orders { vec![] @@ -40,24 +41,25 @@ impl ProtocolFee { boundary::OrderClass::Liquidity => vec![], boundary::OrderClass::Limit => { if !self.fee_policy_skip_market_orders { - return vec![self.policy_builder.build_with(quote)]; - } - - tracing::debug!(?order.metadata.uid, ?self.policy_builder, ?order.data.sell_amount, ?order.data.buy_amount, ?quote, "checking if order is outside market price"); - if boundary::is_order_outside_market_price( - &order.data.sell_amount, - &order.data.buy_amount, - &order.data.fee_amount, - "e.buy_amount, - "e.sell_amount, - "e.fee, - ) { vec![self.policy_builder.build_with(quote)] } else { - vec![] + tracing::debug!(?order.metadata.uid, ?self.policy_builder, ?order.data.sell_amount, ?order.data.buy_amount, ?quote, "checking if order is outside market price"); + if boundary::is_order_outside_market_price( + &order.data.sell_amount, + &order.data.buy_amount, + &order.data.fee_amount, + "e.buy_amount, + "e.sell_amount, + "e.fee, + ) { + vec![self.policy_builder.build_with(quote)] + } else { + vec![] + } } } - } + }; + boundary::order::to_domain(order, protocol_fees) } } diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index b2606754fc..02f7884ba7 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -1,5 +1,5 @@ use { - crate::{boundary, database::Postgres, domain}, + crate::{database::Postgres, domain}, anyhow::Result, bigdecimal::BigDecimal, database::order_events::OrderEventLabel, @@ -237,8 +237,7 @@ impl SolvableOrdersCache { .into_iter() .filter_map(|order| { if let Some(quote) = db_solvable_orders.quotes.get(&order.metadata.uid.into()) { - let protocol_fees = self.protocol_fee.get(&order, quote); - Some(boundary::order::to_domain(order, protocol_fees)) + Some(self.protocol_fee.to_order(order, quote)) } else { tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing"); None From b48ecdcee9cd8741deea502b63778a4c8b8a8c83 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 29 Jan 2024 19:34:49 +0000 Subject: [PATCH 20/24] Imports --- crates/autopilot/src/solvable_orders.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 8086bbeeb1..456abf1060 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -1,5 +1,5 @@ use { - crate::{boundary, database::Postgres, domain, infra}, + crate::{domain, infra}, anyhow::Result, bigdecimal::BigDecimal, database::order_events::OrderEventLabel, From c582f74750f4869445385c1bcdd153c12cc2b0c3 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 6 Feb 2024 15:32:06 +0000 Subject: [PATCH 21/24] Fix after merge --- crates/autopilot/src/domain/fee/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 41664ff0f1..13b06976a5 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -48,12 +48,12 @@ impl ProtocolFee { buy: order.data.buy_amount, fee: order.data.fee_amount, }; - let quote = boundary::Amounts { + let quote_ = boundary::Amounts { sell: quote.sell_amount, buy: quote.buy_amount, fee: quote.fee, }; - if boundary::is_order_outside_market_price(&order_, "e) { + if boundary::is_order_outside_market_price(&order_, "e_) { vec![self.policy_builder.build_with(quote)] } else { vec![] From 8ca5b050da8f1e061b332f14fb359f5c545d28e5 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 6 Feb 2024 15:33:43 +0000 Subject: [PATCH 22/24] Update migration version --- ...y_kind.sql => V062__add_price_improvement_fee_policy_kind.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename database/sql/{V058__add_price_improvement_fee_policy_kind.sql => V062__add_price_improvement_fee_policy_kind.sql} (100%) diff --git a/database/sql/V058__add_price_improvement_fee_policy_kind.sql b/database/sql/V062__add_price_improvement_fee_policy_kind.sql similarity index 100% rename from database/sql/V058__add_price_improvement_fee_policy_kind.sql rename to database/sql/V062__add_price_improvement_fee_policy_kind.sql From ba9a39948993ec6c6e0bbb39ae724c9b72579daf Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 6 Feb 2024 16:56:04 +0000 Subject: [PATCH 23/24] e2e --- crates/autopilot/src/arguments.rs | 16 +++ crates/e2e/tests/e2e/protocol_fee.rs | 149 +++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 748ce9d845..e9edc5b274 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -423,6 +423,22 @@ impl FromStr for FeePolicyKind { max_volume_factor, }) } + "price-improvement" => { + let factor = parts + .next() + .ok_or("missing price-improvement factor")? + .parse::() + .map_err(|e| format!("invalid price-improvement factor: {}", e))?; + let max_volume_factor = parts + .next() + .ok_or("missing price-improvement max volume factor")? + .parse::() + .map_err(|e| format!("invalid price-improvement max volume factor: {}", e))?; + Ok(Self::PriceImprovement { + factor, + max_volume_factor, + }) + } "volume" => { let factor = parts .next() diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index 0016077ccf..122b01b95a 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -49,6 +49,30 @@ async fn local_node_volume_fee_buy_order() { run_test(volume_fee_buy_order_test).await; } +#[tokio::test] +#[ignore] +async fn local_node_price_improvement_fee_sell_order() { + run_test(price_improvement_fee_sell_order_test).await; +} + +#[tokio::test] +#[ignore] +async fn local_node_price_improvement_fee_sell_order_capped() { + run_test(price_improvement_fee_sell_order_capped_test).await; +} + +#[tokio::test] +#[ignore] +async fn local_node_price_improvement_fee_buy_order() { + run_test(price_improvement_fee_buy_order_test).await; +} + +#[tokio::test] +#[ignore] +async fn local_node_price_improvement_fee_buy_order_capped() { + run_test(price_improvement_fee_buy_order_capped_test).await; +} + async fn surplus_fee_sell_order_test(web3: Web3) { let fee_policy = FeePolicyKind::Surplus { factor: 0.3, @@ -206,6 +230,120 @@ async fn volume_fee_buy_order_test(web3: Web3) { .await; } +async fn price_improvement_fee_sell_order_test(web3: Web3) { + let fee_policy = FeePolicyKind::PriceImprovement { + factor: 0.3, + max_volume_factor: 1.0, + }; + // Without protocol fee: + // Expected execution is 10000000000000000000 GNO for + // 9871415430342266811 DAI, with executed_surplus_fee = 167058994203399 GNO + // + // With protocol fee: + // surplus [DAI] = 9871415430342266811 DAI - 5000000000000000000 DAI = + // 4871415430342266811 DAI + // + // protocol fee = 0.3*surplus = 1461424629102680043 DAI = + // 1461424629102680043 DAI / 9871415430342266811 * + // (10000000000000000000 - 167058994203399) = 1480436341679873337 GNO + // + // final execution is 10000000000000000000 GNO for 8409990801239586768 DAI, with + // executed_surplus_fee = 1480603400674076736 GNO + // + // Settlement contract balance after execution = 1480603400674076736 GNO = + // 1480603400674076736 GNO * 8409990801239586768 / (10000000000000000000 - + // 1480603400674076736) = 1461589542731026166 DAI + execute_test( + web3.clone(), + fee_policy, + OrderKind::Sell, + 1480603400674076736u128.into(), + 1461589542731026166u128.into(), + ) + .await; +} + +async fn price_improvement_fee_sell_order_capped_test(web3: Web3) { + let fee_policy = FeePolicyKind::PriceImprovement { + factor: 1.0, + max_volume_factor: 0.1, + }; + // Without protocol fee: + // Expected executed_surplus_fee is 167058994203399 + // + // With protocol fee: + // Expected executed_surplus_fee is 167058994203399 + + // 0.1*10000000000000000000 = 1000167058994203400 + // + // Final execution is 10000000000000000000 GNO for 8884257395945205588 DAI, with + // executed_surplus_fee = 1000167058994203400 GNO + // + // Settlement contract balance after execution = 1000167058994203400 GNO = + // 1000167058994203400 GNO * 8884257395945205588 / (10000000000000000000 - + // 1000167058994203400) = 987322948025407485 DAI + execute_test( + web3.clone(), + fee_policy, + OrderKind::Sell, + 1000167058994203400u128.into(), + 987322948025407485u128.into(), + ) + .await; +} + +async fn price_improvement_fee_buy_order_test(web3: Web3) { + let fee_policy = FeePolicyKind::PriceImprovement { + factor: 0.3, + max_volume_factor: 1.0, + }; + // Without protocol fee: + // Expected execution is 5040413426236634210 GNO for 5000000000000000000 DAI, + // with executed_surplus_fee = 167058994203399 GNO + // + // With protocol fee: + // surplus in sell token = 10000000000000000000 - 5040413426236634210 = + // 4959586573763365790 + // + // protocol fee in sell token = 0.3*4959586573763365790 = 1487875972129009737 + // + // expected executed_surplus_fee is 167058994203399 + 1487875972129009737 = + // 1488043031123213136 + // + // Settlement contract balance after execution = executed_surplus_fee GNO + execute_test( + web3.clone(), + fee_policy, + OrderKind::Buy, + 1488043031123213136u128.into(), + 1488043031123213136u128.into(), + ) + .await; +} + +async fn price_improvement_fee_buy_order_capped_test(web3: Web3) { + let fee_policy = FeePolicyKind::PriceImprovement { + factor: 1.0, + max_volume_factor: 0.1, + }; + // Without protocol fee: + // Expected execution is 5040413426236634210 GNO for 5000000000000000000 DAI, + // with executed_surplus_fee = 167058994203399 GNO + // + // With protocol fee: + // Expected executed_surplus_fee is 167058994203399 + 0.1*5040413426236634210 = + // 504208401617866820 + // + // Settlement contract balance after execution = executed_surplus_fee GNO + execute_test( + web3.clone(), + fee_policy, + OrderKind::Buy, + 504208401617866820u128.into(), + 504208401617866820u128.into(), + ) + .await; +} + // because of rounding errors, it's good enough to check that the expected value // is within a very narrow range of the executed value fn is_approximately_equal(executed_value: U256, expected_value: U256) -> bool { @@ -358,6 +496,9 @@ async fn execute_test( enum FeePolicyKind { /// How much of the order's surplus should be taken as a protocol fee. Surplus { factor: f64, max_volume_factor: f64 }, + /// How much of the order's price improvement should be taken as a protocol + /// fee. + PriceImprovement { factor: f64, max_volume_factor: f64 }, /// How much of the order's volume should be taken as a protocol fee. Volume { factor: f64 }, } @@ -373,6 +514,14 @@ impl std::fmt::Display for FeePolicyKind { "--fee-policy-kind=surplus:{}:{}", factor, max_volume_factor ), + FeePolicyKind::PriceImprovement { + factor, + max_volume_factor, + } => write!( + f, + "--fee-policy-kind=price-improvement:{}:{}", + factor, max_volume_factor + ), FeePolicyKind::Volume { factor } => { write!(f, "--fee-policy-kind=volume:{}", factor) } From 603eb8b8ab1edd5b226922580d961600e3bf9d04 Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 6 Feb 2024 17:08:57 +0000 Subject: [PATCH 24/24] Docs --- database/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/database/README.md b/database/README.md index 7286f20041..c24b0ecad7 100644 --- a/database/README.md +++ b/database/README.md @@ -248,8 +248,8 @@ Column | Type | Nullable | Details surplus_factor | double precision | | percentage of the surplus for fee calculation; value is between 0 and 1 max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1 volume_factor | double precision | | fee percentage of the order volume; value is between 0 and 1 - sell_amount | numeric | | quote's sell amount - buy_amount | numeric | | quote's buy amount + quote_sell_amount | numeric | | quote's sell amount + quote_buy_amount | numeric | | quote's buy amount Indexes: - PRIMARY KEY: composite key(`auction_id`, `order_uid`, `application_order`) @@ -262,6 +262,7 @@ Indexes: Values: - `surplus`: The fee is based on the surplus achieved in the trade. - `volume`: The fee is based on the volume of the order. + - `priceimprovement`: The fee is based on the difference of the order's execution price and the top quote. ### presignature\_events