Wave 11a-i: engine B-tracking schema (KL-FORK-ENGINE-B-TRACKING-1 PARTIALLY REVOKED) - #97
Conversation
…NG-1 PARTIALLY REVOKED) Schema-only foundation for the B-index bankruptcy-residual subsystem (spec §1.2 / §2.1 / §2.2, v12.20.6; toly engine src/percolator.rs:147 constants + 550-554 Account fields + 789-803 RiskEngine fields + 2859-2926 get/set accessors). This is Wave 11a-i. Wave 11a-ii lands the writers (plan/book_bankruptcy_residual_chunk_to_side, b_target_for_account, account_has_unsettled_b, record_uninsured_protocol_loss, trigger_bankruptcy_hmax_lock, add_explicit_unallocated_loss_side) and the bankrupt-close state-machine setters that depend on them (deferred from Wave 5b-ii). ## Schema additions Constants: - `SOCIAL_LOSS_DEN: u128 = 10^21` — implicit denominator for B-index loss weights and remainder/dust accumulators - `PUBLIC_B_CHUNK_ATOMS: u128 = MAX_VAULT_TVL` — per-call budget for `book_bankruptcy_residual_chunk_to_side` (`SOCIAL_LOSS_DEN × PUBLIC_B_CHUNK_ATOMS = 10^37` fits in u128's 3.4×10^38 range) Account (+4 fields): - `loss_weight: u128` — per-holder share over `SOCIAL_LOSS_DEN` - `b_snap: u128` — side B-target snapshot at last loss-weight write - `b_rem: u128` — sub-`SOCIAL_LOSS_DEN` carry for next settlement - `b_epoch_snap: u64` — side epoch at last touch RiskEngine (+17 fields): - `b_long_num`, `b_short_num`: u128 — running side B-targets - `b_epoch_start_long/short_num`: u128 — start-of-epoch snapshots - `loss_weight_sum_long/short`: u128 — sum-over-side of per-holder weight - `social_loss_remainder_long/short_num`: u128 - `social_loss_dust_long/short_num`: u128 - `explicit_unallocated_loss_long/short`: U128 - `explicit_unallocated_protocol_loss`: U128 - `explicit_unallocated_loss_saturated`: u8 Get/set accessors (10 helpers): - `get/set_b_side`, `get/set_b_epoch_start`, `get/set_loss_weight_sum`, `get/set_social_remainder`, `get/set_social_dust` All accessors marked `#[allow(dead_code)]` since no writer exists on this branch — Wave 11a-ii grounds them. ## New invariant `validate_b_tracking_shape` defense-in-depth checks: - `loss_weight_sum_<side> <= SOCIAL_LOSS_DEN` - `social_loss_remainder_<side>_num < SOCIAL_LOSS_DEN` - `social_loss_dust_<side>_num < SOCIAL_LOSS_DEN` - `explicit_unallocated_loss_saturated <= 1` Wired into `assert_public_postconditions`. Trivially holds while no writer exists (all fields stay at 0); becomes a meaningful gate when Wave 11a-ii starts mutating the fields. ## Kani - `proof_b_tracking_shape_holds_at_genesis` — pins init-time state - `proof_b_tracking_loss_weight_sum_overflow_rejects` — catches out-of-range writes on either side Engine Kani proof count: 313 → 315. ## KEEP_LIST - KL-FORK-ENGINE-B-TRACKING-1 marked PARTIALLY REVOKED Wave 11a. Remaining helpers (`b_target_for_account`, `account_has_unsettled_b`, `plan/book_bankruptcy_residual_chunk_to_side`) listed as deferred to Wave 11a-ii. ## Verify scripts - `verify-engine-deferred-absent.sh`: schema-field EXPECT_ABSENT entries (b_long_num, b_short_num, loss_weight_sum_long, social_loss_remainder_long_num) removed (now legitimately present); helper-name EXPECT_ABSENT entries strengthened (`fn b_target_for_account`, `fn account_has_unsettled_b`, `fn plan_bankruptcy_residual_chunk_to_side`, `fn book_bankruptcy_residual_chunk_to_side`) — still absent until 11a-ii. ## Schema growth RiskEngine: +280 bytes (14 × u128 + 3 × U128 + 1 × u8 + alignment). Account: +56 bytes (3 × u128 + 1 × u64 + alignment). NFT vendored-bytes mirror update follows in Wave 11b. Wrapper SLAB_LEN + offsets follow in Wave 11c. ## Verification | Gate | Result | |---|---| | `cargo build --release --features test` | clean | | `cargo test --release --features test` | 335 / 0 / 0 | | Engine Kani proof count | 315 (+2 from 313 baseline) | | `verify-engine-preservation.sh` | silent | | `verify-engine-deferred-absent.sh` | silent | ## Follow-ups (Wave 11 continuation) - Wave 11a-ii (engine): helpers + bankrupt-close state-machine setters + `account_has_unsettled_live_effects` 5-predicate form + `tests/proofs_b_index.rs` port - Wave 11b (NFT): vendored-bytes mirror grows by ~280 bytes on RiskEngine + Account stride grows; update EXPECTED_RISK_ENGINE_SIZE + all downstream `ENGINE_REL_*` / `SLAB_OFF_*` constants - Wave 11c (wrapper): SLAB_LEN + test-helper offsets bump (Wave 7b pattern), plus any new `RiskError` translations Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR extends the persisted risk-engine schema with Wave 11a bankruptcy-residual (B-tracking) accounting by adding constants, struct fields to ChangesWave 11a B-Tracking Subsystem
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tests/proofs_invariants.rs (1)
1167-1178: ⚡ Quick winBroaden the genesis proof to pin all newly added B-tracking fields.
This harness currently asserts zero-init for a subset of the Wave 11a schema. Since
assert_public_postconditions()is mostly shape/bounds-oriented, add explicitassert_eq!(..., 0)checks for the remaining newly-added B-tracking fields (notably the epoch-start and explicit-unallocated-loss fields) so zero-init regressions are caught directly.🤖 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/proofs_invariants.rs` around lines 1167 - 1178, Add explicit zero-init assertions for the newly added B-tracking fields that aren't covered by assert_public_postconditions(): add assert_eq!(engine.social_loss_epoch_start_long_num, 0); assert_eq!(engine.social_loss_epoch_start_short_num, 0); assert_eq!(engine.explicit_unallocated_loss_long_num, 0); and assert_eq!(engine.explicit_unallocated_loss_short_num, 0) (in the same block after the existing asserts like engine.b_long_num and engine.explicit_unallocated_loss_saturated) so epoch-start and explicit-unallocated-loss regressions are caught directly.src/percolator.rs (1)
3157-3191: ⚡ Quick win
validate_b_tracking_shapeshould be complemented with per-account B checks (debug scan path).The new fast validator only covers engine aggregates. Per-account corruption (
loss_weight,b_rem, and free-slot nonzero B fields) is still invisible invalidate_public_account_postconditions, which is already doing the linear scan in test/audit builds.💡 Suggested patch
diff --git a/src/percolator.rs b/src/percolator.rs @@ fn validate_public_account_postconditions(&self) -> Result<()> { if !used { if account.kind != Account::KIND_USER || !account.capital.is_zero() @@ || account.pending_horizon != 0 || account.pending_created_slot != 0 + || account.loss_weight != 0 + || account.b_snap != 0 + || account.b_rem != 0 + || account.b_epoch_snap != 0 || !Self::is_zero_bytes_32(&account.matcher_program) || !Self::is_zero_bytes_32(&account.matcher_context) || !Self::is_zero_bytes_32(&account.owner) { return Err(RiskError::CorruptState); } continue; } @@ self.validate_fee_credits_shape(idx)?; self.validate_reserve_shape(idx)?; + if account.loss_weight > SOCIAL_LOSS_DEN || account.b_rem >= SOCIAL_LOSS_DEN { + return Err(RiskError::CorruptState); + }Also applies to: 4710-4714
🤖 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 `@src/percolator.rs` around lines 3157 - 3191, The validate_b_tracking_shape currently only checks engine-level aggregates; add a debug-only per-account scan (used in test/audit builds) that iterates accounts and verifies each account's loss_weight, b_rem, and B-related free-slot fields are consistent (e.g., per-account loss_weight within expected range, b_rem < SOCIAL_LOSS_DEN, and any “free slot” B fields are zero when they must be); implement this as either an extension to validate_b_tracking_shape under #[cfg(test)]/audit flag or by calling the existing validate_public_account_postconditions logic from validate_b_tracking_shape for debug builds, and ensure you reference and validate the concrete fields named loss_weight, b_rem, social-loss-related B fields and any explicit free-slot/B placeholders so per-account corruption is detected during the debug scan.
🤖 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 `@src/percolator.rs`:
- Around line 467-483: The new Account fields loss_weight, b_snap, b_rem and
b_epoch_snap must be explicitly zeroed wherever an account is
canonicalized/reset in-place; update each account-reset/canonicalization branch
that currently clears other per-account fields to also set loss_weight = 0,
b_snap = 0, b_rem = 0 and b_epoch_snap = 0 so the schema-only/all-zero invariant
holds (i.e., add these zero assignments alongside the existing resets in the
in-place account reset paths that touch other per-account state).
---
Nitpick comments:
In `@src/percolator.rs`:
- Around line 3157-3191: The validate_b_tracking_shape currently only checks
engine-level aggregates; add a debug-only per-account scan (used in test/audit
builds) that iterates accounts and verifies each account's loss_weight, b_rem,
and B-related free-slot fields are consistent (e.g., per-account loss_weight
within expected range, b_rem < SOCIAL_LOSS_DEN, and any “free slot” B fields are
zero when they must be); implement this as either an extension to
validate_b_tracking_shape under #[cfg(test)]/audit flag or by calling the
existing validate_public_account_postconditions logic from
validate_b_tracking_shape for debug builds, and ensure you reference and
validate the concrete fields named loss_weight, b_rem, social-loss-related B
fields and any explicit free-slot/B placeholders so per-account corruption is
detected during the debug scan.
In `@tests/proofs_invariants.rs`:
- Around line 1167-1178: Add explicit zero-init assertions for the newly added
B-tracking fields that aren't covered by assert_public_postconditions(): add
assert_eq!(engine.social_loss_epoch_start_long_num, 0);
assert_eq!(engine.social_loss_epoch_start_short_num, 0);
assert_eq!(engine.explicit_unallocated_loss_long_num, 0); and
assert_eq!(engine.explicit_unallocated_loss_short_num, 0) (in the same block
after the existing asserts like engine.b_long_num and
engine.explicit_unallocated_loss_saturated) so epoch-start and
explicit-unallocated-loss regressions are caught directly.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0cf570e7-f284-4f2e-8352-187cd0af5edf
📒 Files selected for processing (2)
src/percolator.rstests/proofs_invariants.rs
| /// Wave 11a / KL-FORK-ENGINE-B-TRACKING-1 (PARTIALLY REVOKED, schema-only). | ||
| /// | ||
| /// Per-account B-index loss-weight + snapshot state (spec §2.1, | ||
| /// v12.20.6; toly engine src/percolator.rs:550-554). Each holder of a | ||
| /// non-zero `position_basis_q` accumulates a `loss_weight` (numerator | ||
| /// over `SOCIAL_LOSS_DEN`); `b_snap` is the side's B-target at last | ||
| /// loss-weight write; `b_rem` is the sub-`SOCIAL_LOSS_DEN` carry held | ||
| /// for the next settlement; `b_epoch_snap` is the side epoch at last | ||
| /// touch (used by `account_has_unsettled_b`). | ||
| /// | ||
| /// Path A2: all four fields stay at zero on this branch (no writer is | ||
| /// wired). Future waves will set them during liquidation / trade / | ||
| /// fee-sync hot paths. | ||
| pub loss_weight: u128, | ||
| pub b_snap: u128, | ||
| pub b_rem: u128, | ||
| pub b_epoch_snap: u64, |
There was a problem hiding this comment.
Reset new Account B-fields in every account-canonicalization path.
The new per-account B fields are added on Line [467], but they are not reset in the in-place account reset paths (e.g., Line [1885], Line [1996], Line [2148]). That can leak stale B snapshots across slot reuse/re-init and violates the “schema-only/all-zero until writers” contract.
💡 Suggested patch
diff --git a/src/percolator.rs b/src/percolator.rs
@@ fn init_in_place(
a.f_snap = 0;
a.adl_epoch_snap = 0;
+ a.loss_weight = 0;
+ a.b_snap = 0;
+ a.b_rem = 0;
+ a.b_epoch_snap = 0;
a.matcher_program = [0; 32];
a.matcher_context = [0; 32];
a.owner = [0; 32];
@@ fn free_slot(&mut self, idx: u16) -> Result<()> {
a.f_snap = 0;
a.adl_epoch_snap = 0;
+ a.loss_weight = 0;
+ a.b_snap = 0;
+ a.b_rem = 0;
+ a.b_epoch_snap = 0;
a.matcher_program = [0; 32];
a.matcher_context = [0; 32];
a.owner = [0; 32];
@@ fn materialize_at(&mut self, idx: u16, slot_anchor: u64) -> Result<()> {
a.f_snap = 0i128;
a.adl_epoch_snap = 0;
+ a.loss_weight = 0u128;
+ a.b_snap = 0u128;
+ a.b_rem = 0u128;
+ a.b_epoch_snap = 0;
a.matcher_program = [0; 32];
a.matcher_context = [0; 32];
a.owner = [0; 32];Also applies to: 1818-1832
🤖 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 `@src/percolator.rs` around lines 467 - 483, The new Account fields
loss_weight, b_snap, b_rem and b_epoch_snap must be explicitly zeroed wherever
an account is canonicalized/reset in-place; update each
account-reset/canonicalization branch that currently clears other per-account
fields to also set loss_weight = 0, b_snap = 0, b_rem = 0 and b_epoch_snap = 0
so the schema-only/all-zero invariant holds (i.e., add these zero assignments
alongside the existing resets in the in-place account reset paths that touch
other per-account state).
Summary
Schema-only foundation for the B-index bankruptcy-residual subsystem (spec §1.2 / §2.1 / §2.2, v12.20.6). Path A2: adds fields + accessors + defense-in-depth invariant, defers all writers to Wave 11a-ii.
Schema additions
SOCIAL_LOSS_DEN = 10^21,PUBLIC_B_CHUNK_ATOMS = MAX_VAULT_TVLloss_weight,b_snap,b_rem(all u128),b_epoch_snap(u64)validate_b_tracking_shapeinvariant + assert_public_postconditions integrationSchema growth: +280 bytes RiskEngine + 56 bytes Account.
Migration
NFT vendored bytes + wrapper SLAB_LEN follow in Wave 11b and 11c. Path B per FULL_SYNC_PLAN.md (force-resolve + rebuild J51cB2 during deployment prep, no on-chain migration instruction).
Verification
Test plan
Follow-ups (Wave 11 continuation)
plan/book_bankruptcy_residual_chunk_to_side,b_target_for_account,account_has_unsettled_b,trigger_bankruptcy_hmax_lock,record_uninsured_protocol_loss,add_explicit_unallocated_loss_side) + bankrupt-close state-machine setters +account_has_unsettled_live_effects5-predicate form +tests/proofs_b_index.rsport🤖 Generated with Claude Code
Summary by CodeRabbit
Chores
Tests