Skip to content

Commit

Permalink
Allow multiple fee policies (#2595)
Browse files Browse the repository at this point in the history
# Description
Support for multiple fee policies

# Changes
- Allow multiple fee policies in autopilot and driver
- Apply fee policies one by one, where each applied policy creates a new
solution as a base to apply next fee policy

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

## Related Issues

Fixes #2495

---------

Co-authored-by: sunce86 <dusan.stanivukovic@gmail.com>
  • Loading branch information
m-lord-renkse and sunce86 authored Apr 18, 2024
1 parent dd2fe4b commit ae05328
Show file tree
Hide file tree
Showing 16 changed files with 720 additions and 224 deletions.
6 changes: 6 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ pub struct Arguments {
#[clap(long, env, use_value_delimiter = true)]
pub fee_policies: Vec<FeePolicy>,

/// Enables multiple fees
#[clap(long, env, action = clap::ArgAction::Set, default_value = "false")]
pub enable_multiple_fees: 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")]
Expand Down Expand Up @@ -258,6 +262,7 @@ impl std::fmt::Display for Arguments {
shadow,
solve_deadline,
fee_policies,
enable_multiple_fees,
fee_policy_max_partner_fee,
order_events_cleanup_interval,
order_events_cleanup_threshold,
Expand Down Expand Up @@ -325,6 +330,7 @@ impl std::fmt::Display for Arguments {
"protocol_fee_exempt_addresses: {:?}",
protocol_fee_exempt_addresses
)?;
writeln!(f, "enable_multiple_fees: {:?}", enable_multiple_fees)?;
writeln!(
f,
"fee_policy_max_partner_fee: {:?}",
Expand Down
142 changes: 100 additions & 42 deletions crates/autopilot/src/domain/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ pub struct ProtocolFees {
/// List of addresses which are exempt from the protocol
/// fees
protocol_fee_exempt_addresses: ProtocolFeeExemptAddresses,
enable_protocol_fees: bool,
}

impl ProtocolFees {
pub fn new(
fee_policies: &[arguments::FeePolicy],
fee_policy_max_partner_fee: FeeFactor,
protocol_fee_exempt_addresses: &[H160],
enable_protocol_fees: bool,
) -> Self {
Self {
fee_policies: fee_policies
Expand All @@ -78,32 +80,39 @@ impl ProtocolFees {
.cloned()
.collect::<HashSet<_>>(),
max_partner_fee: fee_policy_max_partner_fee,
enable_protocol_fees,
}
}

/// 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 {
// If the partner fee is specified, it overwrites the current volume fee policy
if let Some(validated_app_data) = order
let partner_fee = order
.metadata
.full_app_data
.as_ref()
.map(|full_app_data| Validator::new(usize::MAX).validate(full_app_data.as_bytes()))
.transpose()
.ok()
.flatten()
.and_then(|full_app_data| {
Validator::new(usize::MAX)
.validate(full_app_data.as_bytes())
.ok()?
.protocol
.partner_fee
.map(|partner_fee| Policy::Volume {
factor: FeeFactor::try_from_capped(
partner_fee.bps.into_f64() / 10_000.0,
self.max_partner_fee.into(),
)
.unwrap(),
})
})
.into_iter()
.collect::<Vec<_>>();

if self
.protocol_fee_exempt_addresses
.contains(&order.metadata.owner)
{
if let Some(partner_fee) = validated_app_data.protocol.partner_fee {
let fee_policy = vec![Policy::Volume {
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);
}
return boundary::order::to_domain(order, partner_fee);
}

let order_ = boundary::Amounts {
Expand All @@ -116,35 +125,84 @@ impl ProtocolFees {
buy: quote.buy_amount,
fee: quote.fee,
};
let protocol_fees = if self
.protocol_fee_exempt_addresses
.contains(&order.metadata.owner)
{
vec![]

if self.enable_protocol_fees {
self.apply_multiple_policies(order, quote, order_, quote_, partner_fee)
} else {
self
.fee_policies
.iter()
// TODO: support multiple fee policies
.find_map(|fee_policy| {
let outside_market_price = boundary::is_order_outside_market_price(&order_, &quote_, order.data.kind);
match (outside_market_price, &fee_policy.order_class) {
(_, OrderClass::Any) => Some(&fee_policy.policy),
(true, OrderClass::Limit) => Some(&fee_policy.policy),
(false, OrderClass::Market) => Some(&fee_policy.policy),
_ => None,
}
})
.and_then(|policy| match policy {
policy::Policy::Surplus(variant) => variant.apply(&order),
policy::Policy::PriceImprovement(variant) => variant.apply(&order, quote),
policy::Policy::Volume(variant) => variant.apply(&order),
})
.into_iter()
.collect_vec()
};
self.apply_single_policy(order, quote, order_, quote_, partner_fee)
}
}

fn apply_single_policy(
&self,
order: boundary::Order,
quote: &domain::Quote,
order_: boundary::Amounts,
quote_: boundary::Amounts,
partner_fees: Vec<Policy>,
) -> domain::Order {
if let Some(partner_fee) = partner_fees.first() {
return boundary::order::to_domain(order, vec![*partner_fee]);
}
let protocol_fees = self
.fee_policies
.iter()
.find_map(|fee_policy| {
Self::protocol_fee_into_policy(&order, &order_, &quote_, fee_policy)
})
.and_then(|policy| Self::variant_fee_apply(&order, quote, policy))
.into_iter()
.collect_vec();
boundary::order::to_domain(order, protocol_fees)
}

fn apply_multiple_policies(
&self,
order: boundary::Order,
quote: &domain::Quote,
order_: boundary::Amounts,
quote_: boundary::Amounts,
partner_fees: Vec<Policy>,
) -> domain::Order {
let protocol_fees = self
.fee_policies
.iter()
.filter_map(|fee_policy| {
Self::protocol_fee_into_policy(&order, &order_, &quote_, fee_policy)
})
.flat_map(|policy| Self::variant_fee_apply(&order, quote, policy))
.chain(partner_fees)
.collect::<Vec<_>>();
boundary::order::to_domain(order, protocol_fees)
}

fn variant_fee_apply(
order: &boundary::Order,
quote: &domain::Quote,
policy: &policy::Policy,
) -> Option<Policy> {
match policy {
policy::Policy::Surplus(variant) => variant.apply(order),
policy::Policy::PriceImprovement(variant) => variant.apply(order, quote),
policy::Policy::Volume(variant) => variant.apply(order),
}
}

fn protocol_fee_into_policy<'a>(
order: &boundary::Order,
order_: &boundary::Amounts,
quote_: &boundary::Amounts,
protocol_fee: &'a ProtocolFee,
) -> Option<&'a policy::Policy> {
let outside_market_price =
boundary::is_order_outside_market_price(order_, quote_, order.data.kind);
match (outside_market_price, &protocol_fee.order_class) {
(_, OrderClass::Any) => Some(&protocol_fee.policy),
(true, OrderClass::Limit) => Some(&protocol_fee.policy),
(false, OrderClass::Market) => Some(&protocol_fee.policy),
_ => None,
}
}
}

#[derive(Debug, Copy, Clone, PartialEq)]
Expand Down
1 change: 1 addition & 0 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ pub async fn run(args: Arguments) {
&args.fee_policies,
args.fee_policy_max_partner_fee,
args.protocol_fee_exempt_addresses.as_slice(),
args.enable_multiple_fees,
),
);

Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/competition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ pub struct Amounts {
pub buy: eth::TokenAmount,
}

#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct PriceLimits {
pub sell: eth::TokenAmount,
pub buy: eth::TokenAmount,
Expand Down
51 changes: 31 additions & 20 deletions crates/driver/src/domain/competition/solution/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,29 @@ use {
order::{FeePolicy, Side},
PriceLimits,
},
eth,
eth::{self},
},
bigdecimal::Zero,
};

impl Fulfillment {
/// Applies the protocol fees to the existing fulfillment creating a new
/// one.
pub fn with_protocol_fees(&self, prices: ClearingPrices) -> Result<Self, Error> {
let mut current_fulfillment = self.clone();
for protocol_fee in &self.order().protocol_fees {
current_fulfillment = current_fulfillment.with_protocol_fee(prices, protocol_fee)?;
}
Ok(current_fulfillment)
}

/// Applies the protocol fee to the existing fulfillment creating a new one.
pub fn with_protocol_fee(&self, prices: ClearingPrices) -> Result<Self, Error> {
let protocol_fee = self.protocol_fee_in_sell_token(prices)?;
fn with_protocol_fee(
&self,
prices: ClearingPrices,
protocol_fee: &FeePolicy,
) -> Result<Self, Error> {
let protocol_fee = self.protocol_fee_in_sell_token(prices, protocol_fee)?;

// Increase the fee by the protocol fee
let fee = match self.surplus_fee() {
Expand Down Expand Up @@ -74,17 +88,16 @@ impl Fulfillment {
}

/// Computed protocol fee in surplus token.
fn protocol_fee(&self, prices: ClearingPrices) -> Result<eth::TokenAmount, Error> {
// TODO: support multiple fee policies
if self.order().protocol_fees.len() > 1 {
return Err(Error::MultipleFeePolicies);
}

match self.order().protocol_fees.first() {
Some(FeePolicy::Surplus {
fn protocol_fee(
&self,
prices: ClearingPrices,
protocol_fee: &FeePolicy,
) -> Result<eth::TokenAmount, Error> {
match protocol_fee {
FeePolicy::Surplus {
factor,
max_volume_factor,
}) => self.calculate_fee(
} => self.calculate_fee(
PriceLimits {
sell: self.order().sell.amount,
buy: self.order().buy.amount,
Expand All @@ -93,11 +106,11 @@ impl Fulfillment {
*factor,
*max_volume_factor,
),
Some(FeePolicy::PriceImprovement {
FeePolicy::PriceImprovement {
factor,
max_volume_factor,
quote,
}) => {
} => {
let price_limits = adjust_quote_to_order_limits(
Order {
sell_amount: self.order().sell.amount.0,
Expand All @@ -112,8 +125,7 @@ impl Fulfillment {
)?;
self.calculate_fee(price_limits, prices, *factor, *max_volume_factor)
}
Some(FeePolicy::Volume { factor }) => self.fee_from_volume(prices, *factor),
None => Ok(0.into()),
FeePolicy::Volume { factor } => self.fee_from_volume(prices, *factor),
}
}

Expand Down Expand Up @@ -175,11 +187,12 @@ impl Fulfillment {
fn protocol_fee_in_sell_token(
&self,
prices: ClearingPrices,
protocol_fee: &FeePolicy,
) -> Result<eth::TokenAmount, Error> {
let fee_in_sell_token = match self.order().side {
Side::Buy => self.protocol_fee(prices)?,
Side::Buy => self.protocol_fee(prices, protocol_fee)?,
Side::Sell => self
.protocol_fee(prices)?
.protocol_fee(prices, protocol_fee)?
.0
.checked_mul(prices.buy)
.ok_or(Math::Overflow)?
Expand Down Expand Up @@ -270,8 +283,6 @@ pub fn adjust_quote_to_order_limits(order: Order, quote: Quote) -> Result<PriceL

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("multiple fee policies are not supported yet")]
MultipleFeePolicies,
#[error("orders with non solver determined gas cost fees are not supported")]
ProtocolFeeOnStaticOrder,
#[error(transparent)]
Expand Down
27 changes: 2 additions & 25 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use {
solver::Solver,
Simulator,
},
util::conv::u256::U256Ext,
},
futures::future::try_join_all,
itertools::Itertools,
Expand Down Expand Up @@ -95,7 +94,7 @@ impl Solution {
buy: solution.prices
[&fulfillment.order().buy.token.wrap(solution.weth)],
};
let fulfillment = fulfillment.with_protocol_fee(prices)?;
let fulfillment = fulfillment.with_protocol_fees(prices)?;
trades.push(Trade::Fulfillment(fulfillment))
}
order::Kind::Liquidity => {
Expand Down Expand Up @@ -152,29 +151,7 @@ impl Solution {
.get(&trade.order().buy.token.wrap(self.weth))
.ok_or(error::Scoring::InvalidClearingPrices)?,
};
let custom_prices = scoring::CustomClearingPrices {
sell: match trade.order().side {
order::Side::Sell => trade
.executed()
.0
.checked_mul(uniform_prices.sell)
.ok_or(error::Math::Overflow)?
.checked_ceil_div(&uniform_prices.buy)
.ok_or(error::Math::DivisionByZero)?,
order::Side::Buy => trade.executed().0,
},
buy: match trade.order().side {
order::Side::Sell => trade.executed().0 + trade.fee().0,
order::Side::Buy => {
(trade.executed().0)
.checked_mul(uniform_prices.buy)
.ok_or(error::Math::Overflow)?
.checked_div(uniform_prices.sell)
.ok_or(error::Math::DivisionByZero)?
+ trade.fee().0
}
},
};
let custom_prices = trade.calculate_custom_prices(&uniform_prices)?;
trades.push(scoring::Trade::new(
trade.order().sell,
trade.order().buy,
Expand Down
Loading

0 comments on commit ae05328

Please sign in to comment.