Skip to content

Commit

Permalink
Allow whitelisting addresses for protocol fees discounts
Browse files Browse the repository at this point in the history
  • Loading branch information
m-lord-renkse committed Apr 2, 2024
1 parent 11e356c commit e835e06
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 76 deletions.
51 changes: 51 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,51 @@ pub struct Arguments {
/// `order_events` database table.
#[clap(long, env, default_value = "30d", value_parser = humantime::parse_duration)]
pub order_events_cleanup_threshold: Duration,

/// List of addresses which receives a specific discount for the protocol
/// fees
#[clap(long, env, use_value_delimiter = true)]
pub protocol_fee_discounted_addresses: Vec<ProtocolFeeDiscountedAddresses>,
}

#[derive(Debug, Clone)]
pub struct ProtocolFeeDiscountedAddresses {
pub address: H160,
pub kind: ProtocolFeeDiscountKind,
}

impl FromStr for ProtocolFeeDiscountedAddresses {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split(':');

let address_str = parts
.next()
.context("missing protocol fee discounted addresses")?;
let address_bytes = hex::decode(address_str.trim_start_matches("0x"))
.map_err(|e| anyhow::anyhow!("Failed to parse address: {}", e))?;
let kind = parts
.next()
.context("missing protocol fee discounted kind")?;
let kind = match kind {
"full" => Ok(ProtocolFeeDiscountKind::Full),
_ => Err(anyhow::anyhow!(
"invalid protocol fee discounted kind: {}",
kind
)),
}?;

Ok(ProtocolFeeDiscountedAddresses {
address: H160::from_slice(&address_bytes),
kind,
})
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProtocolFeeDiscountKind {
Full,
}

impl std::fmt::Display for Arguments {
Expand Down Expand Up @@ -262,6 +307,7 @@ impl std::fmt::Display for Arguments {
auction_update_interval,
max_settlement_transaction_wait,
s3,
protocol_fee_discounted_addresses,
} = self;

write!(f, "{}", shared)?;
Expand Down Expand Up @@ -314,6 +360,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,
"protocol_fee_discounted_addresses: {:?}",
protocol_fee_discounted_addresses
)?;
writeln!(
f,
"fee_policy_max_partner_fee: {:?}",
Expand Down
52 changes: 46 additions & 6 deletions crates/autopilot/src/domain/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ mod policy;

use {
crate::{
arguments,
arguments::{self, ProtocolFeeDiscountKind},
boundary::{self},
domain::{self},
},
app_data::Validator,
derive_more::Into,
itertools::Itertools,
primitive_types::U256,
model::DomainSeparator,
primitive_types::{H160, U256},
prometheus::core::Number,
std::str::FromStr,
std::{collections::HashMap, str::FromStr, sync::Arc},
};

enum OrderClass {
Expand Down Expand Up @@ -51,32 +52,50 @@ impl From<arguments::FeePolicy> for ProtocolFee {
}
}

pub type ProtocolFeeDiscountedAddresses = HashMap<H160, ProtocolFeeDiscountKind>;

pub struct ProtocolFees {
fee_policies: Vec<ProtocolFee>,
max_partner_fee: FeeFactor,
/// List of addresses which receives a specific discount for the protocol
/// fees
protocol_fee_discounted_addresses: Arc<ProtocolFeeDiscountedAddresses>,
}

impl ProtocolFees {
pub fn new(
fee_policies: &[arguments::FeePolicy],
fee_policy_max_partner_fee: FeeFactor,
protocol_fee_discounted_addresses: &[arguments::ProtocolFeeDiscountedAddresses],
) -> Self {
Self {
fee_policies: fee_policies
.iter()
.cloned()
.map(ProtocolFee::from)
.collect(),
protocol_fee_discounted_addresses: Arc::new(
protocol_fee_discounted_addresses
.iter()
.map(|protocol_fee_discounted_addresses| {
(
protocol_fee_discounted_addresses.address,
protocol_fee_discounted_addresses.kind,
)
})
.collect::<HashMap<_, _>>(),
),
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: &ProtocolFees,
&self,
order: boundary::Order,
quote: &domain::Quote,
domain_separator: &DomainSeparator,
) -> domain::Order {
// If the partner fee is specified, it overwrites the current volume fee policy
if let Some(validated_app_data) = order
Expand All @@ -92,7 +111,7 @@ impl ProtocolFees {
let fee_policy = vec![Policy::Volume {
factor: FeeFactor::try_from_capped(
partner_fee.bps.into_f64() / 10_000.0,
protocol_fees.max_partner_fee.into(),
self.max_partner_fee.into(),
)
.unwrap(),
}];
Expand All @@ -110,9 +129,18 @@ impl ProtocolFees {
buy: quote.buy_amount,
fee: quote.fee,
};
let protocol_fees = protocol_fees
let apply_protocol_fee = if let Ok(Some(address)) = order
.signature
.recover(domain_separator, &order.data.hash_struct())
{
self.driver_must_apply_protocol_fee(address.signer)
} else {
false
};
let protocol_fees = self
.fee_policies
.iter()
.filter(|_| apply_protocol_fee)
// TODO: support multiple fee policies
.find_map(|fee_policy| {
let outside_market_price = boundary::is_order_outside_market_price(&order_, &quote_, order.data.kind);
Expand All @@ -132,6 +160,18 @@ impl ProtocolFees {
.collect_vec();
boundary::order::to_domain(order, protocol_fees)
}

/// Internal function in `ProtocolFees` in order to determine whether the
/// driver has to apple the protocol fee to an order/solution
fn driver_must_apply_protocol_fee(&self, address: H160) -> bool {
let is_address_whitelisted_for_full_discount = self
.protocol_fee_discounted_addresses
.get(&address)
.map(|kind| *kind == ProtocolFeeDiscountKind::Full)
.unwrap_or_default();

!is_address_whitelisted_for_full_discount
}
}

#[derive(Debug, Copy, Clone, PartialEq)]
Expand Down
11 changes: 9 additions & 2 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ pub async fn run(args: Arguments) {
},
));

let domain_separator = DomainSeparator::new(chain_id, eth.contracts().settlement().address());

if let Some(ethflow_contract) = args.ethflow_contract {
let start_block = determine_ethflow_indexing_start(
&skip_event_sync_start,
Expand Down Expand Up @@ -512,7 +514,7 @@ pub async fn run(args: Arguments) {
web3.clone(),
quoter.clone(),
Box::new(custom_ethflow_order_parser),
DomainSeparator::new(chain_id, eth.contracts().settlement().address()),
domain_separator,
eth.contracts().settlement().address(),
);
let broadcaster_event_updater = Arc::new(
Expand Down Expand Up @@ -558,7 +560,12 @@ 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::ProtocolFees::new(&args.fee_policies, args.fee_policy_max_partner_fee),
domain::ProtocolFees::new(
&args.fee_policies,
args.fee_policy_max_partner_fee,
args.protocol_fee_discounted_addresses.as_slice(),
),
domain_separator,
);

let liveness = Arc::new(Liveness::new(args.max_auction_age));
Expand Down
8 changes: 6 additions & 2 deletions crates/autopilot/src/solvable_orders.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::{
domain,
domain::{self},
infra::{self, banned},
},
anyhow::Result,
Expand All @@ -13,6 +13,7 @@ use {
order::{Order, OrderClass, OrderUid},
signature::Signature,
time::now_in_epoch_seconds,
DomainSeparator,
},
number::conversions::u256_to_big_decimal,
primitive_types::{H160, H256, U256},
Expand Down Expand Up @@ -81,6 +82,7 @@ pub struct SolvableOrdersCache {
weth: H160,
limit_order_price_factor: BigDecimal,
protocol_fees: domain::ProtocolFees,
domain_separator: DomainSeparator,
}

type Balances = HashMap<Query, U256>;
Expand All @@ -105,6 +107,7 @@ impl SolvableOrdersCache {
weth: H160,
limit_order_price_factor: BigDecimal,
protocol_fees: domain::ProtocolFees,
domain_separator: DomainSeparator,
) -> Arc<Self> {
let self_ = Arc::new(Self {
min_order_validity_period,
Expand All @@ -122,6 +125,7 @@ impl SolvableOrdersCache {
weth,
limit_order_price_factor,
protocol_fees,
domain_separator,
});
tokio::task::spawn(
update_task(Arc::downgrade(&self_), update_interval, current_block)
Expand Down Expand Up @@ -242,7 +246,7 @@ impl SolvableOrdersCache {
.into_iter()
.filter_map(|order| {
if let Some(quote) = db_solvable_orders.quotes.get(&order.metadata.uid.into()) {
Some(domain::ProtocolFees::apply(&self.protocol_fees, order, quote))
Some(self.protocol_fees.apply(order, quote, &self.domain_separator))
} else {
tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing");
None
Expand Down
Loading

0 comments on commit e835e06

Please sign in to comment.