Skip to content

feat: /context/v7 signs the underlying price and drops the NAV ratio (RAI-2198) - #70

Open
hardyjosh wants to merge 1 commit into
mainfrom
feat/context-v7-underlying
Open

feat: /context/v7 signs the underlying price and drops the NAV ratio (RAI-2198)#70
hardyjosh wants to merge 1 commit into
mainfrom
feat/context-v7-underlying

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What

Adds /context/v7 — the signed-context version for the "derive, don't gate" pivot (RAI-1479). v7 signs the vault's UNDERLYING asset price and drops the signed NAV ratio entirely. v1–v6 stay served unchanged.

Why

v6's /context/v6 signs the NAV ratio at slot 9 and forces the strategy to assert exact equality against the vault's live convertToAssets answer at settlement. That exact-match gate is a DoS surface (audit H03): the vault NAV can step between the sign and the settle for reasons entirely outside an attacker's control, and every step bricks otherwise-valid signed frames.

v7 removes the gate. It signs only the underlying price and leaves the ratio unsigned. The consuming Rainlang strat (RAI-2199, separate follow-on) reads the vault's live erc4626-convert-to-assets on-chain at settlement and derives the vault price atomically:

vault_price = underlying × convertToAssets(1 share)

Nothing to straddle — the ratio used is always the live one. The underlying is the only quantity the on-chain side can re-derive the vault price from, so it is what must be signed. The server stays a dumb carrier: it signs the underlying rate straight off the same cached Quote, no re-derivation.

v7 slot layout (0-indexed) — load-bearing for RAI-2199

The v5 nine-slot shape, but slot 1 is the underlying price and there is no slot 9:

  • context[0]: schema version (Rain Float, = 7)
  • context[1]: UNDERLYING price (Rain Float; directional underlying_rate_* for the request's swap direction, inverted into Raindex ratio units, spread included)
  • context[2]: publish_time (Rain Float, Unix seconds)
  • context[3]: session tag (Rain IntOrAString V3, bytes32)
  • context[4]: session_start (Rain Float, Unix seconds)
  • context[5]: session_end (Rain Float, Unix seconds)
  • context[6]: input_token address (bytes32, Address left-padded)
  • context[7]: output_token address (bytes32, Address left-padded)
  • context[8]: quote expiry (Rain Float, Unix seconds; less-than consumer assert makes the expiry second itself EXCLUSIVE)

No context[9]. 9 elements total. The ratio is never signed; the strat reads it live on-chain.

Direction selection & fail-closed

pick_underlying_rate_bytes is the v7 analogue of pick_rate_bytes: it picks underlying_rate_quote_to_base / underlying_rate_base_to_quote by the same input/output orientation and inverts the same way. It fails closed on the all-zero "not carried" sentinel (a producer predating the underlying_rate_* fields, or a misconfigured pair) rather than signing a garbage mark — unlike v6's NAV-ratio zero, which is a legitimate "no assertion" sentinel the strategy may accept, a zero underlying price is never usable.

The underlying rate is carried from PriceFrame into the cached Quote so v7 signs off the same observation as the rest of the context.

Dependency pin

Re-pins st0x-pricing-types from tag = "v0.6.0" to rev = 7794ed474dd032a927c5b51a8b32c28259f4eb82 — the head of st0x.pricing-types #4 (branch feat/underlying-price), which added the directional underlying_rate_* fields to PriceFrame/Quote. Re-pin to tag = "v0.7.0" once #4 merges and is tagged (noted in the Cargo.toml comment).

v6 left as-is

v6 (ratio-signing) is kept, unchanged, still served on /context/v6. v7 is an additive new version per the repo's convention (every earlier version stays served); nothing consuming v6 is touched by this PR.

Tests

  • v7 route + slot layout (underlying at slot 1, no ratio slot, expiry at slot 8)
  • v7 is the v5 shape byte-for-byte past slot 0
  • v5/v6 still sign the vault rate when the underlying differs (slot-1 meaning is per-schema; no leak)
  • maker orientation (buy vs sell picks the right underlying rate) across v1/v4/v5/v6/v7
  • fail-closed on absent underlying rate → 500
  • underlying rates carry through PriceFrameQuote bit-for-bit

Local gate

nix develop -c oracle-rs-test ✅ · nix develop -c oracle-rs-static ✅ · rainix pre-commit ✅. (cargo-audit fails pre-existingly on main — crossbeam-epoch/ruint — not from this change.)

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

…(RAI-2198)

Part of the "derive, don't gate" pivot (RAI-1479). v6's exact-match NAV
gate is a DoS surface (audit H03): the vault NAV can step between sign and
settle for reasons outside any attacker's control, bricking otherwise-valid
signed frames. v7 signs only the vault's UNDERLYING asset rate and leaves
the NAV ratio UNSIGNED — the consuming strategy (RAI-2199) reads the vault's
live convertToAssets on-chain at settlement and derives the vault price
(underlying x convertToAssets(1 share)) atomically. Nothing to straddle.

- /context/v7: the v5 nine-slot shape, slot 1 carrying the underlying price
  (directional underlying_rate_*, picked and inverted exactly as the vault
  rate), NO slot-9 ratio. v1-v6 stay served unchanged.
- pick_underlying_rate_bytes: v7 analogue of pick_rate_bytes; fails closed on
  the all-zero "not carried" sentinel (old producer / misconfigured pair)
  rather than signing a garbage mark.
- Carry underlying_rate_* from PriceFrame into the cached Quote so v7 signs
  off the same observation as everything else.
- Re-pin st0x-pricing-types to the rev that added the underlying_rate_*
  fields; re-pin to tag v0.7.0 once pricing-types #4 merges and is tagged.

Tests: v7 route + slot layout (underlying at slot 1, no ratio slot),
v7-is-v5-shape, v5/v6 still sign the vault rate when underlying differs,
maker orientation across v1/v4/v5/v6/v7, fail-closed on absent underlying.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQ3QV1QrwmaS7ACSf7cds7
@linear-code

linear-code Bot commented Sep 2, 2026

Copy link
Copy Markdown

RAI-2198

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

LGTM 👍

I was wondering if this gave up the oracle's bound on the vault price, but I went back to your note on telegram and it cleared it up for me - keeping the ratio and banding it covers that.

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