Skip to content

feat: serve the underlying stock price on PriceFrame and Quote (v0.7.0, RAI-2197) - #4

Open
hardyjosh wants to merge 1 commit into
masterfrom
feat/underlying-price
Open

feat: serve the underlying stock price on PriceFrame and Quote (v0.7.0, RAI-2197)#4
hardyjosh wants to merge 1 commit into
masterfrom
feat/underlying-price

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What

Adds the underlying (stock) directional price to PriceFrame and Quote, alongside the existing vault-token rates and nav_ratio:

  • underlying_rate_base_to_quote: WireFloat
  • underlying_rate_quote_to_base: WireFloat

These are the directional rates for the vault's underlying ERC4626 asset (the offchain stock) — the same rates the model computes before scaling the mark by the vault NAV ratio. Same directionality and spread policy as rate_base_to_quote / rate_quote_to_base; the only difference is the NAV scaling.

Why (RAI-2197 / RAI-1479)

Root of the NAV-ratio "derive, don't gate" pivot. The exact-match NAV gate was found to be a DoS surface (audit H03). Serving the underlying lets on-chain consumers derive the vault price atomically (vault_price = underlying × convertToAssets(1 share)) instead of asserting a signed ratio. As Josh put it: serve both and let consumers derive if they like.

Compatibility

  • Additive and wire-backward-compatible. Both fields carry #[serde(default)], so frames from producers that predate them decode to the all-zero WireFloat. A real stock rate is never all-zero, so that value reads as "not carried".
  • Existing vault rates and nav_ratio are untouched.
  • Non-vault base (nav_ratio zero): underlying_rate_* == rate_* (no separate underlying).

Version bump + re-pin

Wire-contract version bumped v0.6.0 → v0.7.0. Following the repo's post-merge tag convention, the merge commit gets tagged v0.7.0 and consumers re-pin their tag = "v0.6.0" to tag = "v0.7.0". The companion st0x.pricing PR pins this branch rev meanwhile and carries a Cargo.toml comment noting the re-pin step.

Tests

  • Exact wire round-trip of both new fields (distinct byte patterns per field).
  • Backward-compat decode: strip the keys from an encoded frame → both default to zero, rest of the frame intact.
  • Wire-size ceiling updated for the two added byte-strings.

Local gate: cargo test, cargo clippy --all-targets -D warnings, cargo fmt --check, and the full pre-commit run --all-files in rainix#rust-shell (incl. denofmt markdown) all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TQ3QV1QrwmaS7ACSf7cds7


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@linear-code

linear-code Bot commented Sep 2, 2026

Copy link
Copy Markdown

RAI-2197

Comment thread .gitignore Outdated
@@ -1,2 +1,3 @@
/target
Cargo.lock
/.pre-commit-config.yaml

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: unrelated to the wire change, and this ignore silently blocks committing a shared config here later. Possibly worth dropping.

@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.

One optional nit comment posted, otherwise LGTM 👍

…0, RAI-2197)

Add underlying_rate_base_to_quote / underlying_rate_quote_to_base to
PriceFrame and Quote: the directional rates for the vault's underlying
ERC4626 asset (the offchain stock). Defined as the served vault rate
un-scaled by the NAV ratio, so consumers can derive the vault price
atomically as underlying * live convertToAssets(1 share) and reproduce
the SERVED vault rate rather than trusting a signed vault rate, which
removes the exact-match NAV gate as a DoS surface.

Because the underlying is derived from the served vault rate it is
clamp-consistent: when the model's no-cross guard clamps the vault rate,
the underlying reflects the clamped rate, so deriving from it cannot
reconstruct an unclamped, self-crossing quote. Field docs and
wire-format.md state the contract, including that decimal-float division
is not a bit-exact inverse of multiplication (derive reproduces the
served rate to Float precision).

Additive and wire-backward-compatible: both fields carry #[serde(default)]
so frames from producers that predate them decode to the all-zero Float.
Existing vault rates and nav_ratio are untouched. Wire-contract version
bumped 0.6.0 -> 0.7.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQ3QV1QrwmaS7ACSf7cds7
@hardyjosh
hardyjosh force-pushed the feat/underlying-price branch from 7794ed4 to 8bcfd87 Compare September 4, 2026 13:26
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