Skip to content

Commit

Permalink
Make the partner fee max cap configurable in autopilot (#2559)
Browse files Browse the repository at this point in the history
# Description
We should make configurable in the autopilot the maximum partner fee
amount.

# Changes
- Introduce a new configuration parameter in the autopilot for the
partner fee max cap with a default value 0.01
- Enforce the configured partner fee max cap

## How to test
1. e2e test
2. unit test

## Related Issues
Fixes #2554
  • Loading branch information
m-lord-renkse authored Mar 26, 2024
1 parent 27c3492 commit 358d483
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 31 deletions.
11 changes: 11 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ pub struct Arguments {
#[clap(long, env, use_value_delimiter = true)]
pub fee_policies: Vec<FeePolicy>,

/// 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,

/// Arguments for uploading information to S3.
#[clap(flatten)]
pub s3: infra::persistence::cli::S3,
Expand Down Expand Up @@ -253,6 +258,7 @@ impl std::fmt::Display for Arguments {
shadow,
solve_deadline,
fee_policies,
fee_policy_max_partner_fee,
order_events_cleanup_interval,
order_events_cleanup_threshold,
db_url,
Expand Down Expand Up @@ -314,6 +320,11 @@ impl std::fmt::Display for Arguments {
display_option(f, "shadow", shadow)?;
writeln!(f, "solve_deadline: {:?}", solve_deadline)?;
writeln!(f, "fee_policies: {:?}", fee_policies)?;
writeln!(
f,
"fee_policy_max_partner_fee: {:?}",
fee_policy_max_partner_fee
)?;
writeln!(
f,
"order_events_cleanup_interval: {:?}",
Expand Down
46 changes: 35 additions & 11 deletions crates/autopilot/src/domain/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
crate::{
arguments,
boundary::{self},
domain,
domain::{self},
},
app_data::Validator,
derive_more::Into,
Expand Down Expand Up @@ -42,18 +42,39 @@ pub struct ProtocolFee {
order_class: OrderClass,
}

impl ProtocolFee {
pub fn new(fee_policy_args: arguments::FeePolicy) -> Self {
impl From<arguments::FeePolicy> for ProtocolFee {
fn from(value: arguments::FeePolicy) -> Self {
Self {
policy: fee_policy_args.fee_policy_kind.into(),
order_class: fee_policy_args.fee_policy_order_class.into(),
policy: value.fee_policy_kind.into(),
order_class: value.fee_policy_order_class.into(),
}
}
}

pub struct ProtocolFees {
fee_policies: Vec<ProtocolFee>,
max_partner_fee: FeeFactor,
}

impl ProtocolFees {
pub fn new(
fee_policies: &[arguments::FeePolicy],
fee_policy_max_partner_fee: FeeFactor,
) -> Self {
Self {
fee_policies: fee_policies
.iter()
.cloned()
.map(ProtocolFee::from)
.collect(),
max_partner_fee: fee_policy_max_partner_fee,
}
}

/// Converts an order from the boundary layer to the domain layer, applying
/// protocol fees if necessary.
pub fn apply(
protocol_fees: &[ProtocolFee],
protocol_fees: &ProtocolFees,
order: boundary::Order,
quote: &domain::Quote,
) -> domain::Order {
Expand All @@ -69,9 +90,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,
),
protocol_fees.max_partner_fee.into(),
)
.unwrap(),
}];
return boundary::order::to_domain(order, fee_policy);
}
Expand All @@ -88,6 +111,7 @@ impl ProtocolFee {
fee: quote.fee,
};
let protocol_fees = protocol_fees
.fee_policies
.iter()
// TODO: support multiple fee policies
.find_map(|fee_policy| {
Expand Down Expand Up @@ -149,9 +173,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<Self> {
value.max(0.0).min(cap).try_into()
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/autopilot/src/domain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pub use {
Auction,
AuctionWithId,
},
fee::ProtocolFee,
fee::ProtocolFees,
quote::Quote,
};
5 changes: 1 addition & 4 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,10 +560,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"),
args.fee_policies
.into_iter()
.map(domain::ProtocolFee::new)
.collect(),
domain::ProtocolFees::new(&args.fee_policies, args.fee_policy_max_partner_fee),
);
solvable_orders_cache
.update(block)
Expand Down
6 changes: 3 additions & 3 deletions crates/autopilot/src/solvable_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub struct SolvableOrdersCache {
metrics: &'static Metrics,
weth: H160,
limit_order_price_factor: BigDecimal,
protocol_fees: Vec<domain::ProtocolFee>,
protocol_fees: domain::ProtocolFees,
}

type Balances = HashMap<Query, U256>;
Expand All @@ -104,7 +104,7 @@ impl SolvableOrdersCache {
update_interval: Duration,
weth: H160,
limit_order_price_factor: BigDecimal,
protocol_fees: Vec<domain::ProtocolFee>,
protocol_fees: domain::ProtocolFees,
) -> Arc<Self> {
let self_ = Arc::new(Self {
min_order_validity_period,
Expand Down Expand Up @@ -242,7 +242,7 @@ impl SolvableOrdersCache {
.into_iter()
.filter_map(|order| {
if let Some(quote) = db_solvable_orders.quotes.get(&order.metadata.uid.into()) {
Some(domain::ProtocolFee::apply(&self.protocol_fees, order, quote))
Some(domain::ProtocolFees::apply(&self.protocol_fees, order, quote))
} else {
tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing");
None
Expand Down
79 changes: 67 additions & 12 deletions crates/e2e/tests/e2e/protocol_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -91,7 +97,7 @@ async fn surplus_fee_sell_order_test(web3: Web3) {
// 1480603400674076736) = 1461589542731026166 DAI
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Sell,
None,
1480603400674076736u128.into(),
Expand Down Expand Up @@ -133,7 +139,7 @@ async fn surplus_fee_sell_order_capped_test(web3: Web3) {
// 1000150353094783059) = 987306456662572858 DAI
execute_test(
web3.clone(),
protocol_fees,
vec![ProtocolFeesConfig(protocol_fees)],
OrderKind::Sell,
None,
1000150353094783059u128.into(),
Expand Down Expand Up @@ -166,7 +172,7 @@ async fn volume_fee_sell_order_test(web3: Web3) {
// 1000150353094783059) = 987306456662572858 DAI
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Sell,
None,
1000150353094783059u128.into(),
Expand Down Expand Up @@ -201,7 +207,7 @@ async fn partner_fee_sell_order_test(web3: Web3) {
// 100165388404261365) = 98879067931768848 DAI
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Sell,
Some(OrderCreationAppData::Full {
full: json!({
Expand All @@ -221,6 +227,55 @@ 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,
};
let protocol_fee = ProtocolFee {
policy: fee_policy,
policy_order_class: FeePolicyOrderClass::Market,
};
// 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![
ProtocolFeesConfig(vec![protocol_fee]).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,
Expand All @@ -246,7 +301,7 @@ async fn surplus_fee_buy_order_test(web3: Web3) {
// Settlement contract balance after execution = executed_surplus_fee GNO
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Buy,
None,
1488043031123213136u128.into(),
Expand Down Expand Up @@ -275,7 +330,7 @@ async fn surplus_fee_buy_order_capped_test(web3: Web3) {
// Settlement contract balance after execution = executed_surplus_fee GNO
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Buy,
None,
504208401617866820u128.into(),
Expand All @@ -301,7 +356,7 @@ async fn volume_fee_buy_order_test(web3: Web3) {
// Settlement contract balance after execution = executed_surplus_fee GNO
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Buy,
None,
504208401617866820u128.into(),
Expand Down Expand Up @@ -343,7 +398,7 @@ async fn price_improvement_fee_sell_order_test(web3: Web3) {
// 205312824093583) = 202676203868731 DAI
execute_test(
web3.clone(),
vec![protocol_fee],
vec![ProtocolFeesConfig(vec![protocol_fee])],
OrderKind::Sell,
None,
205312824093583u128.into(),
Expand All @@ -362,7 +417,7 @@ fn is_approximately_equal(executed_value: U256, expected_value: U256) -> bool {

async fn execute_test(
web3: Web3,
protocol_fees: Vec<ProtocolFee>,
autopilot_config: Vec<impl ToString>,
order_kind: OrderKind,
app_data: Option<OrderCreationAppData>,
expected_surplus_fee: U256,
Expand Down Expand Up @@ -435,12 +490,12 @@ async fn execute_test(
endpoint: solver_endpoint,
}],
);
let autopilot_args = vec![
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(),
ProtocolFeesConfig(protocol_fees).to_string(),
];
services.start_autopilot(None, autopilot_args);
config.extend(autopilot_config.iter().map(ToString::to_string));
services.start_autopilot(None, config);
services
.start_api(vec![
"--price-estimation-drivers=test_quoter|http://localhost:11088/test_solver".to_string(),
Expand Down

0 comments on commit 358d483

Please sign in to comment.