fix: reject hard quotes whose outputs span multiple tokens - #480
Open
codyborn wants to merge 1 commit into
Open
Conversation
We collapse a hard quote into a single (tokenIn, tokenOut, amount) request, where tokenOut is outputs[0].token and amount raw-sums every output's startAmount. That projection is only meaningful when the outputs agree on a token. When they don't, the scalar we ask quoters to beat mixes assets of different denominations, and a quoter is bidding on something the order does not contain. The cost lands on the quoter. Winning that auction hands it exclusivity on an order it has to fade, which spends its fill rate and circuit-breaker standing on a request it was never able to price. uniswapx-service rejects the same shape at POST /order (#693), so the fill could not have happened anyway. Rejected in the handler immediately after parsing the inner order, before the KMS round trip, before the order is cosigned and before any quoter is contacted, with a QUOTE_MIXED_OUTPUT_TOKENS metric so we can see whether this ever fires. Multi-output orders are unaffected: fee outputs use the swapper output's token. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to uniswapx-service#693. That PR stops the order service accepting an order whose outputs span multiple tokens. This one stops us asking fillers to quote it in the first place.
The problem
HardQuoteRequest.toCleanJSONcollapses the signed order into a single(tokenIn, tokenOut, amount)request, wheretokenOutisoutputs[0].tokenandamountcomes fromtotalOutputAmountStart, which raw-sums every output'sstartAmount. That projection is only meaningful when the outputs agree on a token. When they don't, the scalar we ask quoters to beat mixes assets of different denominations, and the quoter is pricing something the order does not contain. Two orders with completely different output vectors produce a byte-identical quote request.The cost of that lands on the quoter, which is the reason this is worth fixing here and not only in the order service. A quoter that wins on a price it could not have computed gets exclusivity on an order it then has to fade, spending fill rate and circuit-breaker standing on a request it was never able to price.
The change
HardQuoteRequest.hasUniformOutputTokenschecks every output againstoutputs[0].token, checksummed so casing doesn't matter. The handler rejects on it immediately after parsing the inner order, which is:getAddressround trip,Rejection is a 400
VALIDATION_ERRORvia a newMixedOutputTokensError, and emitsQUOTE_MIXED_OUTPUT_TOKENS. I expect that metric to sit at zero; it is there so we find out if it doesn't, before anyone notices as a fade.Multi-output orders are unaffected. Fee outputs use the swapper output's token, which is the assumption the raw sum was already relying on (#271 raised it at review time and it was never enforced). Applies to both Dutch V2 and V3, since both go through this handler.
Testing
yarn test:unitgreen, 269 tests. New coverage onHardQuoteRequest: uniform single output, uniform fee output, casing insensitivity, and a mixed vector. New coverage on the handler: 400 with the right detail,quotenever called on the quoter,signDigestnever called, and a same-token fee output still returning 200.🤖 Generated with Claude Code