apply haircut logic in pre-processing#4417
Conversation
* add test for haircut math * reuse BPS defintion
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to tighten order limits sent to solvers by applying a 'haircut' factor, ensuring that post-haircut executions still respect user-signed limit prices. The implementation includes a new apply_haircut function in the auction DTO and updates to the solver configuration and test mocks. A review comment identifies a potential issue in a debug_assert! that uses a strict inequality for the maximum basis points, which could cause panics in debug builds if the haircut is set to 100%.
tilacog
left a comment
There was a problem hiding this comment.
LGTM, just left two minor comments
fleupold
left a comment
There was a problem hiding this comment.
I was hoping we could actually model this 💇 concept as an additional fee policy to begin with (this removes code and complexity for two practically identical code paths).
This change is probably the correct fix (and thus might be less risk to ship), but I think it would be a good follow up that can be rolled out with low risk (ie. move one solver from haircut to driver-injected protocol fee, check it works as expected, then remove haircut concept from the codebase).
In the meantime, I'd like to see an integration test for this before merging.
MartinquaXD
left a comment
There was a problem hiding this comment.
Agree that we should rather model the haircut logic as a custom volume fee that gets injected by the driver.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
|
Added an e2e test now. As discussed I will merge this now and followup with an attempt to merge this with volume fee separately. |
Description
The driver applies a per-solver haircut (
haircut_bps) to the solution it receives from a solver before submitting on-chain: the user receives less buy-token (sell orders) or pays more sell-token (buy orders) than the solver reported. The solver doesn't know about this and bids against the user's signed limit price.When the haircut is larger than the solver's surplus over the limit, the post-haircut delivery falls below the signed limit and
settle()reverts withGPv2: limit price not respected.Evidence (prod, order
0xa978e3ec…6a020c06)441_289_983_646_158_011_001SATO (signed limitB).0x4c52…f739bidexecuted_buy = 444_985_590_464_474_499_347SATO — ~83 bps overB.buyAmount = B, contract simulation reverted:Fix
Tighten the order limits the driver sends to the solver by the haircut factor, mirroring the volume-fee adjustment already done a few lines above:
buy.amount := buy.amount / (1 − h)sell.amount := sell.amount / (1 + h)Any solver bid
Ethat clears the tightened limit survives the post-hoc haircut by construction:Solvers that can't deliver the tightened limit fail their own
satisfiescheck and don't bid — strictly better than bidding a solution that's guaranteed to revert.Test plan
New unit tests in
infra::solver::dto::auction::tests:haircut_zero_is_noop—haircut_bps = 0leaves bothsellandbuyuntouched for either side.haircut_tightens_buy_for_sell_order— exact prod numbers from0xa978e3ec…; assertsbuy.amount == B / (1 − h)and that the round-trippost_haircut ≥ Bholds within f64 rounding.haircut_tightens_sell_for_buy_order— symmetric for buy orders.haircut_overflow_preserves_original_limit— explicit guard that a failingapply_factordoes NOT zero the limit.Existing end-to-end tests
tests/cases/haircut.rs::{order_haircut_reduces_score, buy_order_haircut}continue to pass; the test-side mirror intests/setup/solver.rskeeps the stub's expected auction JSON aligned with what the driver now sends.Notes
haircut_bps = 0(the default). Only affects solvers explicitly configured with a haircut.