fix(SeaportRouter): terminate tally loop on partially-available batches - #1415
Open
aeonframework wants to merge 1 commit into
Open
Conversation
fulfillAvailableAdvancedOrders returns a mixed bool[] whenever a batch is partially available (some orders cancelled, expired, already filled, or beyond maximumFulfilled). In SeaportRouter's post-call tally the ++j increment lived inside `if (newAvailableOrders[j])`, so the first false entry pinned j and the loop spun until it ran out of gas. Any caller with a partially-available batch lost their whole gas limit instead of getting a partial fill, and it is cheaply triggerable by a third party front-running one order in a pending batch with a cancel. Move ++j into the loop body so it advances on every iteration regardless of the per-order flag. Behaviour on fully-available batches is unchanged.
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.
Summary
SeaportRouter.fulfillAvailableAdvancedOrdersnever terminates on a partially-available batch, burning the caller's entire gas limit instead of returning a partial fill.In the post-call tally at
contracts/helpers/SeaportRouter.sol:146-154, theforheader has no increment and the only++jis nested insideif (newAvailableOrders[j]):fulfillAvailableAdvancedOrdersreturnsfalsefor any order it skipped — cancelled, expired, already filled, zone-rejected, or beyondmaximumFulfilled— and only reverts (NoSpecifiedOrdersAvailable) when no order is available. So a batch where some fill and some don't returns a mixedbool[]with at least onefalse— the exact case this router exists to serve. The firstfalsepinsj, and the loop spins to out-of-gas.Impact
Any caller whose batch is partially available loses their whole gas limit rather than getting a cheap revert or a partial fill. It is cheaply and adversarially triggerable: a seller front-running one order in a pending batch with a
cancel(~30-50k gas), or another buyer taking one listing first, is enough to force the victim's call to out-of-gas. No funds are stolen and nothing is stuck — this is a gas-griefing DoS.SeaportRouteris not in the canonical deployment table, so the exposed parties are third-party integrators who deploy it themselves; reporting it as a defect in a shipped helper contract.Fix
Move
++jinto the loop body so it advances on every iteration regardless of the per-order flag. Fully-available batches are unaffected.Reproduction
The defect is entirely in the router's tally of Seaport's return value, independent of order execution, so a mock Seaport isolates it.
forge testat080133906585(solc 0.8.24): the[true, false]case out-of-gasses a 3M-gas inner call; the[true, true]control returns cleanly through the identical path.The existing
test/router.spec.tsmisses this because it puts one order behind each Seaport contract, so each call returns all-true or all-false — a mixed array within a single call is never produced. A regression test needs a single Seaport call whose batch is genuinely mixed (two orders on the same Seaport, one cancelled). Happy to add that in whichever harness you prefer.