Skip to content

New logic to compute scores of buy order trades#3340

Merged
MartinquaXD merged 19 commits intomainfrom
buy-order-score-change
Apr 14, 2025
Merged

New logic to compute scores of buy order trades#3340
MartinquaXD merged 19 commits intomainfrom
buy-order-score-change

Conversation

@MartinquaXD
Copy link
Copy Markdown
Contributor

@MartinquaXD MartinquaXD commented Mar 29, 2025

Description

Implements the necessary changes for this upcoming CIP.
I also created #3343 as a follow up PR for the same logic but on the autopilot side (eventually we should have 1 code shared across autopilot and driver).

Changes

  • refactor (hopefully simplify) logic to compute scores for trades
  • moved logic to multiple U256 with a fractional f64 into U256Ext
  • added boilerplate to switch to the new scoring logic when the deadline of a /solve requests is past the cutover date

How to test

Added a unit test based on the order that sparked the CIP. (@harisang confirmed that the new values are correct)
Updated our e2e tests to use the new scores everywhere and verified that the new values are reasonable

@harisang
Copy link
Copy Markdown
Contributor

harisang commented Mar 29, 2025

The new score looks correct. The reason it is extremely small is the tiny amount being traded and the extremely low native price of the buy token. Specifically:

native_price = 113181296327

which implies that 1 unit of the buy token is worth

10^18 * 113181296327 / 10^36 = 1.13181296327e-07 ETH.

Since the buy amount is 8050667745 atoms of the buy token, according to the native price, the buy amount is "equal" to

8050667745 * 1.13181296327e-07 / 10^18 = 9.111850116770659e-16 ETH

So the score indeed should be bounded by this number.

We can now directly compute it and verify it is correct. The original score is 19729793891650888. This is expressed in wei, and the only thing we need to do is scale by the limit price of the order and multiply by the native price of the buy token (and divide by the native price of the ETH, which is 10^18).

From here, we see that

limit_price = limit_buy / limit_sell = 4025333872768468868566822740 / 9865986634773384514560000000000000

and so we get

new_score = 19729793891650888 * limit_price * native_price / 10^18 = 911.084372209975

Copy link
Copy Markdown
Contributor

@sunce86 sunce86 left a comment

Choose a reason for hiding this comment

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

I think the logic is correct.

We still have the equivalent autopilot code that should be updated as well: https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/domain/settlement/trade/math.rs
Note that the fn score() in the autopilot is not used for anything particular yet, but other functions are. I tried to keep the code 1:1 to the driver version (with regards to function names, signatures etc) as much as I could, but your refactor in this PR diverges from that. I'd let you decide how important (keeping the code 1:1) is for further maintaining the code.

assert_eq!(old_score.0, 19731899115084598u128.into());

let new_score = trade.score(&native_prices).unwrap();
// TODO: double check, this seems super low...
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Probably due to the new score being bounded by the volume of the trade. The actual volume of the order was low.

@MartinquaXD MartinquaXD marked this pull request as ready for review April 3, 2025 14:14
@MartinquaXD MartinquaXD requested a review from a team as a code owner April 3, 2025 14:14
Copy link
Copy Markdown
Contributor

@mrnaveira mrnaveira left a comment

Choose a reason for hiding this comment

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

LGTM

// the actual score of the solution was 19729793891650888
// (~0.02 ETH) but our result is slightly different
// because we ignored fees.
assert_eq!(old_score.0, 19731899115084598u128.into());
Copy link
Copy Markdown
Contributor

@mrnaveira mrnaveira Apr 4, 2025

Choose a reason for hiding this comment

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

This PR refactors the old scoring code (although it should be equivalent to the previous one). I'm a bit concerned that all the unit tests now use the new scoring so this is the only test we have of the old scoring.

The old scoring will still be in place until the cutover, as a suggestion we could make the unit tests assert both old and new scoring to be on the safe side.

Copy link
Copy Markdown
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

Comment on lines +85 to +87
/// Time at which the buy order scoring change should go into
/// effect (based on `deadline` in `/solve` request).
buy_order_scoring_change_cutover: chrono::DateTime<chrono::Utc>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it a required param now that requires an infra PR?

Copy link
Copy Markdown
Contributor Author

@MartinquaXD MartinquaXD Apr 14, 2025

Choose a reason for hiding this comment

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

It was until now. The official CIP defines this date so I could use this as the default value now.

Copy link
Copy Markdown
Contributor

@mateo-mro mateo-mro left a comment

Choose a reason for hiding this comment

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

LGTM.

settle_queue: mpsc::Sender<SettleRequest>,
/// If the auction deadline is later than this timestamp the score
/// for buy orders will be computed based on the latest rules.
// TODO: remove after the cutover happened
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe we should create an issue for this, so we don't forget.

Comment on lines +136 to +137
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
pub struct SurplusTokenAmount(pub U256);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: why to have from/into if we have access to the internal data (pub)?

@github-actions
Copy link
Copy Markdown

This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed.

@github-actions github-actions bot added the stale label Apr 12, 2025
@MartinquaXD MartinquaXD merged commit 31ea719 into main Apr 14, 2025
11 checks passed
@MartinquaXD MartinquaXD deleted the buy-order-score-change branch April 14, 2025 07:16
@github-actions github-actions bot locked and limited conversation to collaborators Apr 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants