-
Notifications
You must be signed in to change notification settings - Fork 83
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
Define protocol fee params and forward to driver #2098
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will the fee information be propagated for the frontend to consume? Are we planning to put it on the trade records we write into the database?
Alternatively we could have the API set the fee policy whenever orders are created. Curious if you have thought about the trade-offs?
It will follow the same flow as solver fees, meaning, fees that are unknown before sending the auction to solvers for solving and also not communicated back to the autopilot before settling, are only possible to evaluate once the transaction is mined and |
Once I restart the autopilot with updated fee policies I want all orders to follow them from that point in time. It's easier to debug and evaluate the change. |
This requires information about the fee policy though right (the surplus fee can be observed on chain)? What if the fee policy changes between the auction being created and the settlement submission being mined (e.g. on restarts) or if it's different across staging and production? |
Oh, you meant the fee policy (I was talking about the executed protocol fee). |
The other alternative would be to save the fee policies to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configuration and propagation of current fee policies look alright to me.
I'm worried that we might end up with a lot of additional CLI args in the autopilot due to different fee policies but for the start where we only have a single fee policy this seems sufficient.
crates/autopilot/src/arguments.rs
Outdated
)] | ||
pub protocol_fee_factor: f64, | ||
|
||
/// Cap protocol fee with a percentage of the order's volume. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the --help
text refers to a percentage
when it's actually a factor that is to be understood as a percentage. Slight difference that bit us in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are covered with the parsing function that makes sure it's a factor. We also have a factor in the variable name. I wanted to keep the comments focused on the meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sure that the value entered is in [0,1]
but it doesn't make sure that what you provided is actually what you intended. Take slippage for example where it makes sense to configure 0.5%
. We had a CLI argument in the past that claimed it was a percentage so we passed in 0.5
which then ended up allowing 50%
relative slippage. Despite using shared::arguments::parse_percentage_factor
it took us a while to notice the error. 😬
We also have a factor in the variable name.
This is true but I think with MFW's recent push to make the repo more contributor friendly it still makes sense to be precise in the --help
text.
It could be as short as:
Cap protocol fee with a percentage of the order's volume. (1.0 meaning 100%)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only nits, but I think it's important we pay attention to the way we document things and where. E.g. the autopilot is agnostic to the fact that the fee "is taken from surplus". It just warns participants in the protocol that if they match an order they may have to pay the protocol something at the end of the accounting period.
Our implementation of the driver then is the one that decides to hide fees from solver engines and instead "take" those fees in the form of positive slippage.
We should also make sure we use a common language when talking about the different types of fees (that will be consistent throughout or developer docs, marketing, communication with solvers etc).
Lastly, we should make it easy for maintainers to configure these parameters in a way that are intuitive and contextually clear.
|
||
#[derive(Debug, Deserialize)] | ||
#[serde(rename_all = "camelCase", deny_unknown_fields)] | ||
enum FeePolicy { | ||
QuoteDeviation { factor: f64, volume_cap_factor: f64 }, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the dto also have documentation (so that it can be generated for the swagger docs)?
f783d68 is a significant change: While thinking how to save the fee policies to the database, I figured that I have to design everything from the start, to support all ways of taking fees:
Each of these can be modelled with 3 parameters: price_improvement_percent, volume_percent, absolute_fee:
If we have all 3 fees defines like in the case of (1), we will calculate all 3 and take the smallest (which is equivalent to calculating price_improvement with volume cap and absolute cap). |
crates/autopilot/src/arguments.rs
Outdated
/// A list of fee policies in the following format: `[LimitOrderOutOfMarket:0.1:0.2,LimitOrderInMarket::2.5]` | ||
/// | ||
#[clap(long, env, use_value_delimiter = true)] | ||
pub fee_policies: Vec<FeePolicy>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would it semantically mean to define multiple entries with the same policy but different parameters? That the protocol has multiple fees for the same order type? This sounds confusing to me.
I don't foresee us using vastly different parameters for in market vs out of market orders (if so we can define them later as two different fields). I therefore think we should just define a struct with
- price improvement factor
- volume % cap
- absolute cap
- whether or not to include in-market priced limit orders
On a per order basis, I think we should then just signal the concrete fee for this order, which can be one of three (maybe 4) in my opinion, potentially chained in case integrators want to charge additional fees.
- % of price improvement (WE ONLY NEED THIS FOR NOW)
- % of volume (e.g. 0.1% of volume)
- Absolute amount (e.g. $1 per trade)
- maybe % of surplus (although the incentives are too obviously bad to even experiment with this IMO)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would it semantically mean to define multiple entries with the same policy but different parameters?
It enables us to take a quote deviation fee, and after that another fee as percent of volume (meaning we can chain multiple different fees for a single order). IIUC, this is something we have to support for integrators.
One could argue that we should summarize all of them to a single value and send that single value to the driver
, but can't do that since not all types of fees are known upfront (volume percent and absolute fee are, quote deviation not).
Then, since db migrations are painful, I decided to model the db table for fee polices in a way that supports all foreseeable types of policies and not just for price improvement type. That design was created to be memory efficient and decent performance wise (I could have created a separate table for each fee type but then the fetching would be slower).
Why
pub enum FeePolicy {
LimitOrderOutOfMarket(FeePolicyParams),
LimitOrderInMarket(FeePolicyParams),
}
Because I expect to add a third type - TWAP orders.
I hope my reasonings are clearer now. Note that this PR is not yet ready for merging, I just need to get the approval for the last commit that my new logic is ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I hoped that I could support these two types of fees in the first iteration, since it will be trivial with this design:
% of price improvement (WE ONLY NEED THIS FOR NOW)
% of volume (e.g. 0.1% of volume)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, this is something we have to support for integrators.
My vision for integrators is that they specify their fee policy as part of the app data and it doesn't become part of the arguments we start the core protocol with.
The core protocol will experiment with different fees (price improvements, volume based, etc) but I foresee its fee config to be relatively concise and not a chain of many different fee compositions. Also, the protocol can only charge fees on order types that are a first class citizen in the core protocol (limit orders).
Because I expect to add a third type - TWAP orders.
TWAP orders are not part of the core CoW Protocol. They are a set of orders that semantically belong together and are orchestrated and placed from a watch tower, but as far as CoW protocol is concerned users are simply placing limit orders in awkwardly consistent periods.
So my goal here is to split what the protocol boots up with as a fee policy from the different types of protocol fees we can attach to each order that is sent to the solvers.
E.g. the protocol may define that we want to take 0.1% volume fee on all orders. This is what the autopilot should be started with.
If an integrator (e.g. CoW Swap's TWAP feature or Metamask) wants to charge 0.85% volume fee on top, then they can create an order specifying this in the app data and what we end up sending to the driver is two fees (0.1% for the protocol, 0.85% for the integrator), both of which will be charged from solvers if they settle this order at the end. Potentially the protocol will demand a cut (cf. Apple charging 30% of all app store purchases) from the integrator fees.
To sum up, I think we should have a FeePolicy
struct on the autopilot arguments and the a protocol_fee: Vec<Fee>
on the order itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, not using configuration type for autopilot domain makes sense, will split it.
Do you think we should keep the possibility that 2 different policies (for example quote deviation and volume based) can be sent for a single order to the driver, at the same time?
If you don't like having a third enum variant for TWAP orders, how would you model a situation where we set quote deviation policy for limit orders and volume based policy for TWAP orders using:
price improvement factor
volume % cap
absolute cap
whether or not to include in-market priced limit orders
We talked about not supporting this on slack, if so, I can just add another whether to include twap orders
flag.
@fleupold @MartinquaXD re-asking for review, third time's a charm :) This setup now fits nicely with the db table: #2118 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all the turn-around I think we are getting there on the naming.
My remaining worry now is about simply inlining the increasingly complex fee policy parsing and mapping logic into the main autopilot run-loop component, which is a recipe for complexity.
/// The fee is taken in `sell` token for `sell` orders and in `buy` | ||
/// token for `buy` orders. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto - what currency does the protocol charge solvers the fee in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you mentioned in the comment ☝️ , the protocol collects the protocol fees in different tokens. The question is, whether the conversion to ETH/COW/else should be done in the autopilot or later in the script for accounting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conversion will be done in the script similarly to how we do it in solver rewards script, using the prices
table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but it will still be charged in some currency right? Solvers will receive a bill from us at the end of the week saying: order x got 100 USDT price improvement, please pay us y
.
y
will be some percentage of 100 USDT, but in which currency (USDT, COW, ETH)? This is important for someone consuming this model to know so that they can make sure they convert and keep the fees in the right currency if they want to be neutral.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/autopilot/src/run_loop.rs
Outdated
|
||
// todo https://github.com/cowprotocol/services/issues/2115 | ||
// skip protocol fee for TWAP limit orders | ||
OrderClass::Limit(_) => vec![fee_policy_to_domain(&fee_policy)], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this somehow take fee_policy_skip_market_orders
into account and include the match statement on order.metadata.class
?
I'd create a new protocol_fee module and have a struct apply the fee. Note that in the future we will likely have logic that based on app_data adds additional fees here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be implemented as a separate task: #2092
Let's also move this logic into a separate file as part of ☝️ task, to unblock this PR and merge it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only nits at this point.
/// The fee is taken in `sell` token for `sell` orders and in `buy` | ||
/// token for `buy` orders. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fees get taken in the opposite token (compared to surplus fee). This is because it would allow us to easily implement the fee model in the driver without solvers having to know about it, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My initial idea was that we need to take the fee as some percent of the executed_amount
which is always expressed in the sell token for sell orders and in buy token for buy orders, but this is still open and could change during implementation if we figure out we actually need something else.
Merging this PR to unblock @narcis96 so that he could work on other tasks in parallel. |
Description
Tackles #2083 and #2084.
Defines fee policy configuration parameters. Based on them, we can take the protocol fee as a cut from price improvement or as a percent of the order volume (fixed fee in absolute value will be implemented later).
On top of that, we also need two flags, for skipping in-market limit orders and twap orders, since default initial configuration will be to run the fee experiment only for out-of-market limit orders.
Note that the quote is not forwarded to the
driver
at this moment, since it is not needed for out-of-market limit orders.Also note that I am missing the cap for absolute value of protocol fee (default value 1000$ or 0.5ETH) but this cap is still discussed (will be added later if needed).
Skipping forwarding protocol fee parameters for in-market limit orders will be done in a separate PR.
Changes