Skip to content

[simplex]: keep an order's evaluation cached until all its bids are sent - #1424

Open
Wizdave97 wants to merge 1 commit into
mainfrom
fix/simplex-bid-loop-cache-expiry
Open

Wizdave97 wants to merge 1 commit into
mainfrom
fix/simplex-bid-loop-cache-expiry

Conversation

@Wizdave97

Copy link
Copy Markdown
Member

What this does

IntentFiller.executeOrder sends one bid per cached BidPlan, in turn, and every bid is signed by ContractInteractionService.prepareBidUserOp with the gas estimate estimateGasFillPost cached at evaluation. CacheService entries expire after CACHE_EXPIRY_MS (60 s). When Hyperbridge holds each place_bid in its pool for 20 s or more, the loop outlives the cache, and the later plans fail with No cached gas estimate found for order <id>. EventMonitor drops repeat order ids, so those legs never get a bid.

Seen in the simplex testnet swaps run https://github.com/polytope-labs/hyperbridge/actions/runs/39275362211 (job 118147728951), scenario multi-leg-same-input: TIMEOUT with 2 fills, 4 No cached gas estimate errors.

Behavior

  • The bid loop pins the order before it reads the plans. CacheService.pinOrder(orderId) returns the release, which executeOrder calls in a .finally on the queued task, however the task ends.
  • While the order is pinned, every cache entry under its id (gas estimate, bid plans, filler outputs/inputs/budget, funding prepends, partial-fill and fee-check-waived flags) is aged as of the pin rather than now, and the stale-data sweep skips it.
  • An entry that had already expired when the loop started (for example after a long wait in the chain queue) stays expired, so the loop sends nothing, as before. Once released, the entries expire on the usual clock.
  • Later bids are not re-estimated: each signs the gas cost its payout was priced against.

fill-path-safety.test.ts stubs the cache by hand, so its two stubs gain pinOrder. The new src/tests/core/bid-loop-cache-expiry.test.ts is added to test:unit, which did not cover src/tests/core before. Note: docs/ai/changelog/2026-10-20-bid-loop-outlives-the-evaluation-cache.md.

The open stack #1410 → #1414 → #1421 → #1422 rewrites this loop. To keep the conflict small, the filler.ts change is about 10 lines and does not re-indent the loop. That stack's rewritten fill-path-safety.test.ts stubs will need pinOrder: () => () => {} too.

Validation

  • bid-loop-cache-expiry.test.ts runs three plans through the real prepareBidUserOp, with each submission taking CACHE_EXPIRY_MS + 1 s. All three are signed and executed, and the pin is released afterwards. On main only the first is signed. A second case checks that an evaluation which expired before the loop sends nothing.
  • pnpm --filter @hyperbridge/simplex test:unit: 263 passed. test:filler: 59 passed, 5 skipped.
  • All of src/tests/core plus bid-budget, bid-call-gas and bid-gas-estimate: 138 passed.
  • lint: no errors. tsc: nothing reported in the touched files.

🤖 Generated with Claude Code

The bid loop signs every plan with the gas estimate cached at evaluation,
which expired after CACHE_EXPIRY_MS while slow place_bid calls were still
going out, so the later plans never bid. The loop now pins the order in
CacheService: its entries are aged as of the pin until the queued task
settles, and entries already expired when the loop starts stay expired.

This branch has not been deployed

No deployments
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.

1 participant