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

[BLOCKED] Save executed fee in surplus token #3184

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

sunce86
Copy link
Contributor

@sunce86 sunce86 commented Dec 26, 2024

Blocked by frontend team. I need a confirmation that frontend team switched to using executed_fee field introduced in #2966

Description

This PR implements item (3) from #2963

Previously, executed fees were saved and displayed in the sell token. With this change, executed fees will now be saved and expressed in the surplus token.

This change applies to all orders settled after this PR is merged.

  • Historical entries will remain expressed in the sell token.
  • From now on, all fees (e.g., gas fees, protocol fees) will be consistently saved and expressed in the surplus token. 🥳

Follow-Up Tasks

Migrate Historical Fees to Surplus Token

I will manually execute a migration immediately after this PR is merged.

  • This migration will convert existing fee values from the sell token to the surplus token using UCP prices from solver competition.
  • The migration is safe because it effectively reverses the logic used to save fees in the sell token in the current implementation.

Current version can be observed here (under function convert_executed_fee): cowprotocol/data-migration@76dc7bd#diff-1d06da761111802c793c6e5ca704bfa0d6336d0becf87fddff02d81548a838abR129

Fix Remaining Issue

After the migration, we need to update the following code reference:
https://github.com/cowprotocol/services/blob/main/crates/database/src/orders.rs#L575

  • Currently, the system will have a mix of fees saved in the sell token and surplus token.
  • Once all fees are migrated to the surplus token, we can finalize this fix

Changes

  • Save executed_fee in surplus token instead of in sell token

How to test

Expanded one unit test.
Existing e2e tests.

@sunce86 sunce86 self-assigned this Dec 26, 2024
@sunce86 sunce86 requested a review from a team as a code owner December 26, 2024 11:35
@squadgazzz
Copy link
Contributor

I will manually execute a migration immediately after this PR is merged.

Is this script ready? It would be worth posting it in the description under a spoiler. Also, I am not sure I understand how the old entries will be identified; the script will probably explain it.

@sunce86
Copy link
Contributor Author

sunce86 commented Dec 26, 2024

I will manually execute a migration immediately after this PR is merged.

Is this script ready? It would be worth posting it in the description under a spoiler. Also, I am not sure I understand how the old entries will be identified; the script will probably explain it.

Not yet ready, currently implementing it.

Comment on lines +413 to +424
/// Fee per trade in a solution. These fees are taken for the execution of the
/// trade.
#[derive(Debug, Clone)]
pub struct FeeBreakdown {
/// Fee reported by solvers, representing difference between UCP and custom
/// prices.
pub executed: eth::TokenAmount,
/// Breakdown of protocol fees.
pub protocol: Vec<ExecutedProtocolFee>,
/// Surplus token in which all the fees were taken.
pub token: eth::TokenAddress,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to include details from your comment somewhere here.

Copy link
Contributor

@squadgazzz squadgazzz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I would still first look into the script before approving this PR.

@sunce86
Copy link
Contributor Author

sunce86 commented Dec 27, 2024

Current version of migration code can be observed here (under function convert_executed_fee): cowprotocol/data-migration@76dc7bd#diff-1d06da761111802c793c6e5ca704bfa0d6336d0becf87fddff02d81548a838abR129

The code is not clean since we want to primarily focus on logic and the code is practically used one time.

@squadgazzz
Copy link
Contributor

squadgazzz commented Dec 30, 2024

Current version of migration code can be observed here (under function convert_executed_fee): cowprotocol/data-migration@76dc7bd#diff-1d06da761111802c793c6e5ca704bfa0d6336d0becf87fddff02d81548a838abR129

Could you make a PR there so it will be possible to ask questions about the migration?

Copy link
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will manually execute a migration immediately after this PR is merged.

Is it important that this happens after the PR is merged? I assume this needs to be synchronized with the frontend to not show nonsensical values?
Asking because this comment implies to me that there should be 2 batches of DB updates (one set for staging and one set for prod).

Comment on lines +417 to +419
/// Fee reported by solvers, representing difference between UCP and custom
/// prices.
pub executed: eth::TokenAmount,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just by reading the struct and comments it's not really clear to me what this field is.
It sounds like this is just the sum of all protocol fees but I think since we need to account for the gas in the breakdown this is all protocol fees + gas fees.
In that case I think it would be nicer to have a field like gas_costs and a public member function total_fees(). WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how I implemented it initially, but I had to revert those changes. I commented here on the reasons why.

Comment on lines +420 to +421
/// Breakdown of protocol fees.
pub protocol: Vec<ExecutedProtocolFee>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order of items also indicates the order in which the fee policies got applied, right?
Seems important enough to point out in a comment.

},
bigdecimal::Zero,
};

mod math;

pub use math::FeeBreakdown;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Seems like the original location of FeeBreakdown makes more sense (i.e. in trade/mod.rs).

fn fee_in_buy_token(order: &Order, quote: &OrderQuote) -> U256 {
order.metadata.executed_fee * quote.buy_amount / quote.sell_amount
fn fee_in_buy_token(order: &Order) -> U256 {
order.metadata.executed_fee
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this now only correct when order is a buy order given that fees should now always be computed in the surplus token?
In that case I would add an assert for that.

@@ -182,7 +160,7 @@ impl Trade {
policy: *policy,
fee,
});
total += fee.amount;
total += fee;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Safer would be to use checked_add().

fn surplus_fee(
&self,
surplus: eth::TokenAmount,
factor: f64,
Copy link
Contributor

@mstrug mstrug Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use FeeFactor here instead of f64 so we can ensure on type-level that factor cannot have value of 1 (so there will be no division by 0).

Same for volume_fee().

Self::Jit(trade) => trade.sell.token,
Self::Fulfillment(trade) => match trade.side {
order::Side::Buy => trade.sell.token,
order::Side::Sell => trade.buy.token,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding new function to implementation of Fulfillment type like get_token_by_side() (or similar), will remove the code duplication (here and Self::Jit() case below).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants