diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 448acef837..6c8dc0a04b 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -366,6 +366,11 @@ pub struct FeePolicy { /// filled. #[clap(long, env, action = clap::ArgAction::Set, default_value = "true")] pub fee_policy_skip_market_orders: bool, + + /// Maximum partner fee allow. If the partner fee specified is greater than + /// this maximum, the partner fee will be capped + #[clap(long, env, default_value = "0.01")] + pub fee_policy_max_partner_fee: FeeFactor, } #[derive(clap::Parser, Debug, Clone)] diff --git a/crates/autopilot/src/domain/fee/mod.rs b/crates/autopilot/src/domain/fee/mod.rs index 679f878a9e..9c173feca4 100644 --- a/crates/autopilot/src/domain/fee/mod.rs +++ b/crates/autopilot/src/domain/fee/mod.rs @@ -10,7 +10,10 @@ use { crate::{ arguments, boundary::{self}, - domain, + domain::{ + self, + fee::policy::{PriceImprovement, Surplus, Volume}, + }, }, app_data::Validator, derive_more::Into, @@ -23,15 +26,39 @@ use { /// Constructs fee policies based on the current configuration. pub struct ProtocolFee { policy: policy::Policy, + max_partner_fee: FeeFactor, } -impl ProtocolFee { - pub fn new(fee_policy_args: arguments::FeePolicy) -> Self { +impl From for ProtocolFee { + fn from(policy_arg: arguments::FeePolicy) -> Self { + let policy = match policy_arg.fee_policy_kind { + arguments::FeePolicyKind::Surplus { + factor, + max_volume_factor, + } => policy::Policy::Surplus(Surplus { + factor, + max_volume_factor, + skip_market_orders: policy_arg.fee_policy_skip_market_orders, + }), + arguments::FeePolicyKind::PriceImprovement { + factor, + max_volume_factor, + } => policy::Policy::PriceImprovement(PriceImprovement { + factor, + max_volume_factor, + }), + arguments::FeePolicyKind::Volume { factor } => { + policy::Policy::Volume(Volume { factor }) + } + }; Self { - policy: fee_policy_args.into(), + policy, + max_partner_fee: policy_arg.fee_policy_max_partner_fee, } } +} +impl ProtocolFee { /// Converts an order from the boundary layer to the domain layer, applying /// protocol fees if necessary. pub fn apply(&self, order: boundary::Order, quote: &domain::Quote) -> domain::Order { @@ -47,9 +74,11 @@ impl ProtocolFee { { if let Some(partner_fee) = validated_app_data.protocol.partner_fee { let fee_policy = vec![Policy::Volume { - factor: FeeFactor::partner_fee_capped_from( + factor: FeeFactor::try_from_capped( partner_fee.bps.into_f64() / 10_000.0, - ), + self.max_partner_fee.into(), + ) + .unwrap(), }]; return boundary::order::to_domain(order, fee_policy); } @@ -105,9 +134,9 @@ pub enum Policy { pub struct FeeFactor(f64); impl FeeFactor { - /// Convert a partner fee into a `Factor` capping its value - pub fn partner_fee_capped_from(value: f64) -> Self { - Self(value.max(0.0).min(0.01)) + /// Convert a fee into a `FeeFactor` capping its value + pub fn try_from_capped(value: f64, cap: f64) -> anyhow::Result { + value.max(0.0).min(cap).try_into() } } diff --git a/crates/autopilot/src/domain/fee/policy.rs b/crates/autopilot/src/domain/fee/policy.rs index 5c79861630..6de1af9a07 100644 --- a/crates/autopilot/src/domain/fee/policy.rs +++ b/crates/autopilot/src/domain/fee/policy.rs @@ -1,5 +1,4 @@ use crate::{ - arguments, boundary, domain::{self, fee::FeeFactor}, }; @@ -11,41 +10,18 @@ pub enum Policy { } pub struct Surplus { - factor: FeeFactor, - max_volume_factor: FeeFactor, - skip_market_orders: bool, + pub(crate) factor: FeeFactor, + pub(crate) max_volume_factor: FeeFactor, + pub(crate) skip_market_orders: bool, } pub struct PriceImprovement { - factor: FeeFactor, - max_volume_factor: FeeFactor, + pub(crate) factor: FeeFactor, + pub(crate) max_volume_factor: FeeFactor, } pub struct Volume { - factor: FeeFactor, -} - -impl From for Policy { - fn from(policy_arg: arguments::FeePolicy) -> Self { - match policy_arg.fee_policy_kind { - arguments::FeePolicyKind::Surplus { - factor, - max_volume_factor, - } => Policy::Surplus(Surplus { - factor, - max_volume_factor, - skip_market_orders: policy_arg.fee_policy_skip_market_orders, - }), - arguments::FeePolicyKind::PriceImprovement { - factor, - max_volume_factor, - } => Policy::PriceImprovement(PriceImprovement { - factor, - max_volume_factor, - }), - arguments::FeePolicyKind::Volume { factor } => Policy::Volume(Volume { factor }), - } - } + pub(crate) factor: FeeFactor, } impl Surplus { diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 7b40304797..dce9fd9899 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -552,7 +552,7 @@ pub async fn run(args: Arguments) { args.limit_order_price_factor .try_into() .expect("limit order price factor can't be converted to BigDecimal"), - domain::ProtocolFee::new(args.fee_policy.clone()), + domain::ProtocolFee::from(args.fee_policy.clone()), ); solvable_orders_cache .update(block) diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index adb9c692b0..00212de603 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -38,6 +38,12 @@ async fn local_node_partner_fee_sell_order() { run_test(partner_fee_sell_order_test).await; } +#[tokio::test] +#[ignore] +async fn local_node_partner_fee_with_not_default_cap_sell_order_test() { + run_test(partner_fee_with_not_default_cap_sell_order_test).await; +} + #[tokio::test] #[ignore] async fn local_node_surplus_fee_buy_order() { @@ -87,7 +93,7 @@ async fn surplus_fee_sell_order_test(web3: Web3) { // 1480603400674076736) = 1461589542731026166 DAI execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Sell, None, 1480603400674076736u128.into(), @@ -117,7 +123,7 @@ async fn surplus_fee_sell_order_capped_test(web3: Web3) { // 1000150353094783059) = 987306456662572858 DAI execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Sell, None, 1000150353094783059u128.into(), @@ -144,7 +150,7 @@ async fn volume_fee_sell_order_test(web3: Web3) { // 1000150353094783059) = 987306456662572858 DAI execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Sell, None, 1000150353094783059u128.into(), @@ -175,7 +181,7 @@ async fn partner_fee_sell_order_test(web3: Web3) { // 100165388404261365) = 98879067931768848 DAI execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Sell, Some(OrderCreationAppData::Full { full: json!({ @@ -195,6 +201,51 @@ async fn partner_fee_sell_order_test(web3: Web3) { .await; } +async fn partner_fee_with_not_default_cap_sell_order_test(web3: Web3) { + // Fee policy to be overwritten by the partner fee + capped to 0.02 + let fee_policy = FeePolicyKind::PriceImprovement { + factor: 0.5, + max_volume_factor: 0.9, + }; + // Without protocol fee: + // Expected execution is 10000000000000000000 GNO for + // 9871415430342266811 DAI, with executed_surplus_fee = 167058994203399 GNO + // + // With protocol fee: + // Expected executed_surplus_fee is 167058994203399 + + // 0.02*(10000000000000000000 - 167058994203399) = 200163717814319331 + // + // Final execution is 10000000000000000000 GNO for 9772701276038844388 DAI, with + // executed_surplus_fee = 200163717814319331 GNO + // + // Settlement contract balance after execution = 200163717814319331 GNO = + // 200163717814319331 GNO * 9673987121735421787 / (10000000000000000000 - + // 200163717814319331) = 197593222235191520 DAI + execute_test( + web3.clone(), + vec![ + fee_policy.to_string(), + "--fee-policy-max-partner-fee=0.02".to_string(), + ], + OrderKind::Sell, + Some(OrderCreationAppData::Full { + full: json!({ + "version": "1.1.0", + "metadata": { + "partnerFee": { + "bps":1000, + "recipient": "0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9", + } + } + }) + .to_string(), + }), + 200163717814319331u128.into(), + 197593222235191520u128.into(), + ) + .await; +} + async fn surplus_fee_buy_order_test(web3: Web3) { let fee_policy = FeePolicyKind::Surplus { factor: 0.3, @@ -216,7 +267,7 @@ async fn surplus_fee_buy_order_test(web3: Web3) { // Settlement contract balance after execution = executed_surplus_fee GNO execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Buy, None, 1488043031123213136u128.into(), @@ -241,7 +292,7 @@ async fn surplus_fee_buy_order_capped_test(web3: Web3) { // Settlement contract balance after execution = executed_surplus_fee GNO execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Buy, None, 504208401617866820u128.into(), @@ -263,7 +314,7 @@ async fn volume_fee_buy_order_test(web3: Web3) { // Settlement contract balance after execution = executed_surplus_fee GNO execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Buy, None, 504208401617866820u128.into(), @@ -301,7 +352,7 @@ async fn price_improvement_fee_sell_order_test(web3: Web3) { // 205312824093583) = 202676203868731 DAI execute_test( web3.clone(), - fee_policy, + vec![fee_policy.to_string()], OrderKind::Sell, None, 205312824093583u128.into(), @@ -320,7 +371,7 @@ fn is_approximately_equal(executed_value: U256, expected_value: U256) -> bool { async fn execute_test( web3: Web3, - fee_policy: FeePolicyKind, + autopilot_config: Vec, order_kind: OrderKind, app_data: Option, expected_surplus_fee: U256, @@ -393,15 +444,13 @@ async fn execute_test( endpoint: solver_endpoint, }], ); - services.start_autopilot( - None, - vec![ - "--drivers=test_solver|http://localhost:11088/test_solver".to_string(), - "--price-estimation-drivers=test_quoter|http://localhost:11088/test_solver".to_string(), - "--fee-policy-skip-market-orders=false".to_string(), - fee_policy.to_string(), - ], - ); + let mut config = vec![ + "--drivers=test_solver|http://localhost:11088/test_solver".to_string(), + "--price-estimation-drivers=test_quoter|http://localhost:11088/test_solver".to_string(), + "--fee-policy-skip-market-orders=false".to_string(), + ]; + config.extend(autopilot_config); + services.start_autopilot(None, config); services .start_api(vec![ "--price-estimation-drivers=test_quoter|http://localhost:11088/test_solver".to_string(),