Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow whitelisting addresses for protocol fees discounts #2573

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ pub struct Arguments {
#[clap(long, env, default_value = "0.01")]
pub fee_policy_max_partner_fee: FeeFactor,

/// List of addresses which are exempt from the protocol
/// fees
#[clap(long, env, use_value_delimiter = true)]
pub protocol_fee_exempt_addresses: Vec<H160>,

/// Arguments for uploading information to S3.
#[clap(flatten)]
pub s3: infra::persistence::cli::S3,
Expand Down Expand Up @@ -262,6 +267,7 @@ impl std::fmt::Display for Arguments {
auction_update_interval,
max_settlement_transaction_wait,
s3,
protocol_fee_exempt_addresses,
} = self;

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

use {
crate::{
arguments,
arguments::{self},
boundary::{self},
domain::{self},
},
app_data::Validator,
derive_more::Into,
itertools::Itertools,
primitive_types::U256,
primitive_types::{H160, U256},
prometheus::core::Number,
std::str::FromStr,
std::{collections::HashSet, str::FromStr},
};

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

pub type ProtocolFeeExemptAddresses = HashSet<H160>;

pub struct ProtocolFees {
fee_policies: Vec<ProtocolFee>,
max_partner_fee: FeeFactor,
/// List of addresses which are exempt from the protocol
/// fees
protocol_fee_exempt_addresses: ProtocolFeeExemptAddresses,
}

impl ProtocolFees {
pub fn new(
fee_policies: &[arguments::FeePolicy],
fee_policy_max_partner_fee: FeeFactor,
protocol_fee_exempt_addresses: &[H160],
) -> Self {
Self {
fee_policies: fee_policies
.iter()
.cloned()
.map(ProtocolFee::from)
.collect(),
protocol_fee_exempt_addresses: protocol_fee_exempt_addresses
.iter()
.cloned()
.collect::<HashSet<_>>(),
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,
order: boundary::Order,
quote: &domain::Quote,
) -> domain::Order {
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
.metadata
Expand All @@ -92,7 +98,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,26 +116,33 @@ impl ProtocolFees {
buy: quote.buy_amount,
fee: quote.fee,
};
let protocol_fees = protocol_fees
.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();
let protocol_fees = if self
.protocol_fee_exempt_addresses
.contains(&order.metadata.owner)
{
vec![]
} 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()
};
boundary::order::to_domain(order, protocol_fees)
}
}
Expand Down
6 changes: 5 additions & 1 deletion crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,11 @@ 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_exempt_addresses.as_slice(),
),
);

let liveness = Arc::new(Liveness::new(args.max_auction_age));
Expand Down
4 changes: 2 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 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::ProtocolFees::apply(&self.protocol_fees, order, quote))
Some(self.protocol_fees.apply(order, quote))
} else {
tracing::warn!(order_uid = %order.metadata.uid, "order is skipped, quote is missing");
None
Expand Down
81 changes: 59 additions & 22 deletions crates/e2e/tests/e2e/protocol_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,17 @@ async fn combined_protocol_fees(web3: Web3) {
let mut onchain = OnchainComponents::deploy(web3.clone()).await;

let [solver] = onchain.make_solvers(to_wei(200)).await;
let [trader] = onchain.make_accounts(to_wei(200)).await;
let [limit_order_token, market_order_token, partner_fee_order_token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(20), to_wei(20))
.await;
let [trader, trader_exempt] = onchain.make_accounts(to_wei(200)).await;
let [limit_order_token, market_order_token, partner_fee_order_token, fee_exempt_token] =
onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(20), to_wei(20))
.await;

for token in &[
&limit_order_token,
&market_order_token,
&partner_fee_order_token,
&fee_exempt_token,
] {
token.mint(solver.address(), to_wei(1000)).await;
tx!(
Expand All @@ -82,24 +84,28 @@ async fn combined_protocol_fees(web3: Web3) {
to_wei(1000)
)
);
for trader in &[&trader, &trader_exempt] {
tx!(
trader.account(),
token.approve(onchain.contracts().uniswap_v2_router.address(), to_wei(100))
);
}
}

for trader in &[&trader, &trader_exempt] {
tx!(
trader.account(),
token.approve(onchain.contracts().uniswap_v2_router.address(), to_wei(100))
onchain
.contracts()
.weth
.approve(onchain.contracts().allowance, to_wei(100))
);
tx_value!(
trader.account(),
to_wei(100),
onchain.contracts().weth.deposit()
);
}

tx!(
trader.account(),
onchain
.contracts()
.weth
.approve(onchain.contracts().allowance, to_wei(100))
);
tx_value!(
trader.account(),
to_wei(100),
onchain.contracts().weth.deposit()
);
tx!(
solver.account(),
onchain
Expand All @@ -111,6 +117,10 @@ async fn combined_protocol_fees(web3: Web3) {
let autopilot_config = vec![
ProtocolFeesConfig(vec![limit_surplus_policy, market_price_improvement_policy]).to_string(),
"--fee-policy-max-partner-fee=0.02".to_string(),
format!(
"--protocol-fee-exempt-addresses={:?}",
trader_exempt.address()
),
];
let services = Services::new(onchain.contracts()).await;
services
Expand All @@ -126,12 +136,13 @@ async fn combined_protocol_fees(web3: Web3) {
tracing::info!("Acquiring quotes.");
let quote_valid_to = model::time::now_in_epoch_seconds() + 300;
let sell_amount = to_wei(10);
let [limit_quote_before, market_quote_before, partner_fee_quote] =
let [limit_quote_before, market_quote_before, partner_fee_quote, fee_exempt_quote] =
futures::future::try_join_all(
[
&limit_order_token,
&market_order_token,
&partner_fee_order_token,
&fee_exempt_token,
]
.map(|token| {
get_quote(
Expand All @@ -147,7 +158,7 @@ async fn combined_protocol_fees(web3: Web3) {
.await
.unwrap()
.try_into()
.expect("Expected exactly three elements");
.expect("Expected exactly four elements");

let market_price_improvement_order = OrderCreation {
sell_amount,
Expand Down Expand Up @@ -182,6 +193,16 @@ async fn combined_protocol_fees(web3: Web3) {
&onchain.contracts().domain_separator,
SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()),
);
let fee_exempt_token_order = OrderCreation {
sell_amount,
buy_amount: to_wei(5),
..sell_order_from_quote(&fee_exempt_quote)
}
.sign(
EcdsaSigningScheme::Eip712,
&onchain.contracts().domain_separator,
SecretKeyRef::from(&SecretKey::from_slice(trader_exempt.private_key()).unwrap()),
);

tracing::info!("Rebalancing AMM pools for market & limit order.");
onchain
Expand Down Expand Up @@ -226,19 +247,20 @@ async fn combined_protocol_fees(web3: Web3) {
.try_into()
.expect("Expected exactly two elements");

let [market_price_improvement_uid, limit_surplus_order_uid, partner_fee_order_uid] =
let [market_price_improvement_uid, limit_surplus_order_uid, partner_fee_order_uid, fee_exempt_token_order_uid] =
futures::future::try_join_all(
[
&market_price_improvement_order,
&limit_surplus_order,
&partner_fee_order,
&fee_exempt_token_order,
]
.map(|order| services.create_order(order)),
)
.await
.unwrap()
.try_into()
.expect("Expected exactly three elements");
.expect("Expected exactly four elements");

tracing::info!("Waiting for orders to trade.");
let metadata_updated = || async {
Expand All @@ -248,6 +270,7 @@ async fn combined_protocol_fees(web3: Web3) {
&market_price_improvement_uid,
&limit_surplus_order_uid,
&partner_fee_order_uid,
&fee_exempt_token_order_uid,
]
.map(|uid| async {
services
Expand Down Expand Up @@ -295,6 +318,13 @@ async fn combined_protocol_fees(web3: Web3) {
// see `limit_surplus_policy.factor`, which is 0.3
assert!(limit_executed_surplus_fee_in_buy_token >= limit_quote_diff * 3 / 10);

let fee_exempt_order = services
.get_order(&fee_exempt_token_order_uid)
.await
.unwrap();
let fee_exempt_surplus_fee_in_buy_token =
surplus_fee_in_buy_token(&fee_exempt_order, &fee_exempt_quote.quote);

let balance_after = market_order_token
.balance_of(onchain.contracts().gp_settlement.address())
.call()
Expand All @@ -315,6 +345,13 @@ async fn combined_protocol_fees(web3: Web3) {
.await
.unwrap();
assert_approximately_eq!(partner_fee_executed_surplus_fee_in_buy_token, balance_after);

let balance_after = fee_exempt_token
.balance_of(onchain.contracts().gp_settlement.address())
.call()
.await
.unwrap();
assert_approximately_eq!(fee_exempt_surplus_fee_in_buy_token, balance_after);
}

async fn get_quote(
Expand Down
Loading