Skip to content

fix: publish one-sided executable price books (RAI-2930) - #196

Merged
findolor merged 2 commits into
mainfrom
fix/rai-2930-empty-vault-pricing
Oct 6, 2026
Merged

findolor merged 2 commits into
mainfrom
fix/rai-2930-empty-vault-pricing

Conversation

@findolor

@findolor findolor commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

The API now publishes fresh executable bid and ask observations independently when only one side has inventory. This fixes the API half of RAI-2930 while preserving retained midpoint prices and history; the companion website PR #307 uses the relevant side as its trading reference.

Live effect: Funded one-sided markets expose current trading prices · Risk: Medium (stored data and trade pricing) · Ships: Next API release

Decisions

  • Add nullable executableBook alongside the existing display fields. Missing sides are cleared on each completed sample; current-book timestamps are separate from the retained midpoint. Aggregation.
  • Keep unfunded, failed and atomic-dust quotes excluded. Trading books only include exact canonical-token orders; normalized legacy/underlying orders remain display-only. A successful zero-output quote cannot establish that a strategy intends to trade. Existing quote checks.
  • Commit midpoint and book changes atomically; older samples cannot replace newer book observations, and skipped writes emit a warning. Persistence.

Risks

  • The additive SQLite migration runs at startup. Canonical-address changes expose no book until the next successful sample. Historical requests return no current book.
  • Consumers must use fresh ask for buys and reciprocal bid for sells. A fresh midpoint alone still rejects SNES during its intentionally wide spread. Directional book references guard worsening execution, not independent fair value.

Proof

  • Production replay: a sell fails with both old and current midpoint references, but fully fills with the current bid. Buying remains unavailable with an empty vault.
  • All 520 API tests pass, including canonical-versus-variant orders, independent crossed books, side disappearance, restart, atomic rollback, invalid/dust quotes and varying spreads; check, formatter and all-feature clippy pass. Local EVM calldata/oracle/attribution regression passes.
  • Isolated localhost API smoke returned a real, fully filled SNES sell through public Robinhood sources; unsigned approval-first calldata returned the identical cap. Its actual HTTP price response passes the website adapter/reference/request smoke test. The live localhost sampler independently fetched one funded order and published a bid-only book with zero new midpoints.
  • Not verified: Preview deployment or a production transaction.

Rollout

Deploy the API to preview first and verify a fresh funded SNES bid with no ask. Then deploy the companion website and check quoting/calldata with unchanged 100 bps slippage. Roll back the website consumer first if needed; the additive API field/table can remain unused.

@linear-code

linear-code Bot commented Oct 6, 2026

Copy link
Copy Markdown

RAI-2930

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Team
  • Run ID: d7ef79a3-4ae2-493f-9fe4-e06433eae7fc
📥 Commits

Reviewing files that changed from the base of the PR and between 8ddd854 and b9bf070.

📒 Files selected for processing (3)
  • docs/src/prices.md
  • src/db/market_price_history.rs
  • src/market_price.rs

Included review availability: This review used your included allowance. 6 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 8 reviews per hour.


Walkthrough

Market sampling now records executable bid and ask values independently of midpoint snapshots. The database stores and prunes book snapshots alongside price history. Non-historical price responses include available canonical-token books with a freshness source; historical responses omit them. Documentation describes the response fields and sampling behavior. Tests cover book aggregation, persistence, response conditions, and directional slippage references.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to b9bf0

The one-sided executable book remains available when no midpoint exists. No identified issue prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.41% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 41 functions across 4 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: publishing one-sided executable price books.
Description check ✅ Passed The description explains the API change, its behavior, risks, tests, and rollout. It is directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 63.41% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 41 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 Clippy (1.98.1)

Clippy execution failed


Comment @coderabbitai help to get the list of available commands.

@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin review

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔎 Reviewing 8ddd854, started by @findolor. The review will appear here when it's done.

@findolor findolor self-assigned this Oct 6, 2026

@rain-marvin rain-marvin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a nullable executableBook (best bid and best ask, each optional) to the /v1 and /v2 price responses. Each side now refreshes on its own, so a market where only one side is funded (the SNES case in RAI-2930) still publishes a current trading price. The retained midpoint rules do not change. Books go into a new latest-only market_books table. They commit in the same transaction as the midpoint snapshots, are cleared when a completed sample no longer sees a side, are pruned with the same retention, and are left out of historical (at=) responses.

Overall read: the change is sound and well tested. The panel traced the sampler end to end: one-sided, empty and crossed books, a failed market that discards the whole sample, atomic persistence, and live or cached labeling. It also checked migration order and rollback safety. It found no blocker. Two minor points are below:

  • the executable book still mixes in orders on legacy or unwrapped token variants, which the swap routes cannot take;
  • the observed_at upsert guard can silently freeze books after a backward clock correction.

One design trade-off to keep in mind: the book takes orders from any owner. When the maker's own side is empty, a single third-party order sets the directional reference. The docs and PR description already say this reference guards against worse execution, not against an unfair price, so this is not raised as a finding.

claude-opus-5-5 · high · 17 min

Comment thread src/market_price.rs
Comment thread src/db/market_price_history.rs Outdated
@findolor
findolor force-pushed the fix/rai-2930-empty-vault-pricing branch from 8ddd854 to b9bf070 Compare October 6, 2026 10:34
@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin approve

Both findings from your review are addressed in b9bf070:

  • Observations retain the actual decoded asset token. Executable books aggregate only exact canonical-token orders, while normalized legacy/underlying orders remain display-only. Crossed display and executable books are handled independently. Four new regressions cover better variant prices, variant-only liquidity, independent crossings, and decoded source identity in both directions.
  • The monotonic timestamp guard remains to prevent older samples overwriting newer observations. Successful commits now warn when book writes are skipped, with produced/written/skipped counts and observed_at, as suggested.

All 520 local API tests, formatting, strict all-targets/all-features Clippy/static checks, and commit hooks pass. Please verify these fixes and approve if satisfied. Staging deployment was cancelled before deployment steps at Arda's request.

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔎 Verifying that my findings from 8ddd854 are addressed at b9bf070, started by @findolor. The review will appear here when it's done.

@rain-marvin rain-marvin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a nullable executableBook (best bid and best ask, each optional) to the /v1 and /v2 price responses, stored latest-only in market_books and committed atomically with the midpoint snapshots. The retained midpoint rules do not change.

Both earlier findings are resolved in b9bf070:

  • Variant orders in the executable book (src/market_price.rs): resolved. ObservedQuote now carries source_asset_address, set from the decoded order's actual asset token. aggregate_market_prices builds a separate executable book only from observations whose source token is the canonical wrapped address, so a better legacy or underlying price can no longer become bestAsk/bestBid and break the swap reference guard. Variants still feed the display midpoint. The crossed check now runs on each book independently, so a crossed display book no longer clears valid canonical liquidity. Four new tests cover a better variant price on both sides, variant-only liquidity (midpoint kept, executable sides null), independent crossing, and the decoded source token for canonical and variant orders. The docs say the same.
  • Silent observed_at guard (src/db/market_price_history.rs): resolved. The monotonic guard stays, which my finding allowed, and the write now counts book rows that the guard skipped and logs a warning with produced, written and skipped counts after commit. A backward clock step is now visible in the logs instead of silent.

No thread was resolved by hand; both of my threads are still open and are addressed by the code above.

The diff since my last review adds no new defects that I found. CI is green on b9bf070 (all four Rust test shards, static checks and docs).

claude-opus-5-5 · high · 3 min

@ueco-jb ueco-jb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executableBook takes only canonical-token orders. Failed, unfunded and dust quotes stay out, each completed sample clears sides it no longer sees, and books commit in the same transaction as the midpoints. Historical (at=) responses carry no book, and live means non-future and at most two sample intervals old. The units match the swap: bestAsk and 1/bestBid equal the candidate IO ratios the reference guard filters on. slippage.rs changes only in tests, and the retained midpoint behaviour is unchanged.

Comment thread src/market_price.rs
Comment thread migrations/20261006000000_create_market_books.sql
Comment thread docs/src/prices.md Outdated
@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai approve

@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin approve

@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin approve

1 similar comment
@findolor

findolor commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

@rain-marvin approve

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔎 Verifying that my findings from b9bf070 are addressed at b69a388, started by @findolor. The review will appear here when it's done.

@rain-marvin rain-marvin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a nullable executableBook (best bid and best ask, each optional) to the /v1 and /v2 price responses. The book is built only from canonical wrapped-token orders, stored latest-only in market_books, and committed atomically with the midpoint snapshots. The only change since my last review (b9bf070) is a docs commit in docs/src/prices.md. The code is unchanged and still looks correct to me.

My earlier findings:

  • Variant orders in the executable book (src/market_price.rs): resolved in b9bf070. Book sides come only from orders whose source token is the canonical wrapped address, and tests cover this.
  • Silent observed_at guard (src/db/market_price_history.rs): resolved in b9bf070. The guard stays, and skipped book rows are now counted and logged.

Threads resolved by hand:

  • Crossed canonical executable book clears both sides (src/market_price.rs): dismissed, and the dismissal holds. The code is unchanged, and the docs state that crossed books publish both sides as null. This fails closed: a client loses its reference and does not get a loose one. A crossed canonical book is also an arbitrage that tends to clear. This is acceptable as documented.
  • Unused assets_per_share column in market_books: dismissed, and the dismissal holds. The column is still written and selected but never read into a response. It costs one text column and records which ratio applied to each book. This is a nit with no behavior impact.
  • Book references in unwrapped swap mode (docs/src/prices.md): addressed in b69a388. The docs now say to send references with denomination: "wrapped", or to convert them with wrappedReference * inputAssetsPerShare / outputAssetsPerShare. I checked this formula against normalize_calldata_price_cap in src/routes/swap/denomination.rs. The server converts unwrapped to wrapped with * outputAssetsPerShare / inputAssetsPerShare, so the documented conversion is its exact inverse. It also uses 1 for a token without a wrapper, as ratio_for_token does.

The new diff adds no defects that I found. CI is green on b69a388 (all four Rust test shards, static checks, docs).

claude-opus-5-5 · high · 45 s

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔎 Verifying that my findings from b69a388 are addressed at b69a388, started by @findolor. The review will appear here when it's done.

@rain-marvin rain-marvin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a nullable executableBook (best bid and best ask, each optional) to the /v1 and /v2 price responses. The book is built only from canonical wrapped-token orders, stored latest-only in market_books, and written in the same transaction as the midpoint snapshots.

The head is still b69a388, the commit I already reviewed and approved, so there is no new diff to review. I checked the fixes again in the code at that commit.

My earlier findings:

  • Variant orders in the executable book (src/market_price.rs): resolved in b9bf070. Book sides come only from orders whose source token is the canonical wrapped address. Legacy and unwrapped orders still feed the display midpoint, and tests cover this.
  • Silent observed_at guard (src/db/market_price_history.rs): resolved in b9bf070. The guard stays, but book rows that the upsert skips are now counted, and a warning is logged with the written and skipped counts.

Threads resolved by hand:

  • Crossed canonical executable book clears both sides (src/market_price.rs): dismissed, and the dismissal holds. docs/src/prices.md says that crossed books publish both sides as null, and the code logs ignoring crossed canonical executable book. This fails closed: a client loses its reference and never gets a looser one. A crossed canonical book is also an arbitrage that tends to clear quickly.
  • Unused assets_per_share column in market_books: dismissed, and the dismissal holds. No response reads it, but it costs one text column and records which ratio applied to each book. This is a nit with no effect on behavior.
  • Book references in unwrapped swap mode (docs/src/prices.md): addressed in b69a388. The docs now say to send references with denomination: "wrapped", or to convert them with wrappedReference * inputAssetsPerShare / outputAssetsPerShare. This is the exact inverse of what normalize_calldata_price_cap does on the server.

I found no new defects. CI is green on b69a388 (all four Rust test shards, static checks, rust-checks and docs).

claude-opus-5-5 · high · 30 s

@rain-marvin

rain-marvin Bot commented Oct 6, 2026

Copy link
Copy Markdown

🔎 Verifying that my findings from b69a388 are addressed at b69a388, started by @findolor. The review will appear here when it's done.

@rain-marvin rain-marvin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a nullable executableBook (best bid and best ask, each optional) to the /v1 and /v2 price responses. The book is built only from canonical wrapped-token orders, stored latest-only in market_books, and written in the same transaction as the midpoint snapshots.

The head is still b69a388, the commit I reviewed and approved before, so there is no new diff to check. I checked each fix again in the code at that commit.

My earlier findings:

  • Variant orders in the executable book (src/market_price.rs): resolved. aggregate_observations feeds executable_books only when source_asset_address == asset_address, so legacy and unwrapped orders still feed the display midpoint but cannot set bestBid or bestAsk.
  • Silent observed_at guard (src/db/market_price_history.rs): resolved. The guard stays, but the code now counts book rows that the upsert skips (rows_affected() == 0) and logs a warning with the written and skipped counts.

Threads resolved by hand:

  • Crossed canonical executable book clears both sides (src/market_price.rs): dismissed, and the dismissal holds. docs/src/prices.md says that crossed books publish both sides as null, and the code logs ignoring crossed canonical executable book. This fails closed: a client loses its reference but never gets a looser one.
  • Unused assets_per_share column in market_books: dismissed, and the dismissal holds. No response reads it, but it costs one text column and records which ratio applied to each book. This is a nit with no effect on behavior.
  • Book references in unwrapped swap mode (docs/src/prices.md): addressed. The docs now say to send references with denomination: "wrapped", or to convert them with wrappedReference * inputAssetsPerShare / outputAssetsPerShare.

I found no new defects. CI is green on b69a388 (all four Rust test shards, static checks, rust-checks and docs).

claude-opus-5-5 · high · 30 s

@findolor
findolor merged commit e4ab08e into main Oct 6, 2026
13 checks passed
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