-
Notifications
You must be signed in to change notification settings - Fork 89
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
Price improvemenet fee policy autopilot domain #2378
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.
I'm still not entirely happy about the factoring here (it seems somewhat convoluted and not very straightforward to me).
Here is my mental model:
- As far as the domain is concerned there is one component responsible for all logic of how fees are applied to orders (fee.apply(order, quote) -> OrderWithFees)
- This fee component itself can apply many different types of fee (policies) to a single order (UI integration fee, price improvement fee, etc) based on what the order specifies (ie in its AppData, how much CoW tokens the user holds, etc.) and what the autopilot has been configured with.
- For this, each fee policy (builder) can apply itself to an order
policy.with(order) -> Option<Fee>
- The fee component collects all fees into the order and returns it.
I'm not entirely sure if factoring 3 out into a builder is overkill (we can remove it for now), but I'm worried this logic will explode in complexity as soon as we add more advanced fees (like a volume fee that is specified by the interface or fee discounts based on your CoW token holdings)
boundary::OrderClass::Market => { | ||
if self.fee_policy_skip_market_orders { | ||
vec![] | ||
} else { | ||
vec![self.policy] | ||
vec![self.policy_builder.build_with(quote)] |
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 information whether or not to apply a fee for market order be part of the fee policy (builder) and not part of this component?
Not fully following the intention and how the current structure should be changed.
I thought that
Made an attempt(c519fb8) to follow this logic. Not sure it addresses all the intentions. |
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.
Not fully following the intention and how the current structure should be changed.
Right, I realize that I asked for a bunch of refactorings and am now not happy with it (not really constructive review performance on my end). Feel free to propose something completely different (I just feel we now have a very complex control flow for not even yet very complex fee models, but those are around the corner).
As long as we keep the details private to the domain::fee
component we can also refine it later.
policy: Policy, | ||
fee_policy_skip_market_orders: bool, | ||
policy_builder: PolicyBuilder, | ||
skip_market_orders: bool, |
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 boolean is bothering me here. Skip market orders is something specific to the Surplus fee (we currently charge a surplus fee but skip market orders) and should thus be specified there and considered when we are trying to apply a Surplus FeePolicy to an order.
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.
So, should this function be also changed? Currently, it checks for this flag regardless of the policy kind.
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.
Yes, I think conceptually this flag applies only to the Surplus
policy. Once price improvement is implemented, I think we are likely going to use PriceImprovement
over Surplus
for all order types. I'm not sure though how we would configure things if price improvement for swap orders has different volume factors than for out of market limit orders (I think such a config may be desirable, to be checked with @olgafetisova).
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.
Moved to Surplus
} | ||
|
||
#[derive(Debug)] | ||
pub enum PolicyBuilder { |
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 should make this class private and create it in the main Fee component from the infra config. It might also be nicer to have every variant be there own sub-struct so that we can implement slightly different build behaviors (e.g. surplus and volume don't need the quote).
Then the main apply
function would become something like
self.fee_variants.filter_map(|variant| {
match variant {
FeeVariant::Surplus(variant) => variant.apply(order),
FeeVariant::PriceImprovement(variant) => variant.apply(order, quote),
...
}
}
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.
Refactored
# Conflicts: # crates/autopilot/src/arguments.rs
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.
Looks good to me. Some remaining comments on how to split the code into files (to allow for succinct and nice to read naming), reducing visibility of internal structs as much as possible and avoiding to share logic which is likely not correlated, but nothing blocking.
} | ||
|
||
#[derive(Debug)] | ||
struct SurplusPolicy { |
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: we are trying to keep struct names short for lexicographic appeal. I think they could just be called Surplus
(without policy) but if we want the Policy part on all subtypes, I think it would be prettier to create a new private submodule policy and reference them as
policy::Surplus, policy::PriceImprovement
etc
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.
Done
} | ||
} | ||
|
||
fn can_policy_apply(order: &boundary::Order, quote: &domain::Quote) -> bool { |
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 wouldn't share this method across fee policies (it seems coincidental that there is some shared logic between).
If I understand the current logic correctly, volume fees can only be applied to out of market limit orders, which doesn't seem correct to me.
Also for PriceImprovement, shouldn't this be applied mainly for in market price orders?
Let's just make this logic explicit for every 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.
Fixed
} | ||
|
||
impl SurplusPolicy { | ||
pub fn apply(&self, order: &boundary::Order, quote: &domain::Quote) -> Option<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.
no need to be pub
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.
Since it was moved to its own private submodule, this requires the function to be public.
pub struct ProtocolFee { | ||
policy: Policy, | ||
fee_policy_skip_market_orders: bool, | ||
policy_builder: PolicyBuilder, |
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.
Since this is a private type now, if we move it to a policy
submodule we could even call it Policy
without having a name collision (the way it turned out now it's not really following the Builder pattern anymore)
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.
Moved
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.
Lookin' nice!
pub struct Quote { | ||
pub sell_amount: U256, | ||
pub buy_amount: U256, | ||
pub fee: U256, |
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: maybe add a comment that the fee is always expressed in the sell token
Description
Closes task n2 from #2287