Skip to content

fix(kuru): read MarketStateError as a market that is not trading - #205

Closed
ShadowOfTime1 wants to merge 1 commit into
nishuzumi:mainfrom
ShadowOfTime1:fix/kuru-native-swap-live-test
Closed

ShadowOfTime1 wants to merge 1 commit into
nishuzumi:mainfrom
ShadowOfTime1:fix/kuru-native-swap-live-test

Conversation

@ShadowOfTime1

@ShadowOfTime1 ShadowOfTime1 commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Second half of #194: the native-swap live test (Kuru mainnet > simulates a native swap into an exhaustive typed Receipt) has failed since 2026-09-08 with ROUTE_QUOTE_UNAVAILABLE.

What is on chain. MON/USDC has four Router-verified markets, all running the current template 0x5e3446c600524Be453bbCEFD46a9E4C9bE8899a0. Three fill a 1 MON placeAndExecuteMarketSell probe; 0x764b4c2AF968c97b4ae95490d264c14d955129D5 reverts every probe with MarketStateError() (0x004b65ba). verifiedMarket() is still true for it and discovery still lists it, so it is not migrated or replaced — it is one market that has stopped accepting orders.

Why the test failed. The adapter kept that revert as an unmeasured route, the same as a bare revert, so the exhaustive default on swap refused the pair even though three routes priced.

The fix. MarketStateError() is the market naming itself as the cause, and it refuses at every size, so it is an answer rather than a gap. #quoteFill now decodes it against the vendored OrderBook ABI (decodeErrorResult, not a hand-typed selector) and prices that leg at zero: the route is measured, excluded from the winner, and the comparison stays exhaustive. Every other revert, with or without data, is unchanged and still lands in unavailable, so this does not hide an unmeasured route. A market that answered this way is remembered for the rest of the request, so the reverse search spends one call on it instead of one per size.

The live test keeps requireExhaustive at its default; the earlier requireExhaustive: false version of this PR is gone.

Tests. Two offline cases: a native swap whose better-priced MON/USDC market answers MarketStateError (nested the way viem wraps it) quotes with unavailable: [] and routes through the live market; and a pair whose only market answers it resolves to TARGET_OUTPUT_UNSATISFIABLE / NO_POSITIVE_QUOTE with no unavailable routes, after exactly one call. Both fail on current main.

Verify. pnpm lint, pnpm build, pnpm typecheck; the Kuru suite is 88/88 including the live native swap against Monad mainnet, exhaustive. Patch changeset added.

Refs #194.

@nishuzumi

Copy link
Copy Markdown
Owner

@ShadowOfTime1 before I take requireExhaustive: false in the live smoke, I need the premise checked. The comment calls this a transient book condition, but the failure has been continuous: every live run I have made since 2026-09-08 (9/8, 9/9, 9/10, 9/15, 9/16 and again today) hits the same ROUTE_QUOTE_UNAVAILABLE, and a direct eth_call of placeAndExecuteMarketSell on 0x764b4c2AF968c97b4ae95490d264c14d955129D5 still reverts with 0x004b65ba (MarketStateError()) right now. Two weeks of the same revert from the pair's verified market is a state change, not a momentarily thin book.

Could you establish, on chain, what changed: is the market paused or migrated under the new template (0x5e34…99a0), does Router.verifiedMarket() still return true for it, and has Kuru listed a replacement MON/USDC market that discover should be picking up? If the market is dead, the right fix is in discovery (and #199's record), and disabling exhaustive comparison in the smoke would hide exactly the regression the test exists to catch. If it genuinely is fillable-but-empty, show one block where the probe succeeds and I will take this as written.

ShadowOfTime1 added a commit to ShadowOfTime1/moss that referenced this pull request Sep 22, 2026
…rderBook surface

Address review on nishuzumi#199:
- Derive every Router selector from the vendored ABI entry (toFunctionSelector
  on the artifact) and keep the human-readable signature only as a cross-check,
  so a parameter-list drift between the ABI and the bytecode cannot pass.
- Add the OrderBook counterpart the header only narrated: read the market
  template bytecode and assert the two dispatcher selectors and three event
  topics, each derived from KuruOrderbookAbi. Record the read block as
  verifiedAtBlock in abis.json and pin the bytecode reads to it.
- Drop the header's claim to exercise live behaviour: that leg is red (the
  MON/USDC native-swap smoke fails with MarketStateError on one of four verified
  markets; tracked in nishuzumi#194/nishuzumi#205), so the record states the fact instead.
@ShadowOfTime1

Copy link
Copy Markdown
Contributor Author

You are right on both counts — the premise was wrong and requireExhaustive: false would paper over it. I took the on-chain investigation you asked for; here is what the pair looks like today (block 106978763), one row per Router-verified MON/USDC market, each probed with a 1 MON placeAndExecuteMarketSell:

market verifiedMarket ERC-1967 impl 1 MON probe
0xCb46…249d registered 0x5e34…99a0 fills
0x764b…29D5 registered 0x5e34…99a0 MarketStateError()
0x122C…e3Fa registered 0x5e34…99a0 fills
0x065C…C394 registered 0x5e34…99a0 fills

So, to your three questions: Router.verifiedMarket() still returns true for 0x764b…29D5; it is not migrated — it runs the same template 0x5e34…99a0 as the three markets that do fill; and discovery already picks up the live markets (a requireExhaustive: false swap routes through one of them). The pair is not dead and there is no missing replacement — it has four verified markets and exactly one is individually stuck in MarketStateError. That is a per-market state, not a template or discovery gap, which is why the exhaustive comparison legitimately cannot complete: one verified route cannot be measured.

Proposed fix, and I think it belongs in the quote classifier rather than discovery. Discovery cannot cheaply know a market is stuck — verifiedMarket returns true, so the only way to tell is to probe, and probing every candidate in discovery would blow the per-request call budget the adapter is careful about. The quote path already probes each route, so the signal is in hand exactly where it is needed. MarketStateError() (0x004b65ba) is a decodable, market-attributed error — the market itself saying it is not in a tradeable state — which is the same shape as the Panic(0x11) you already decode in isMarketArithmeticOverflow. So: decode it, and treat a route whose market answers MarketStateError as a measured dead market (a definitive "not trading", excluded from the comparison) rather than an unmeasured route that blocks exhaustiveness. Only that exact decoded error is treated this way — any other revert stays unmeasured/unavailable as today, so this does not hide the regression the test is there to catch. The live smoke then stays exhaustive with no requireExhaustive: false.

If that direction is right I will rework #205 into the classifier change plus an offline fixture (a market stubbed to revert MarketStateError is dropped, not counted against exhaustiveness) and keep the live native-swap test exhaustive. If you would still rather the exclusion live in discovery, say so and I will put it there instead.

The native-swap live test has failed since 2026-09-08 because one of the
four verified MON/USDC markets (0x764b...29D5) reverts every quote probe
with MarketStateError() while the other three, on the same template, fill.
The adapter kept that revert as an unmeasured route, so swap refused the
pair as an incomplete comparison.

The market names itself as the cause and refuses at every size, which is an
answer rather than a gap. Decode it against the vendored OrderBook ABI and
price that leg at zero, so the route is measured and the comparison stays
exhaustive. Any other revert, with or without data, is unchanged and still
reported as unavailable. A market that answered this way is remembered for
the rest of the request, so the reverse search does not pay to hear it again.

The live native-swap test keeps its exhaustive default; two offline cases
cover the decoded refusal (routed around even when it is the better-priced
market) and the single call it costs the reverse search.
@ShadowOfTime1
ShadowOfTime1 force-pushed the fix/kuru-native-swap-live-test branch from f984962 to eeca714 Compare September 25, 2026 10:28
@ShadowOfTime1

Copy link
Copy Markdown
Contributor Author

Reworked as proposed: MarketStateError() is now decoded in the quote path and read as a market that is not trading, so the live smoke stays exhaustive and requireExhaustive: false is gone. Details and the on-chain table are in the updated description. If you would rather the exclusion live in discovery, it is a small move.

@ShadowOfTime1 ShadowOfTime1 changed the title test(kuru): keep the native-swap live smoke off transient book liquidity fix(kuru): read MarketStateError as a market that is not trading Sep 25, 2026
@ShadowOfTime1

Copy link
Copy Markdown
Contributor Author

Closing this to free my claim slot for other pool work. The branch stays on my fork and the change is ready on my side, so it can be reopened as is if it's still wanted.

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.

2 participants