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 Mar 28, 2024
1 parent a5456a9 commit bf3f8f5
Show file tree
Hide file tree
Showing 16 changed files with 582 additions and 167 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ reqwest = "0.11"
serde = "1.0"
serde_json = "1.0"
serde_with = "3.0"
strum = { workspace = true }
tap = "1.0.1"
thiserror = "1.0"
tokio = { version = "1.22", features = ["macros", "rt-multi-thread", "signal", "time"] }
Expand Down
57 changes: 16 additions & 41 deletions crates/driver/src/domain/competition/solution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ use {
},
infra::{
blockchain::{self, Ethereum},
config::file::FeeHandler,
simulator,
solver::Solver,
solver::{protocol_fees::ProtocolFees, Solver},
Simulator,
},
util::conv::u256::U256Ext,
Expand Down Expand Up @@ -40,14 +39,14 @@ type Prices = HashMap<eth::TokenAddress, eth::U256>;
/// [`competition::Auction`]. See also [`settlement::Settlement`].
#[derive(Clone)]
pub struct Solution {
id: Id,
trades: Vec<Trade>,
prices: Prices,
interactions: Vec<Interaction>,
solver: Solver,
score: SolverScore,
weth: eth::WethAddress,
gas: Option<eth::Gas>,
pub id: Id,
pub trades: Vec<Trade>,
pub prices: Prices,
pub interactions: Vec<Interaction>,
pub solver: Solver,
pub score: SolverScore,
pub weth: eth::WethAddress,
pub gas: Option<eth::Gas>,
}

impl Solution {
Expand All @@ -61,7 +60,7 @@ impl Solution {
score: SolverScore,
weth: eth::WethAddress,
gas: Option<eth::Gas>,
fee_handler: FeeHandler,
protocol_fees: &ProtocolFees,
) -> Result<Self, error::Solution> {
let solution = Self {
id,
Expand All @@ -82,33 +81,9 @@ impl Solution {
return Err(error::Solution::InvalidClearingPrices);
}

// Apply protocol fees only if the drivers is set to handler the fees
if fee_handler != FeeHandler::Driver {
return Ok(solution);
}

let mut trades = Vec::with_capacity(solution.trades.len());
for trade in solution.trades {
match &trade {
Trade::Fulfillment(fulfillment) => match fulfillment.order().kind {
order::Kind::Market | order::Kind::Limit { .. } => {
let prices = ClearingPrices {
sell: solution.prices
[&fulfillment.order().sell.token.wrap(solution.weth)],
buy: solution.prices
[&fulfillment.order().buy.token.wrap(solution.weth)],
};
let fulfillment = fulfillment.with_protocol_fee(prices)?;
trades.push(Trade::Fulfillment(fulfillment))
}
order::Kind::Liquidity => {
trades.push(trade);
}
},
Trade::Jit(_) => trades.push(trade),
}
}
Ok(Self { trades, ..solution })
protocol_fees
.apply_to_solution(solution)
.map_err(error::Solution::ProtocolFee)
}

/// The ID of this solution.
Expand Down Expand Up @@ -493,7 +468,7 @@ impl Id {
}

pub mod error {
use super::*;
use {super::*, crate::infra::solver::protocol_fees};

#[derive(Debug, thiserror::Error)]
pub enum Merge {
Expand Down Expand Up @@ -544,8 +519,8 @@ pub mod error {
pub enum Solution {
#[error("invalid clearing prices")]
InvalidClearingPrices,
#[error(transparent)]
ProtocolFee(#[from] fee::Error),
#[error("error applying protocol fee")]
ProtocolFee(#[source] protocol_fees::Error),
}

#[derive(Debug, thiserror::Error)]
Expand Down
2 changes: 1 addition & 1 deletion crates/driver/src/domain/competition/solution/trade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub enum Trade {
/// A trade which fulfills an order from the auction.
#[derive(Debug, Clone)]
pub struct Fulfillment {
order: competition::Order,
pub order: competition::Order,
/// The amount executed by this fulfillment. See [`order::Partial`]. If the
/// order is not partial, the executed amount must equal the amount from the
/// order.
Expand Down
7 changes: 7 additions & 0 deletions crates/driver/src/domain/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ impl From<H160> for WethAddress {
}
}

#[allow(clippy::from_over_into)]
impl Into<H160> for WethAddress {
fn into(self) -> H160 {
self.0.into()
}
}

impl From<H160> for TokenAddress {
fn from(value: H160) -> Self {
Self(value.into())
Expand Down
26 changes: 23 additions & 3 deletions crates/driver/src/infra/config/file/load.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
use {
crate::{
domain::eth,
infra::{self, blockchain, config::file, liquidity, mempool, simulator, solver},
infra::{
self,
blockchain,
config::file,
liquidity,
mempool,
simulator,
solver::{self, protocol_fees::ProtocolFees},
},
},
futures::future::join_all,
std::path::Path,
std::{collections::HashMap, path::Path},
tokio::fs,
};

Expand Down Expand Up @@ -76,7 +84,19 @@ pub async fn load(chain: eth::ChainId, path: &Path) -> infra::Config {
},
request_headers: config.request_headers,
rank_by_surplus_date: config.rank_by_surplus_date,
fee_handler: config.fee_handler,
protocol_fees: ProtocolFees::new(
config.fee_handler,
config
.protocol_fee_discounted_addresses
.into_iter()
.map(|protocol_fee_discounted_addresses| {
(
protocol_fee_discounted_addresses.address,
protocol_fee_discounted_addresses.kind,
)
})
.collect::<HashMap<_, _>>(),
),
}
}))
.await,
Expand Down
21 changes: 21 additions & 0 deletions crates/driver/src/infra/config/file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub use load::load;
use {
crate::{domain::eth, util::serialize},
primitive_types::H160,
reqwest::Url,
serde::{Deserialize, Serialize},
serde_with::serde_as,
solver::solver::Arn,
std::{collections::HashMap, time::Duration},
strum::{Display, EnumString},
};

mod load;
Expand Down Expand Up @@ -192,6 +194,25 @@ struct SolverConfig {
/// Determines whether the `solver` or the `driver` handles the fees
#[serde(default)]
fee_handler: FeeHandler,

/// List of addresses which receives a specific discount for the protocol
/// fees
#[serde(default)]
protocol_fee_discounted_addresses: Vec<ProtocolFeeDiscountedAddresses>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct ProtocolFeeDiscountedAddresses {
address: H160,
kind: ProtocolFeeDiscountKind,
}

#[derive(Debug, Clone, Display, EnumString, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[strum(serialize_all = "kebab-case")]
pub enum ProtocolFeeDiscountKind {
Full,
}

#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
Expand Down
1 change: 1 addition & 0 deletions crates/driver/src/infra/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ pub use {
config::Config,
mempool::Mempool,
simulator::Simulator,
solver::protocol_fees::{self, Error},
};
111 changes: 35 additions & 76 deletions crates/driver/src/infra/solver/dto/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use {
eth,
liquidity,
},
infra::config::file::FeeHandler,
infra::solver::protocol_fees::ProtocolFees,
util::{
conv::{rational_to_big_decimal, u256::U256Ext},
serialize,
Expand All @@ -26,7 +26,7 @@ impl Auction {
auction: &competition::Auction,
liquidity: &[liquidity::Liquidity],
weth: eth::WethAddress,
fee_handler: FeeHandler,
protocol_fees: &ProtocolFees,
) -> Self {
let mut tokens: HashMap<eth::H160, _> = auction
.tokens()
Expand Down Expand Up @@ -68,67 +68,7 @@ impl Auction {
orders: auction
.orders()
.iter()
.map(|order| {
let mut available = order.available(weth);
// In case of volume based fees, fee withheld by driver might be higher than the
// surplus of the solution. This would lead to violating limit prices when
// driver tries to withhold the volume based fee. To avoid this, we artificially
// adjust the order limit amounts (make then worse) before sending to solvers,
// to force solvers to only submit solutions with enough surplus to cover the
// fee.
//
// https://github.com/cowprotocol/services/issues/2440
if fee_handler == FeeHandler::Driver {
if let Some(fees::FeePolicy::Volume { factor }) =
order.protocol_fees.first()
{
match order.side {
Side::Buy => {
// reduce sell amount by factor
available.sell.amount = available
.sell
.amount
.apply_factor(1.0 / (1.0 + factor))
.unwrap_or_default();
}
Side::Sell => {
// increase buy amount by factor
available.buy.amount = available
.buy
.amount
.apply_factor(1.0 / (1.0 - factor))
.unwrap_or_default();
}
}
}
}
Order {
uid: order.uid.into(),
sell_token: available.sell.token.into(),
buy_token: available.buy.token.into(),
sell_amount: available.sell.amount.into(),
buy_amount: available.buy.amount.into(),
fee_amount: available.user_fee.into(),
kind: match order.side {
competition::order::Side::Buy => Kind::Buy,
competition::order::Side::Sell => Kind::Sell,
},
partially_fillable: order.is_partial(),
class: match order.kind {
competition::order::Kind::Market => Class::Market,
competition::order::Kind::Limit { .. } => Class::Limit,
competition::order::Kind::Liquidity => Class::Liquidity,
},
fee_policies: (fee_handler == FeeHandler::Solver).then_some(
order
.protocol_fees
.iter()
.cloned()
.map(Into::into)
.collect(),
),
}
})
.map(|order| protocol_fees.apply_to_order(order, weth))
.collect(),
liquidity: liquidity
.iter()
Expand Down Expand Up @@ -275,39 +215,58 @@ pub struct Auction {
#[serde_as]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct Order {
pub struct Order {
#[serde_as(as = "serialize::Hex")]
uid: [u8; order::UID_LEN],
sell_token: eth::H160,
buy_token: eth::H160,
pub uid: [u8; order::UID_LEN],
pub sell_token: eth::H160,
pub buy_token: eth::H160,
#[serde_as(as = "serialize::U256")]
sell_amount: eth::U256,
pub sell_amount: eth::U256,
#[serde_as(as = "serialize::U256")]
buy_amount: eth::U256,
pub buy_amount: eth::U256,
#[serde_as(as = "serialize::U256")]
fee_amount: eth::U256,
kind: Kind,
partially_fillable: bool,
class: Class,
pub fee_amount: eth::U256,
pub kind: Kind,
pub partially_fillable: bool,
pub class: Class,
#[serde(skip_serializing_if = "Option::is_none")]
fee_policies: Option<Vec<FeePolicy>>,
pub fee_policies: Option<Vec<FeePolicy>>,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
enum Kind {
pub enum Kind {
Sell,
Buy,
}

impl From<Side> for Kind {
fn from(value: Side) -> Self {
match value {
Side::Buy => Self::Buy,
Side::Sell => Self::Sell,
}
}
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
enum Class {
pub enum Class {
Market,
Limit,
Liquidity,
}

impl From<order::Kind> for Class {
fn from(value: order::Kind) -> Self {
match value {
order::Kind::Market => Self::Market,
order::Kind::Limit { .. } => Self::Limit,
order::Kind::Liquidity => Self::Liquidity,
}
}
}

#[serde_as]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down
6 changes: 5 additions & 1 deletion crates/driver/src/infra/solver/dto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ mod auction;
mod notification;
mod solution;

pub use {auction::Auction, notification::Notification, solution::Solutions};
pub use {
auction::{Auction, FeePolicy, Order},
notification::Notification,
solution::Solutions,
};

#[derive(Debug, thiserror::Error)]
#[error("{0}")]
Expand Down
Loading

0 comments on commit bf3f8f5

Please sign in to comment.