Skip to content

Commit

Permalink
Define protocol fee params and forward to driver (#2098)
Browse files Browse the repository at this point in the history
# Description
Tackles #2083 and
#2084.

Defines fee policy configuration parameters. Based on them, we can take
the protocol fee as a cut from price improvement or as a percent of the
order volume (fixed fee in absolute value will be implemented later).
On top of that, we also need two flags, for skipping in-market limit
orders and twap orders, since default initial configuration will be to
run the fee experiment only for out-of-market limit orders.

Note that the quote is not forwarded to the `driver` at this moment,
since it is not needed for out-of-market limit orders.

Also note that I am missing the cap for absolute value of protocol fee
(default value 1000$ or 0.5ETH) but this cap is still discussed (will be
added later if needed).

Skipping forwarding protocol fee parameters for in-market limit orders
will be done in a separate PR.

# Changes
<!-- List of detailed changes (how the change is accomplished) -->

- [ ] Defines basic parameters for protocol fee calculation.
- [ ] Forwards protocol fee parameters to the driver.
  • Loading branch information
sunce86 authored Dec 12, 2023
1 parent 095e4b5 commit 138cadd
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 5 deletions.
73 changes: 73 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use {
std::{
net::SocketAddr,
num::{NonZeroUsize, ParseFloatError},
str::FromStr,
time::Duration,
},
url::Url,
Expand Down Expand Up @@ -208,6 +209,10 @@ pub struct Arguments {
)]
pub solve_deadline: Duration,

/// Describes how the protocol fee should be calculated.
#[clap(flatten)]
pub fee_policy: FeePolicy,

/// Time interval in days between each cleanup operation of the
/// `order_events` database table.
#[clap(long, env, default_value = "1", value_parser = duration_from_days)]
Expand Down Expand Up @@ -284,6 +289,7 @@ impl std::fmt::Display for Arguments {
writeln!(f, "score_cap: {}", self.score_cap)?;
display_option(f, "shadow", &self.shadow)?;
writeln!(f, "solve_deadline: {:?}", self.solve_deadline)?;
writeln!(f, "fee_policy: {:?}", self.fee_policy)?;
writeln!(
f,
"order_events_cleanup_interval: {:?}",
Expand All @@ -298,6 +304,73 @@ impl std::fmt::Display for Arguments {
}
}

#[derive(clap::Parser, Debug, Clone)]
pub struct FeePolicy {
/// Type of fee policy to use. Examples:
///
/// - Price improvement without cap
/// price_improvement:0.5:1.0
///
/// - Price improvement with cap:
/// price_improvement:0.5:0.06
///
/// - Volume based:
/// volume:0.1
#[clap(long, env, default_value = "priceImprovement:0.0:1.0")]
pub fee_policy_kind: FeePolicyKind,

/// Should protocol fees be collected or skipped for orders whose
/// limit price at order creation time suggests they can be immediately
/// filled.
#[clap(long, env, action = clap::ArgAction::Set, default_value = "true")]
pub fee_policy_skip_market_orders: bool,
}

#[derive(clap::Parser, Debug, Clone)]
pub enum FeePolicyKind {
/// How much of the order's price improvement over max(limit price,
/// best_bid) 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 },
}

impl FromStr for FeePolicyKind {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split(':');
let kind = parts.next().ok_or("missing fee policy kind")?;
match kind {
"priceImprovement" => {
let factor = parts
.next()
.ok_or("missing price improvement factor")?
.parse::<f64>()
.map_err(|e| format!("invalid price improvement factor: {}", e))?;
let max_volume_factor = parts
.next()
.ok_or("missing max volume factor")?
.parse::<f64>()
.map_err(|e| format!("invalid max volume factor: {}", e))?;
Ok(Self::PriceImprovement {
factor,
max_volume_factor,
})
}
"volume" => {
let factor = parts
.next()
.ok_or("missing volume factor")?
.parse::<f64>()
.map_err(|e| format!("invalid volume factor: {}", e))?;
Ok(Self::Volume { factor })
}
_ => Err(format!("invalid fee policy kind: {}", kind)),
}
}
}

fn duration_from_days(s: &str) -> Result<Duration, ParseFloatError> {
let days = s.parse::<f64>()?;
Ok(Duration::from_secs_f64(days * 86_400.0))
Expand Down
51 changes: 50 additions & 1 deletion crates/autopilot/src/driver_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub mod quote {

pub mod solve {
use {
crate::arguments,
chrono::{DateTime, Utc},
model::{
app_data::AppDataHash,
Expand Down Expand Up @@ -87,7 +88,7 @@ pub mod solve {
}

#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Order {
pub uid: OrderUid,
Expand Down Expand Up @@ -116,6 +117,9 @@ pub mod solve {
pub app_data: AppDataHash,
#[serde(flatten)]
pub signature: Signature,
/// The types of fees that will be collected by the protocol.
/// Multiple fees are applied in the order they are listed
pub fee_policies: Vec<FeePolicy>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand All @@ -137,6 +141,51 @@ pub mod solve {
pub call_data: Vec<u8>,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum FeePolicy {
/// If the order receives more than expected (positive deviation from
/// quoted amounts) pay the protocol a factor of the achieved
/// improvement. The fee is taken in `sell` token for `buy`
/// orders and in `buy` token for `sell` orders.
#[serde(rename_all = "camelCase")]
PriceImprovement {
/// Factor of price improvement the protocol charges as a fee.
/// Price improvement is the difference between executed price and
/// limit price or quoted price (whichever is better)
///
/// E.g. if a user received 2000USDC for 1ETH while having been
/// quoted 1990USDC, their price improvement is 10USDC.
/// A factor of 0.5 requires the solver to pay 5USDC to
/// the protocol for settling this order.
factor: f64,
/// Cap protocol fee with a percentage of the order's volume.
max_volume_factor: f64,
},
/// 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.
#[serde(rename_all = "camelCase")]
Volume {
/// Percentage of the order's volume should be taken as a protocol
/// fee.
factor: f64,
},
}

pub fn fee_policy_to_dto(fee_policy: &arguments::FeePolicy) -> FeePolicy {
match fee_policy.fee_policy_kind {
arguments::FeePolicyKind::PriceImprovement {
factor: price_improvement_factor,
max_volume_factor,
} => FeePolicy::PriceImprovement {
factor: price_improvement_factor,
max_volume_factor,
},
arguments::FeePolicyKind::Volume { factor } => FeePolicy::Volume { factor },
}
}

#[serde_as]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down
2 changes: 2 additions & 0 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ pub async fn run(args: Arguments) {
max_settlement_transaction_wait: args.max_settlement_transaction_wait,
solve_deadline: args.solve_deadline,
in_flight_orders: Default::default(),
fee_policy: args.fee_policy,
};
run.run_forever().await;
unreachable!("run loop exited");
Expand Down Expand Up @@ -695,6 +696,7 @@ async fn shadow_mode(args: Arguments) -> ! {
trusted_tokens,
args.score_cap,
args.solve_deadline,
args.fee_policy,
);
shadow.run_forever().await;

Expand Down
18 changes: 17 additions & 1 deletion crates/autopilot/src/run_loop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use {
crate::{
arguments,
database::{
competition::{Competition, ExecutedFee, OrderExecution},
Postgres,
Expand All @@ -8,7 +9,7 @@ use {
driver_model::{
reveal::{self, Request},
settle,
solve::{self, Class, TradedAmounts},
solve::{self, fee_policy_to_dto, Class, TradedAmounts},
},
solvable_orders::SolvableOrdersCache,
},
Expand Down Expand Up @@ -56,6 +57,7 @@ pub struct RunLoop {
pub max_settlement_transaction_wait: Duration,
pub solve_deadline: Duration,
pub in_flight_orders: Arc<Mutex<InFlightOrders>>,
pub fee_policy: arguments::FeePolicy,
}

impl RunLoop {
Expand Down Expand Up @@ -310,6 +312,7 @@ impl RunLoop {
&self.market_makable_token_list.all(),
self.score_cap,
self.solve_deadline,
self.fee_policy.clone(),
);
let request = &request;

Expand Down Expand Up @@ -492,6 +495,7 @@ pub fn solve_request(
trusted_tokens: &HashSet<H160>,
score_cap: U256,
time_limit: Duration,
fee_policy: arguments::FeePolicy,
) -> solve::Request {
solve::Request {
id,
Expand All @@ -517,6 +521,17 @@ pub fn solve_request(
.collect()
};
let order_is_untouched = remaining_order.executed_amount.is_zero();

let fee_policies = match order.metadata.class {
OrderClass::Market => vec![],
OrderClass::Liquidity => vec![],
// todo https://github.com/cowprotocol/services/issues/2092
// skip protocol fee for limit orders with in-market price

// todo https://github.com/cowprotocol/services/issues/2115
// skip protocol fee for TWAP limit orders
OrderClass::Limit(_) => vec![fee_policy_to_dto(&fee_policy)],
};
solve::Order {
uid: order.metadata.uid,
sell_token: order.data.sell_token,
Expand All @@ -542,6 +557,7 @@ pub fn solve_request(
class,
app_data: order.data.app_data,
signature: order.signature.clone(),
fee_policies,
}
})
.collect(),
Expand Down
10 changes: 9 additions & 1 deletion crates/autopilot/src/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
use {
crate::{
arguments::FeePolicy,
driver_api::Driver,
driver_model::{reveal, solve},
driver_model::{
reveal,
solve::{self},
},
protocol,
run_loop,
},
Expand Down Expand Up @@ -43,6 +47,7 @@ pub struct RunLoop {
block: u64,
score_cap: U256,
solve_deadline: Duration,
fee_policy: FeePolicy,
}

impl RunLoop {
Expand All @@ -52,6 +57,7 @@ impl RunLoop {
trusted_tokens: AutoUpdatingTokenList,
score_cap: U256,
solve_deadline: Duration,
fee_policy: FeePolicy,
) -> Self {
Self {
orderbook,
Expand All @@ -61,6 +67,7 @@ impl RunLoop {
block: 0,
score_cap,
solve_deadline,
fee_policy,
}
}

Expand Down Expand Up @@ -193,6 +200,7 @@ impl RunLoop {
&self.trusted_tokens.all(),
self.score_cap,
self.solve_deadline,
self.fee_policy.clone(),
);
let request = &request;

Expand Down
28 changes: 28 additions & 0 deletions crates/driver/src/domain/competition/order/fees.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#[derive(Clone, Debug)]
pub enum FeePolicy {
/// If the order receives more than expected (positive deviation from quoted
/// amounts) pay the protocol a factor of the achieved improvement.
/// The fee is taken in `sell` token for `buy` orders and in `buy`
/// token for `sell` orders.
PriceImprovement {
/// Factor of price improvement the protocol charges as a fee.
/// Price improvement is the difference between executed price and
/// limit price or quoted price (whichever is better)
///
/// E.g. if a user received 2000USDC for 1ETH while having been quoted
/// 1990USDC, their price improvement is 10USDC. A factor of 0.5
/// requires the solver to pay 5USDC to the protocol for
/// settling this order.
factor: f64,
/// Cap protocol fee with a percentage of the order's volume.
max_volume_factor: f64,
},
/// 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.
Volume {
/// Percentage of the order's volume should be taken as a protocol
/// fee.
factor: f64,
},
}
8 changes: 7 additions & 1 deletion crates/driver/src/domain/competition/order/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub use signature::Signature;
use {
super::auction,
crate::{
Expand All @@ -9,7 +8,9 @@ use {
bigdecimal::Zero,
num::CheckedDiv,
};
pub use {fees::FeePolicy, signature::Signature};

pub mod fees;
pub mod signature;

/// An order in the auction.
Expand Down Expand Up @@ -39,6 +40,10 @@ pub struct Order {
pub sell_token_balance: SellTokenBalance,
pub buy_token_balance: BuyTokenBalance,
pub signature: Signature,
/// The types of fees the protocol collects from the winning solver.
/// Unless otherwise configured, the driver modifies solutions to take
/// sufficient fee in the form of positive slippage.
pub fee_policies: Vec<FeePolicy>,
}

/// An amount denominated in the sell token of an [`Order`].
Expand Down Expand Up @@ -453,6 +458,7 @@ mod tests {
data: Default::default(),
signer: Default::default(),
},
fee_policies: Default::default(),
};

assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions crates/driver/src/domain/quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl Order {
data: Default::default(),
signer: Default::default(),
},
fee_policies: Default::default(),
}],
[
auction::Token {
Expand Down
Loading

0 comments on commit 138cadd

Please sign in to comment.