Skip to content

feat: InventoryHedge — market-maker inventory-hedging vault#41

Draft
tiero wants to merge 8 commits into
masterfrom
claude/sleepy-cerf-c1mng6
Draft

feat: InventoryHedge — market-maker inventory-hedging vault#41
tiero wants to merge 8 commits into
masterfrom
claude/sleepy-cerf-c1mng6

Conversation

@tiero

@tiero tiero commented Jun 10, 2026

Copy link
Copy Markdown
Member

What

A new .ark contract — examples/hedging/inventory_hedge.ark — that sketches the on-chain core of the design note docs/mm-residual-hedge.md. It is a perpetual, BTC-settled, fully-collateralized market-maker inventory hedge that generalizes examples/stability/stability_vault.ark:

  • claim leg (claimPk, the desk): holds a fiat claim targetFiat, delta-flat.
  • long leg (longPk, the treasury): holds the residual BTC upside, posts the over-collateral, earns funding.
  • Oracle-marked (Fuji pattern), no liquidation (settlement is a clamp into [0, totalCollateral]), one instance per BTC/<fiat> pair via ticker.

Functions (6 → 12 tapleaves)

transfer, updateFunding, addCapital, removeCapital, redeem (desk redeems its fiat claim for BTC), withdraw (treasury-driven settlement).
Funding is rolled on-chain (>= 0 guard + accrual); the dynamic, imbalance-driven value is computed off-chain by the desk's risk engine (design note §4) and supplied to updateFunding.

Also in this PR

  • tests/inventory_hedge_test.rs — roundtrip (both variants emit, non-empty witness schemas), oracle-path checks (OP_CHECKSIGFROMSTACK + OP_DIV64), redeem clamp branches, <SERVER_KEY> injection, and updatedAt-stripped determinism. 8 tests, green.
  • docs/mm-residual-hedge.md — design note plus "Part II — Transaction flows" (per-function input/output layouts), now a single consolidated doc.
  • playground/main.jsHedging project folder wiring inventory_hedge.ark into the WASM playground. (playground/contracts.js is gitignored and regenerated by ./playground/generate_contracts.sh.)

Acceptance

  • cargo run -- examples/hedging/inventory_hedge.ark -o /tmp/ih.json ✅ compiles; only the same benign output-invariant warnings stability_vault.ark emits (computed spend-time locals used in value >= X checks).
  • cargo fmt --check ✅ clean.
  • cargo test ✅ green (full suite, including the 8 new tests).

Dialectical review

A self-review in thesis / antithesis / synthesis is in the first comment, including an empirical comparison against an alternative one-shot draft (compiled in this toolchain: its min() clamp, new P2TR(pk) payouts, and bytes-bound state script all degrade to unbound placeholders — the clamp, payout destination, and state recursion go unenforced). Open questions for reviewers are flagged there.

Draft: opened for review, not for merge.

Summary by CodeRabbit

  • New Features

    • Added a perpetual BTC-settled, fully-collateralized inventory hedge contract for market-making desks.
    • Added a playable Playground project demonstrating the hedge contract.
  • Documentation

    • Added comprehensive design and lifecycle documentation for the hedging mechanism.
  • Tests

    • Added test suite validating compilation, ABI/ASM invariants, oracle-settlement behavior, and determinism.

Perpetual, BTC-settled, fully-collateralized market-maker inventory hedge
generalizing stability_vault.ark: a desk converts residual BTC inventory into a
delta-flat fiat claim (claim leg) while a treasury holds the BTC long leg and
earns funding. Oracle-marked, no liquidation, one instance per BTC/<fiat> pair.

- examples/hedging/inventory_hedge.ark (+ compiled .json): transfer,
  updateFunding, addCapital, removeCapital, claimExit, longExit (12 tapleaves).
- tests/inventory_hedge_test.rs: roundtrip (both variants, non-empty witness
  schemas), oracle-path checks, claimExit clamp branches, <SERVER_KEY> injection,
  updatedAt-stripped determinism.
- docs/mm-residual-hedge.md (design note) + docs/hedging/inventory_hedge_tx_flows.md
  (per-function tx layouts).
- playground: Hedging project folder wired in main.js.

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tiero, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 49 minutes and 51 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9ef2d0f-d8bd-4413-88bf-06427f4c05dc

📥 Commits

Reviewing files that changed from the base of the PR and between a1aeef0 and 8f498c1.

📒 Files selected for processing (3)
  • docs/mm-residual-hedge.md
  • examples/hedging/inventory_hedge.ark
  • examples/hedging/inventory_hedge.json

Walkthrough

This PR adds a BTC-settled InventoryHedge: a design doc, an Ark contract implementing six state transitions (transfer, updateFunding, addCapital, removeCapital, redeem, withdraw), compiled JSON artifact and playground registration, plus tests validating ASM/witness invariants and determinism.

Changes

InventoryHedge Hedging Mechanism

Layer / File(s) Summary
Hedging Architecture & Design Documentation
docs/mm-residual-hedge.md
Design document covering vault structure (claim/long legs), funding model, multi-currency BTC-delta aggregation, oracle/time guards, funding accrual, and specifications for transfer, updateFunding, addCapital, removeCapital, redeem, and withdraw. Build/playground instructions included.
Core Contract & Funding/Transfer
examples/hedging/inventory_hedge.ark (lines 1–141)
Contract definition with constructor fields, funding accrual math using elapsed time and fundingRatePerSec, signature/output validations, and transfer implementation preserving collateral and reassigning claim key.
Capital Operations: addCapital & removeCapital
examples/hedging/inventory_hedge.ark (lines 142–210)
addCapital enforces positive deposit and output preservation; removeCapital verifies fresh oracle signature, computes funding-accrued guard, and enforces minimum collateral ratio for withdrawals without mutating on-chain funding state.
Terminal Settlement: redeem & withdraw
examples/hedging/inventory_hedge.ark (lines 212–314)
redeem (claim-authorized) and withdraw (long-authorized) perform oracle-validated clamped payouts, compute claimRaw, route collateral between claim/long legs with dust/second-output conditions.
Compiled Artifact, Metadata & Playground Integration
examples/hedging/inventory_hedge.json, playground/main.js
JSON artifact with ABI, server/non-server function variants, witness schemas, ASM, compiler metadata and warnings, embedded source, and registration of the contract in the Arkade Playground.
Test Suite: compilation, ASM, and determinism
tests/inventory_hedge_test.rs
Rust tests that compile the contract, assert contract/function counts and variant presence, verify ASM/witness schemas, check SERVER_KEY injection and oracle-op usage in settling functions, validate non-oracle paths, assert clamp branching, and check deterministic compilation output.

Sequence Diagram

sequenceDiagram
  participant Desk as Market Desk
  participant Treasury as Treasury/Provider
  participant Vault as InventoryHedge
  participant Claim as Claim Leg
  participant Long as Long Leg
  participant Oracle as Oracle
  Desk->>Vault: transfer (new claim key)
  Treasury->>Vault: updateFunding (roll accrual & set rate)
  Treasury->>Vault: addCapital (deposit sats)
  Treasury->>Vault: removeCapital (withdraw with oracle check)
  Oracle->>Vault: signed price + timestamp
  Desk->>Vault: redeem (claim payout, oracle-settled)
  Treasury->>Vault: withdraw (long payout, oracle-settled)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • arkanaai
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main addition: a market-maker inventory-hedging vault called InventoryHedge, which is the primary focus of all five changed files (design doc, Ark contract, JSON artifact, tests, and playground integration).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sleepy-cerf-c1mng6

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

tiero commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Dialectical review

Thesis — the case for this implementation

  • Faithful to the reference. It mirrors stability_vault.ark's proven shapes: self-replacement state commitment via require(tx.outputs[0].scriptPubKey == new InventoryHedge(...)), the Fuji oracle (checkSigFromStack(sig, oraclePk, sha256(ticker + price + time))), the interleaved /1e6 funding accrual, the >= 0 clock-regression guards, and new SingleSig(pk) payout targets. Nothing exotic — every construct is already load-bearing in a shipped contract.
  • It actually compiles to enforceable script. 12 tapleaves (6 fns × 2 variants), both variants carry non-empty witness schemas, <SERVER_KEY> is auto-injected on every cooperative leaf and never a constructor param, and the claimExit clamp is real (three OP_IF branches + the 330-sat dust guard). The only output-invariant warnings are the same benign class stability_vault.ark emits — computed locals (delta, claimRaw, longPayout) used inside value >= X comparisons, not spender-supplied placeholders.
  • Scope discipline. Per the spec, funding stays a >= 0 guard + accrual; the imbalance-driven value lives off-chain (design note §4). removeCapital computes accrual only for the ratio guard and deliberately does not roll lastUpdate, so a withdrawal never truncates funding owed the desk.
  • Gates pass: cargo run … -o /tmp/ih.json, cargo fmt --check, and cargo test (full suite + 8 new tests) are all green.

Antithesis — the case against (and an alternative draft)

A separate one-shot draft (min() clamp, new P2TR(pk) payouts, bytes nextScript = new InventoryHedge(...)) reads cleaner than this PR. So is terse-but-untested better? I compiled it in this toolchain to find out — and the readability hides three correctness holes the compiler exposes:

  1. min(claimRaw, totalCollateral) is not a builtin. It compiles to a literal placeholder token <min(claimRaw, totalCollateral)> — an unbound value the spender supplies freely. The clamp is not enforced on-chain. This PR uses nested if/require (uglier under single-assignment, but the bound is real).
  2. new P2TR(pk) references a contract that doesn't exist in the repo and the draft never imports single_sig.ark; it emits <VTXO:P2TR(...)> pointing at nothing. The repo convention is new SingleSig(pk).
  3. bytes nextScript = new InventoryHedge(...) then compare breaks VTXO-placeholder resolution: it emits <nextScript> ("cannot be constructed without a binding") instead of inlining <VTXO:InventoryHedge(...)>. State recursion isn't actually enforced. The working examples always inline new Contract(...) directly in the require.

So "compiles successfully" ≠ "correct": the draft compiles and leaves the clamp, the payout destination, and the state transition all unbound. The lesson cuts at this PR too — a green cargo run is necessary, not sufficient; the behavioral asserts in the test file are what make the difference legible.

But the draft lands two fair hits worth keeping:

  • int64 overflow. newTargetFiat * 1e8 overflows int64 around targetFiat ≈ 9.2e10 minor units. This PR inherits that bound from stability_vault.ark (only funding is interleaved). For plausible single-vault sizes it's fine; for very large claims the sats conversion would need interleaving too.
  • min() would be cleaner if it existed — the nested-if clamp is verbose. A future compiler builtin would simplify three functions here.

Synthesis

Prefer the implementation whose guarantees are machine-checked against this compiler, not the one that reads best. This PR is the conservative generalization: same primitives as the reference, clamp/recursion/payout all genuinely enforced, gates green. Fold in the draft's insights (document the int64 ceiling; consider a min/clamp builtin later) without its constructs (min, P2TR, bytes-bound state), which don't survive contact with the toolchain.

Open questions for reviewers:

  1. Overflow ceiling — is targetFiat bounded well under ~9.2e10 minor units in practice, or should claimRaw interleave like the funding math?
  2. merge/split — the design note §6 lists them for resizing; the spec scoped them out. Add as a fast-follow, or keep this minimal?
  3. longExit symmetry — should a provider-driven exit charge the desk anything, or stay fee-free as here?

Draft — opened for review, not merge.


Generated by Claude Code

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Playground Preview

A live preview of this PR's playground is available at:
https://arkade-os.github.io/compiler/pr-previews/pr-41/

Built from commit 03a6ab2a144393c831fbfacb7320d86bd1c44612 · Workflow run

claude added 2 commits June 10, 2026 00:05
Merge docs/hedging/inventory_hedge_tx_flows.md into docs/mm-residual-hedge.md as
'Part II — Transaction flows', cross-referencing the design sections (§2/§4/§6/
§9). Remove the standalone file and point the playground Hedging folder at the
single doc.

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg
Drop the Exit suffix: claimExit -> redeem (desk redeems its fiat claim for BTC),
longExit -> withdraw (treasury-driven settlement). Update the contract, tests,
and the Part II transaction-flow doc.

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

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

🔍 Arkana Code Review — InventoryHedge

⚠️ PROTOCOL-CRITICAL: This PR adds a new settlement contract with oracle-priced exits, collateral ratio enforcement, and funding accrual. Requires human sign-off regardless of code quality.

Overall this is a clean generalization of StabilityVault with the exit fee removed and naming adapted for the hedging use case. The core settlement math, oracle pattern, anti-grief rule, and clamp logic are correctly carried over. The rename to redeem/withdraw in the latest commit is clean. No downstream consumers exist yet.

I found one substantive issue and a few items worth discussing before this leaves draft.


🔴 Issue — removeCapital missing newTargetFiat > 0 guard

inventory_hedge.ark:116 (the removeCapital function)

updateFunding correctly guards require(newTargetFiat > 0, "claim wiped by funding"), but removeCapital computes the same newTargetFiat = targetFiat + delta and feeds it into the collateral-ratio check without validating it's positive:

int newTargetFiat      = targetFiat + delta;
int currentClaimBase   = newTargetFiat * 100000000 / oraclePrice;
int minCollateral      = currentClaimBase * (100 + collateralRatioPct) / 100;

If the initial fundingRatePerSec is negative (which the constructor does not prevent — the >= 0 constraint only applies to updateFunding's new rate), and enough time passes before the first updateFunding call, newTargetFiat can go negative → currentClaimBase negative → minCollateral negative → the ratio check newTotalCollateral >= minCollateral becomes trivially true → the long leg drains the vault.

Yes, this is the same pattern as StabilityVault. And yes, in practice the initial rate should be non-negative for a hedging vault. But defense-in-depth says: add require(newTargetFiat > 0, "claim wiped by funding") in removeCapital (and arguably in redeem/withdraw too, before the clamp — though the clamp handles it safely, an explicit guard documents intent).

Same applies to redeem and withdraw at lines ~651 and ~696. The clamp (if (claimRaw <= 0)) handles the case correctly, but an explicit require(newTargetFiat > 0) before the conversion makes the invariant visible and prevents subtle bugs if the clamp logic is ever refactored.


🟡 Observations (non-blocking, but worth considering)

1. Doc comment says "signed" but contract enforces >= 0inventory_hedge.ark:10 says fundingRatePerSec is a "signed fixed-point fraction" but updateFunding enforces newFundingRatePerSec >= 0. The initial rate at construction is the only place a negative value could enter. If negative initial rates are not intended for the hedging use case, consider calling this out explicitly in the doc (or adding a construction-time guard if the language supports it). Current wording could mislead an integrator into thinking negative rates are supported at runtime.

2. Doc §6 references split for resizing but the contract omits it — The design doc talks about "split → peel off part of the claim when D shrinks" and "merge → combine positions when D grows." These are deferred to "thinnest first build" per §10, but the doc still reads as if they exist. Consider either (a) noting in the doc that split/merge are deferred, or (b) adding a forward-reference to a future PR.

3. Compiler warnings in the JSON artifact — The compiled inventory_hedge.json carries 50+ output-invariant warnings (e.g., placeholder <tx.offchainTime> is not in witnessSchema). These are expected for computed intermediates and match StabilityVault's pattern, but shipping them in the committed JSON is noisy. If the compiler has a way to suppress expected warnings, it'd clean up the artifact.

4. Test coverage is compilation-level only — The test suite (tests/inventory_hedge_test.rs) pins structural properties (12 tapleaves, SERVER_KEY injection, OP_IF branches for the clamp). There are no behavioral/simulation tests for edge cases like: anti-grief rule triggering, collateral ratio boundary, dust threshold routing, zero-elapsed funding. This matches StabilityVault's test pattern, but for a protocol-critical contract, consider adding property-based or scenario tests that exercise the arithmetic with concrete values.


✅ What looks good

  • Oracle verification: Fuji-pattern sha256(ticker + price + time) + checkSigFromStack + 600s freshness window — correct and consistent with StabilityVault.
  • Anti-grief rule in updateFunding: correctly prevents zero-accrual clock advancement when fundingRatePerSec != 0.
  • Settlement clamp in redeem/withdraw: three-way branch (claim wiped / fully covered / split) with 330-sat dust guard — correct.
  • Self-replacement pattern: all non-terminal functions correctly assert tx.outputs[0].scriptPubKey == new InventoryHedge(...) with the next state.
  • Exit variants: 2-of-2 (or 3-of-3 for transfer) multisig + CLTV — standard operator-liveness backstop.
  • removeCapital correctly uses accrued funding for the ratio check without rolling it into the vault state (no clock advancement).
  • Playground integration is clean — just a new project entry.
  • Rename to redeem/withdraw (commit 3) was applied consistently across .ark, .json, .md, and .rs files.

Verdict: Request changes on the newTargetFiat > 0 defense-in-depth guard. Everything else is solid. Flagging for mandatory human review — this is a new settlement contract that handles real funds.

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

🔍 Arkana Code Review — InventoryHedge

⚠️ PROTOCOL-CRITICAL: This PR adds a new settlement contract with oracle-priced exits, collateral ratio enforcement, and funding accrual. Requires human sign-off regardless of code quality.

Overall this is a clean generalization of StabilityVault with the exit fee removed and naming adapted for the hedging use case. The core settlement math, oracle pattern, anti-grief rule, and clamp logic are correctly carried over. The rename to redeem/withdraw in the latest commit is clean. No downstream consumers exist yet.

I found one substantive issue and a few items worth discussing before this leaves draft.


🔴 Issue — `removeCapital` missing `newTargetFiat > 0` guard

`inventory_hedge.ark:116` (the `removeCapital` function)

`updateFunding` correctly guards `require(newTargetFiat > 0, "claim wiped by funding")`, but `removeCapital` computes the same `newTargetFiat = targetFiat + delta` and feeds it into the collateral-ratio check without validating it's positive:

```ark
int newTargetFiat = targetFiat + delta;
int currentClaimBase = newTargetFiat * 100000000 / oraclePrice;
int minCollateral = currentClaimBase * (100 + collateralRatioPct) / 100;
```

If the initial `fundingRatePerSec` is negative (which the constructor does not prevent — the `>= 0` constraint only applies to `updateFunding`'s new rate), and enough time passes before the first `updateFunding` call, `newTargetFiat` can go negative → `currentClaimBase` negative → `minCollateral` negative → the ratio check `newTotalCollateral >= minCollateral` becomes trivially true → the long leg drains the vault.

Yes, this is the same pattern as StabilityVault. And yes, in practice the initial rate should be non-negative for a hedging vault. But defense-in-depth says: add `require(newTargetFiat > 0, "claim wiped by funding")` in `removeCapital` (and arguably in `redeem`/`withdraw` too, before the clamp — though the clamp handles it safely, an explicit guard documents intent).

Same applies to `redeem` and `withdraw` at lines ~651 and ~696. The clamp (`if (claimRaw <= 0)`) handles the case correctly, but an explicit `require(newTargetFiat > 0)` before the conversion makes the invariant visible and prevents subtle bugs if the clamp logic is ever refactored.


🟡 Observations (non-blocking, but worth considering)

1. Doc comment says "signed" but contract enforces `>= 0` — `inventory_hedge.ark:10` says `fundingRatePerSec` is a "signed fixed-point fraction" but `updateFunding` enforces `newFundingRatePerSec >= 0`. The initial rate at construction is the only place a negative value could enter. If negative initial rates are not intended for the hedging use case, consider calling this out explicitly in the doc (or adding a construction-time guard if the language supports it). Current wording could mislead an integrator into thinking negative rates are supported at runtime.

2. Doc §6 references `split` for resizing but the contract omits it — The design doc talks about "split → peel off part of the claim when D shrinks" and "merge → combine positions when D grows." These are deferred to "thinnest first build" per §10, but the doc still reads as if they exist. Consider either (a) noting in the doc that split/merge are deferred, or (b) adding a forward-reference to a future PR.

3. Compiler warnings in the JSON artifact — The compiled `inventory_hedge.json` carries 50+ `output-invariant` warnings (e.g., `placeholder <tx.offchainTime> is not in witnessSchema`). These are expected for computed intermediates and match StabilityVault's pattern, but shipping them in the committed JSON is noisy. If the compiler has a way to suppress expected warnings, it'd clean up the artifact.

4. Test coverage is compilation-level only — The test suite (`tests/inventory_hedge_test.rs`) pins structural properties (12 tapleaves, SERVER_KEY injection, OP_IF branches for the clamp). There are no behavioral/simulation tests for edge cases like: anti-grief rule triggering, collateral ratio boundary, dust threshold routing, zero-elapsed funding. This matches StabilityVault's test pattern, but for a protocol-critical contract, consider adding property-based or scenario tests that exercise the arithmetic with concrete values.


✅ What looks good

  • Oracle verification: Fuji-pattern `sha256(ticker + price + time)` + `checkSigFromStack` + 600s freshness window — correct and consistent with StabilityVault.
  • Anti-grief rule in `updateFunding`: correctly prevents zero-accrual clock advancement when `fundingRatePerSec != 0`.
  • Settlement clamp in `redeem`/`withdraw`: three-way branch (claim wiped / fully covered / split) with 330-sat dust guard — correct.
  • Self-replacement pattern: all non-terminal functions correctly assert `tx.outputs[0].scriptPubKey == new InventoryHedge(...)` with the next state.
  • Exit variants: 2-of-2 (or 3-of-3 for transfer) multisig + CLTV — standard operator-liveness backstop.
  • `removeCapital` correctly uses accrued funding for the ratio check without rolling it into the vault state (no clock advancement).
  • Playground integration is clean.
  • Rename to `redeem`/`withdraw` (commit 3) was applied consistently across .ark, .json, .md, and .rs files.

Verdict: Request changes on the `newTargetFiat > 0` defense-in-depth guard. Everything else is solid. Flagging for mandatory human review — this is a new settlement contract that handles real funds.

Add require(newTargetFiat > 0) in removeCapital so a claim driven non-positive by
a negative *initial* funding rate (not prevented at construction) can't make
minCollateral <= 0 and trivially pass the ratio check — which would let the long
leg drain the vault. Matches the existing updateFunding guard.

Deliberately NOT added to redeem/withdraw: their claimRaw <= 0 clamp branch is
the correct terminal settlement for a wiped claim (all collateral to the long
leg); a hard require there would abort a legitimate withdraw. Documented inline.

Also: clarify fundingRatePerSec doc (runtime >= 0; negative only possible at
construction, unsupported), and note split/merge are deferred (design doc Part II).

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

tiero commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Thanks — substantive review. Addressed in 9ed13de.

🔴 removeCapital guard — fixed

Agreed, this is the real one. Added require(newTargetFiat > 0, "claim wiped by funding") in removeCapital before the ratio math, matching updateFunding. Without it a negative initial fundingRatePerSec (not prevented at construction) could drive newTargetFiatminCollateral ≤ 0 and trivially pass the ratio check, draining the vault. "claim wiped by funding" now appears in both updateFunding and removeCapital.

redeem / withdraw — intentionally not guarded (with reasoning)

I did not add require(newTargetFiat > 0) here, and I'd argue it would be a regression rather than defense-in-depth. These are terminal settlements, and the claimRaw <= 0 branch is the correct outcome for a wiped claim: route the entire collateral to the long leg. A hard require before the clamp would abort a legitimate settlement — including the long leg's own withdraw, which must succeed precisely when the claim is worthless so the treasury can recover its collateral. The clamp is the invariant, not a require. I added an inline comment to both functions documenting this on purpose so a future refactor doesn't "fix" it into a bug.

The distinction vs removeCapital: removeCapital is non-terminal (keep the vault alive, reclaim only excess) — there a wiped claim has no valid semantics and should force a withdraw instead, so aborting is right.

🟡 Observations

  1. "signed" doc wording — clarified. The constructor comment now states runtime updates enforce >= 0 and a negative value can only enter at construction (and shouldn't, for a hedging vault).
  2. split/merge deferred — added an explicit "Deferred (fast-follow)" note in the design doc's Part II: they're reusable from stability_vault.ark but out of scope for the §10 thinnest build; only transfer ships now.
  3. JSON warnings — these are the same benign output-invariant class stability_vault.ark emits (computed spend-time locals used in value >= X checks, never spender-supplied). Suppressing them would be a change to the gated compiler (src/compiler), out of scope for this contract PR; happy to file a separate issue for an "expected-intermediate" annotation.
  4. Behavioral tests — worth flagging that this repo has no script interpreter; it's a compiler, so every contract's tests (StabilityVault included) are necessarily compilation/ASM-structural, not arithmetic simulation. Property/scenario execution would need a Tapscript evaluator that doesn't exist here. The new test pins the structural invariants (12 tapleaves, <SERVER_KEY>, the clamp's OP_IF branches, oracle-path opcodes); concrete-value simulation is a separate harness, not something I can add in-tree today.

Staying in draft — acknowledging the protocol-critical flag; this wants human sign-off before it leaves draft, not just a green bot.


Generated by Claude Code

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

🔍 Arkana Code Review — follow-up on 9ed13de (removeCapital guard)

Previous review requested require(newTargetFiat > 0) in removeCapital. This commit delivers it correctly:

  • removeCapital (line ~170): Guard added after newTargetFiat computation, before the collateral ratio check. Correct placement — blocks the negative-minCollateral drain vector.
  • redeem/withdraw: Deliberately left without a hard guard. The reasoning in the added comments is sound: these are terminal settlement functions where the clamp (if (claimRaw <= 0)) is the correct invariant — it routes all collateral to the long leg, which is the right outcome for a wiped claim. A require would block legitimate settlement. This is actually better than what I suggested.
  • Doc fixes: fundingRatePerSec comment now correctly documents the runtime vs. construction asymmetry. Split/merge deferred note added to the design doc.
  • JSON artifact: Recompiled cleanly — OP_GREATERTHAN opcode present in removeCapital script, additional comparison witness schema entry matches.

All requested changes resolved. Approving.

⚠️ PROTOCOL-CRITICAL — human sign-off still required. This is a new settlement contract that handles real BTC collateral with oracle-priced exits.

@tiero tiero marked this pull request as ready for review June 10, 2026 14:03

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
docs/mm-residual-hedge.md (1)

91-91: 💤 Low value

Consider adding language specifiers to fenced code blocks.

The markdown linter flags 12 fenced code blocks without language specifiers. While the content is correct, adding identifiers like text or pseudocode would improve rendering and markdown hygiene.

Example fix for one block
-```
+```text
 fundingRate = clamp( premiumIndex + carryComponent , -cap, +cap )
 premiumIndex ∝ open-interest skew between claim and long legs

Also applies to: 132-132, 287-287, 299-299, 313-313, 337-337, 355-355, 367-367, 379-379, 388-388, 411-411, 439-439

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/mm-residual-hedge.md` at line 91, Multiple fenced code blocks in
docs/mm-residual-hedge.md are missing language specifiers; update each
triple-backtick block (``` ... ```) noted in the review to include an
appropriate language tag such as text or pseudocode (e.g., change ``` to
```text) so the markdown linter stops flagging them and rendering improves;
apply this consistently to all flagged blocks (including the example block shown
and the other listed blocks).

Source: Linters/SAST tools

tests/inventory_hedge_test.rs (1)

149-164: 💤 Low value

Optional: Clarify test comment for clamp branch count.

The comment at line 151 states "three OP_IF branches plus the long-leg dust guard" which might be read as four total branches. Actually, the dust guard (if (longPayout > 330)) is one of the three OP_IF statements, not additional. The test assertion >= 3 is correct (the redeem function contains exactly 3 nested OP_IF statements).

Consider rewording for clarity:

-    // claimRaw is clamped into [0, totalCollateral]: claimRaw<=0 (all to long),
-    // claimRaw>=collateral (all to claim), else split. That is three OP_IF
-    // branches plus the long-leg dust guard.
+    // claimRaw is clamped into [0, totalCollateral] with three nested OP_IF branches:
+    // 1) claimRaw<=0 (all to long), 2) claimRaw>=collateral (all to claim),
+    // 3) else split with long-leg dust guard (longPayout > 330).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/inventory_hedge_test.rs` around lines 149 - 164, The comment in
test_redeem_has_clamp_branches is ambiguous about the OP_IF count; update the
comment to state clearly that the redeem function contains exactly three OP_IF
statements (one of which is the long-leg dust guard), not "three plus" a
separate guard, so the assertion if_count >= 3 remains correct; locate the
test_redeem_has_clamp_branches function and the references to HEDGE_CODE,
redeem, f, and if_count and rewrite the inline comment to clarify that the dust
guard is one of the three OP_IF branches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/hedging/inventory_hedge.ark`:
- Line 219: The multiplication newTargetFiat * 100000000 can overflow int64;
replace the direct multiply/divide used to compute claimRaw in both redeem and
withdraw with a safe split-division to avoid exceeding INT64: compute the
quotient and remainder first and then scale, e.g. claimRaw = (newTargetFiat /
oraclePrice) * 100000000 + ((newTargetFiat % oraclePrice) * 100000000) /
oraclePrice, so the large scale factor is only applied to the smaller remainder;
update the calculations for the claimRaw assignment in both occurrences to this
safe form (refer to the claimRaw variable and the redeem/withdraw blocks).

---

Nitpick comments:
In `@docs/mm-residual-hedge.md`:
- Line 91: Multiple fenced code blocks in docs/mm-residual-hedge.md are missing
language specifiers; update each triple-backtick block (``` ... ```) noted in
the review to include an appropriate language tag such as text or pseudocode
(e.g., change ``` to ```text) so the markdown linter stops flagging them and
rendering improves; apply this consistently to all flagged blocks (including the
example block shown and the other listed blocks).

In `@tests/inventory_hedge_test.rs`:
- Around line 149-164: The comment in test_redeem_has_clamp_branches is
ambiguous about the OP_IF count; update the comment to state clearly that the
redeem function contains exactly three OP_IF statements (one of which is the
long-leg dust guard), not "three plus" a separate guard, so the assertion
if_count >= 3 remains correct; locate the test_redeem_has_clamp_branches
function and the references to HEDGE_CODE, redeem, f, and if_count and rewrite
the inline comment to clarify that the dust guard is one of the three OP_IF
branches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9cd49637-6142-4b08-834e-198dc2f5dd82

📥 Commits

Reviewing files that changed from the base of the PR and between d021899 and 9ed13de.

📒 Files selected for processing (5)
  • docs/mm-residual-hedge.md
  • examples/hedging/inventory_hedge.ark
  • examples/hedging/inventory_hedge.json
  • playground/main.js
  • tests/inventory_hedge_test.rs

int rateElapsedScaled = fundingRatePerSec * elapsed / 1000000;
int delta = targetFiat * rateElapsedScaled / 1000000;
int newTargetFiat = targetFiat + delta;
int claimRaw = newTargetFiat * 100000000 / oraclePrice;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for overflow handling, max value checks, or documentation of limits
rg -nC3 --type=rust --type=md 'overflow|int64|9\.2e10|920.*million|targetFiat.*limit'

Repository: arkade-os/compiler

Length of output: 23278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant sections around the referenced lines
for f in examples/hedging/inventory_hedge.ark; do
  echo "===== $f ====="
  wc -l "$f"
  # print around line 180-260 (includes 219 and 269)
  sed -n '180,260p' "$f" | nl -ba | sed 's/^/L:/' | head -n 200
  # print around 255-310 (includes 269)
  sed -n '255,310p' "$f" | nl -ba | sed 's/^/L:/' | head -n 120
done

# Search within examples/hedging for claimRaw / newTargetFiat / oraclePrice usages and any guards
rg -n --hidden --no-ignore -S 'claimRaw|newTargetFiat|oraclePrice|redeem\(|withdraw\(' examples/hedging/inventory_hedge.ark

# Search for documented overflow/ceiling patterns in docs and examples
rg -n --hidden --no-ignore -S 'OP_MUL64 aborts|int64 overflow|fail-closed|overflow flag|FOLLOW-UP|ceiling' docs examples | head -n 200

Repository: arkade-os/compiler

Length of output: 213


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant sections around the referenced lines
for f in examples/hedging/inventory_hedge.ark; do
  echo "===== $f ====="
  wc -l "$f"
  # print around line 180-260 (includes 219 and 269)
  sed -n '180,260p' "$f" | nl -ba | sed 's/^/L:/' | head -n 200
  # print around 255-310 (includes 269)
  sed -n '255,310p' "$f" | nl -ba | sed 's/^/L:/' | head -n 120
done

# Search within examples/hedging for claimRaw / newTargetFiat / oraclePrice usages and any guards
rg -n --hidden --no-ignore -S 'claimRaw|newTargetFiat|oraclePrice|redeem\(|withdraw\(' examples/hedging/inventory_hedge.ark

# Search for documented overflow/ceiling patterns in docs and examples
rg -n --hidden --no-ignore -S 'OP_MUL64 aborts|int64 overflow|fail-closed|overflow flag|FOLLOW-UP|ceiling' docs examples | head -n 200

Repository: arkade-os/compiler

Length of output: 213


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="examples/hedging/inventory_hedge.ark"
echo "===== $f ====="
wc -l "$f"

echo "---- lines 190-245 ----"
awk 'NR>=190 && NR<=245 {printf "%d:%s\n", NR, $0}' "$f"

echo "---- lines 245-310 ----"
awk 'NR>=245 && NR<=310 {printf "%d:%s\n", NR, $0}' "$f"

echo "---- targeted usages in inventory_hedge.ark ----"
rg -n 'claimRaw|newTargetFiat|oraclePrice|redeem\(|withdraw\(' "$f"

echo "---- targeted overflow/ceiling documentation in docs/examples ----"
rg -n -S 'OP_MUL64 aborts|int64 overflow|fail-closed|overflow flag|FOLLOW-UP|ceiling|9\.2e10|920.*million|targetFiat.*limit' docs examples || true

Repository: arkade-os/compiler

Length of output: 11241


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect compiler arithmetic emission around the referenced comment
f="src/compiler/mod.rs"
echo "===== $f (around 2030-2120) ====="
sed -n '2030,2125p' "$f" | cat -n

echo "===== $f (around 2580-2690) ====="
sed -n '2580,2690p' "$f" | cat -n

echo "===== $f (around 2620-2685, arithmetic opcode selection) ====="
sed -n '2620,2685p' "$f" | cat -n

# Inspect repayment_pool overflow FOLLOW-UP notes
for rf in examples/bonds/repayment_pool.ark; do
  echo "===== $rf (FOLLOW-UP overflow around first match) ====="
  sed -n '140,190p' "$rf" | cat -n
  echo "===== $rf (FOLLOW-UP overflow around second match) ====="
  sed -n '540,590p' "$rf" | cat -n
  echo "===== $rf (FOLLOW-UP overflow around third match) ====="
  sed -n '600,660p' "$rf" | cat -n
done

Repository: arkade-os/compiler

Length of output: 22022


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where OP_MUL / OP_MUL64 are emitted and how ArkType::Int vs ArkType::Uint64Le is handled.
rg -n "OP_MUL64|OP_MUL[^0-9]" src/compiler/mod.rs
rg -n "ArkType::Int|ArkType::Uint64Le" src/compiler/mod.rs | head -n 200

# Search for code paths that emit binary ops differently for 64-bit vs scriptnum
rg -n "emit_binary_op_asm|emit_.*binary.*op|BinaryOp" src/compiler/mod.rs | head -n 200

# Specifically inspect around where BinaryOp is compiled/emitted
# (find first occurrence near "Expression::BinaryOp" in emit_expression_asm)
rg -n "Expression::BinaryOp" src/compiler/mod.rs

Repository: arkade-os/compiler

Length of output: 2408


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="src/compiler/mod.rs"
echo "===== OP_MUL64 at ~1147 ====="
sed -n '1125,1165p' "$f" | cat -n

echo "===== OP_MUL64 at ~2081 (emit_binary_op_asm) ====="
sed -n '2025,2105p' "$f" | cat -n

echo "===== BinaryOp compilation site around 1600-1705 ====="
sed -n '1580,1705p' "$f" | cat -n

echo "===== BinaryOp compilation site around 1660-1685 ====="
sed -n '1640,1695p' "$f" | cat -n

echo "===== Expression::BinaryOp earlier visitor around 70-120 (opcode planning) ====="
sed -n '70,130p' "$f" | cat -n

Repository: arkade-os/compiler

Length of output: 16689


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --no-ignore -S 'stability_vault\.ark|9\.2e10|920.*million|newTargetFiat\s*\*\s*100000000|claimRaw\s*=.*100000000' examples docs || true

Repository: arkade-os/compiler

Length of output: 12580


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="examples/hedging/inventory_hedge.ark"
echo "===== inventory_hedge.ark header (1-120) ====="
awk 'NR>=1 && NR<=130 {print NR ":" $0}' "$f"

echo "===== inventory_hedge.ark around currentClaimBase/minCollateral (130-210) ====="
awk 'NR>=130 && NR<=210 {print NR ":" $0}' "$f"

Repository: arkade-os/compiler

Length of output: 11205


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find stability_vault.ark (imported by examples)
fd -a -t f "stability_vault.ark" -d 6 . || true

# Search for the specific multiplication pattern / bound text in the repo
rg -n --hidden --no-ignore -S 'stability_vault\.ark|newTargetFiat\s*\*\s*100000000|claimRaw\s*=|92(\.|,)?0|9\.2e10|920.*million|100000000\s*/\s*oraclePrice' examples docs || true

# Search for the exact formula comment from stability_vault if present
rg -n --hidden --no-ignore -S 'claimRaw\s*=\s*newTargetFiat\s*×\s*1e8' . || true

Repository: arkade-os/compiler

Length of output: 25818


claimRaw int64 overflow ceiling in redeem/withdraw (fail-closed)
examples/hedging/inventory_hedge.ark computes claimRaw = newTargetFiat * 100000000 / oraclePrice in both redeem (line 219) and withdraw (line 269). The compiler emits OP_MUL64 followed by OP_VERIFY for *, so if newTargetFiat * 1e8 exceeds signed int64 range, the script aborts (liveness ceiling, not incorrect payouts). This happens when newTargetFiat > floor(INT64_MAX / 1e8) ≈ 9.22e10 minor units (≈ ~$922M if targetFiat is cents).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/hedging/inventory_hedge.ark` at line 219, The multiplication
newTargetFiat * 100000000 can overflow int64; replace the direct multiply/divide
used to compute claimRaw in both redeem and withdraw with a safe split-division
to avoid exceeding INT64: compute the quotient and remainder first and then
scale, e.g. claimRaw = (newTargetFiat / oraclePrice) * 100000000 +
((newTargetFiat % oraclePrice) * 100000000) / oraclePrice, so the large scale
factor is only applied to the smaller remainder; update the calculations for the
claimRaw assignment in both occurrences to this safe form (refer to the claimRaw
variable and the redeem/withdraw blocks).

- tests: assert the oracle message is RECONSTRUCTED on-chain (>=2 OP_CAT +
  OP_SHA256), matching stability_vault_test, so a compiler regression that drops
  cat/hash can't slip the replay guard; add addCapital-must-not-call-oracle
  negative test (code-review finding 5).
- tests: reword redeem clamp-branch comment — the dust guard IS the third OP_IF,
  not a fourth branch (CodeRabbit nitpick).
- docs: add 'text' language tags to fenced code blocks (markdown-lint nitpick).

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

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

🔍 Arkana Code Review — follow-up on d93a3ef (oracle hardening + negative tests)

Diff reviewed: 9ed13de..d93a3ef (the only new commit since my last approval).

Changes

  1. tests/inventory_hedge_test.rs — Three improvements:

    • test_oracle_paths_verify_price_and_divide: now asserts >=2 OP_CAT and OP_SHA256 in every oracle-gated function, pinning the on-chain message reconstruction (sha256(ticker || price || time)). This mirrors the stability_vault_test pattern and closes the gap where a compiler regression could silently drop the concat/hash steps while still passing the old sig-only check. ✅
    • New test_add_capital_does_no_oracle_call: negative test ensuring addCapital never grows an oracle dependency. Directly addresses finding #5 from my first review. ✅
    • test_redeem_has_clamp_branches comment: clarified that the dust guard IS the third OP_IF, not a fourth branch. Accurate. ✅
  2. docs/mm-residual-hedge.md — All fenced code blocks now have text language tags. Pure markdown-lint fix, no content change. ✅

Verdict

Tests-only + docs-only diff. CI green (build + format + deploy-preview all pass). No protocol-critical code changes — the contract source (inventory_hedge.ark) is untouched.

LGTM. Approving.

Per design decision (leave logic as-is, document only):
- updateFunding: note the known limitation that a negative *initial* rate bricks
  repricing (require(delta > 0) can't pass); funds aren't locked since
  redeem/withdraw still settle; a validating factory would seal it off.
- Settlement math: note the fail-closed int64 ceiling (~9.2e10 minor units /
  ~$922M) on claimRaw, inherited verbatim from stability_vault.ark — overflow
  aborts via OP_MUL64+OP_VERIFY, never a wrong payout. Split-division deferred
  (no modulo operator; avoid diverging from the audited reference).

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

tiero commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Resolution on the two deeper findings (high-effort review + the line-219/269 overflow comment). Decision: document, don't change the logic — both are inherited verbatim from the audited stability_vault.ark, and a rewrite would diverge from it. Pushed in a1aeef0.

1. claimRaw = newTargetFiat * 1e8 / oraclePrice overflow (🟡, lines 219/269). Real but fail-closed: the compiler emits OP_MUL64 + OP_VERIFY, so an overflow aborts settlement — it never yields a wrong payout. It's a liveness ceiling at ~9.2e10 minor units (≈ $922M). The suggested split-division can't be expressed here: this language has no % operator (grammar is + - * / only), and a subtraction-based remainder would itself overflow at extreme prices — trading one regime for another while diverging from the reference. Documented the ceiling inline instead.

2. updateFunding bricked under a negative initial rate. require(delta > 0) can't pass when the current rate is negative, so repricing can't recover. Reachable only via a negative initial fundingRatePerSec, which is unsupported and not constructible-against on-chain (no constructor-level guard in the language). Funds are not locked — redeem/withdraw still settle. Documented as a known limitation; the clean fix is a validating factory (à la stability_offer.ark), deferred as a fast-follow.

Test/oracle hardening + the nitpicks are in d93a3ef. Contract settlement logic is unchanged and remains byte-for-byte aligned with the StabilityVault reference.


Generated by Claude Code

…dback)

A passive provider settling at a lagging oracle mark is adversely selected:
the settlement initiator holds a free option on the oracle delay, which is why
oracle-priced AMMs died on Ethereum (picked off when oracle error > fee, ignored
when below). Stability-style lending tolerates the 0.2-0.3% enter/exit premium
because settlement is rare; a hedging book may not.

- design doc: new §9.b spelling out the problem, why §6 internalization is
  load-bearing (not an optimization), that the dropped exit fee is only sound
  desk<->own-treasury, that 'no perp-spot basis' needs qualification, and that
  the §8 order-book/RFQ layer is closer to required than optional for any
  public version.
- contract header: trust-scope note on fee-free redeem/withdraw — reinstate
  seekerExitFee for any external counterparty.

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/hedging/inventory_hedge.ark (1)

173-197: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject negative collateral ratios before applying the withdrawal bound.

removeCapital trusts collateralRatioPct from state as-is. A negative ratio weakens the floor, and <= -100 makes minCollateral non-positive, so this path can authorize draining a live claim instead of enforcing the promised collateral buffer.

Suggested fix
     require(amount > 0, "zero amount");
     require(oraclePrice > 0, "invalid oracle price");
+    require(collateralRatioPct >= 0, "invalid collateral ratio");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/hedging/inventory_hedge.ark` around lines 173 - 197, In
removeCapital, reject negative collateral ratios before computing
minCollateral/newTotalCollateral: add a precondition that collateralRatioPct >=
0 (matching updateFunding's invariant) at the start of the removal logic (before
computing currentClaimBase/minCollateral), so a negative or <= -100
collateralRatioPct cannot weaken the floor and allow draining a live claim.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/hedging/inventory_hedge.ark`:
- Around line 115-124: The updateFunding function currently bails with
require(delta > 0, "no accrual; wait longer") when fundingRatePerSec != 0,
preventing recovery from a negative fundingRatePerSec; change the guard so
negative deltas are allowed to be applied (so the function can move
fundingRatePerSec toward zero) instead of always rejecting delta <= 0—i.e., in
updateFunding remove or relax the require(delta > 0) check and ensure subsequent
logic that adjusts fundingRatePerSec and claim/redeem accounting correctly
handles delta < 0 paths using the existing delta and fundingRatePerSec variables
to reduce (not increase) the negative rate back toward >= 0.

---

Outside diff comments:
In `@examples/hedging/inventory_hedge.ark`:
- Around line 173-197: In removeCapital, reject negative collateral ratios
before computing minCollateral/newTotalCollateral: add a precondition that
collateralRatioPct >= 0 (matching updateFunding's invariant) at the start of the
removal logic (before computing currentClaimBase/minCollateral), so a negative
or <= -100 collateralRatioPct cannot weaken the floor and allow draining a live
claim.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1011f8f-85b2-4f35-a8fc-958391445a9f

📥 Commits

Reviewing files that changed from the base of the PR and between 9ed13de and a1aeef0.

📒 Files selected for processing (4)
  • docs/mm-residual-hedge.md
  • examples/hedging/inventory_hedge.ark
  • examples/hedging/inventory_hedge.json
  • tests/inventory_hedge_test.rs
✅ Files skipped from review due to trivial changes (2)
  • examples/hedging/inventory_hedge.json
  • docs/mm-residual-hedge.md

Comment on lines +115 to +124
// KNOWN LIMITATION (by design): with a NEGATIVE current rate, delta < 0, so
// `require(delta > 0)` always fails and repricing via updateFunding is
// bricked — the rate can never be moved back to >= 0. This is only reachable
// when a vault is constructed with a negative initial fundingRatePerSec,
// which is unsupported (see the constructor comment). Funds are NOT locked:
// redeem/withdraw still settle (their clamp handles a wiped claim). A
// factory that validates the initial rate would seal this off; out of scope
// for the thinnest first build.
if (fundingRatePerSec != 0) {
require(delta > 0, "no accrual; wait longer");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Allow updateFunding to recover from an invalid negative current rate.

The delta > 0 gate only protects against suppressing positive funding owed to the claim leg. With the current fundingRatePerSec != 0 check, any vault instantiated with a negative rate can never move back to a legal non-negative rate because delta is always <= 0.

Suggested fix
-    if (fundingRatePerSec != 0) {
+    if (fundingRatePerSec > 0) {
       require(delta > 0, "no accrual; wait longer");
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/hedging/inventory_hedge.ark` around lines 115 - 124, The
updateFunding function currently bails with require(delta > 0, "no accrual; wait
longer") when fundingRatePerSec != 0, preventing recovery from a negative
fundingRatePerSec; change the guard so negative deltas are allowed to be applied
(so the function can move fundingRatePerSec toward zero) instead of always
rejecting delta <= 0—i.e., in updateFunding remove or relax the require(delta >
0) check and ensure subsequent logic that adjusts fundingRatePerSec and
claim/redeem accounting correctly handles delta < 0 paths using the existing
delta and fundingRatePerSec variables to reduce (not increase) the negative rate
back toward >= 0.

CodeRabbit finding: the constructor cannot validate collateralRatioPct, and a
negative ratio (<= -100) makes minCollateral non-positive so the ratio check
trivially passes — the long leg could drain a live claim. Add
require(collateralRatioPct >= 0), the same in-function defense-in-depth pattern
as the existing newTargetFiat guard. StabilityOffer seals this at take() time;
with no factory here the in-function guard is the equivalent.

https://claude.ai/code/session_01E3fpVJfLdM3ye3ZWy5VxGg

tiero commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Response to CodeRabbit's two actionable comments (pushed in 8f498c1):

✅ Accepted — removeCapital negative collateralRatioPct (🟠 Major, outside-diff). Good catch, and a genuinely new one: with collateralRatioPct <= -100, minCollateral goes non-positive and the ratio check trivially passes — the long leg could drain a live claim. Added require(collateralRatioPct >= 0, "invalid collateral ratio") at the top of removeCapital, the same in-function defense-in-depth pattern as the existing newTargetFiat > 0 guard. (Context: stability_vault.ark has the identical formula but seals this at StabilityOffer.take() time with collateralRatioPct >= 100; InventoryHedge ships no factory, so the in-function guard is the equivalent protection.) Artifact recompiled, tests green.

❌ Declined (maintainer decision) — relax updateFunding's anti-grief guard (!= 0> 0). This exact change was proposed twice and the maintainer explicitly chose leave as-is, document as unsupported (see the KNOWN LIMITATION comment added in a1aeef0). Rationale: the suggested relaxation is not as harmless as it looks — with rate > 0 gating, a vault holding a negative rate could call updateFunding with delta == 0 accruals (truncation) and silently advance lastUpdate, the precise grief the guard exists to block, now on the claim-favoring side. Negative initial rates are unsupported, funds are never locked (redeem/withdraw settle a wiped claim correctly), and the clean seal is a validating factory (à la stability_offer.ark) deferred as a fast-follow. The trade-off is documented inline at the guard.


Generated by Claude Code

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