Skip to content

harden(v16): derive the array-length constants from the asset count (M-1, #160) - #185

Merged
dcccrypto merged 1 commit into
dcccrypto:mainfrom
0x-SquidSol:fix/derive-bitmap-words
Sep 2, 2026
Merged

harden(v16): derive the array-length constants from the asset count (M-1, #160)#185
dcccrypto merged 1 commit into
dcccrypto:mainfrom
0x-SquidSol:fix/derive-bitmap-words

Conversation

@0x-SquidSol

@0x-SquidSol 0x-SquidSol commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Addresses M-1 (#160).

What I checked, and the result

M-1's core point is that the const_assert!s prove the mirror is internally consistent and prove nothing about correspondence to the live engine — so drift "can mask itself as compile-pass". Nobody had actually done that correspondence check, so I did it against the engine source (percolator/src/v16.rs):

result
PortfolioAccountV16Account top-level fields identical — 21 fields, same names, types, order
ProvenanceHeaderV16Account match (5 fields)
PortfolioLegV16Account match (15)
PortfolioSourceDomainV16Account match (14)
HealthCertV16Account match (11)
CloseProgressLedgerV16Account match (18)
ResolvedPayoutReceiptV16Account match (6)

The mirror is currently correct. The risk M-1 describes is real but not presently realised.

One near-miss worth recording: my first pass appeared to show PORTFOLIO_SOURCE_DOMAIN_CAP as engine=4 vs mirror=2. That was wrong twice over — the engine's 4 is #[cfg(kani)]-only, and my pattern had truncated 2 * V16_MAX_PORTFOLIO_ASSETS_N at the 2. Both sides use the identical expression. Flagging it because a careless version of that check would have produced a false CRITICAL.

The one real instance of M-1's failure mode

V16_ACTIVE_BITMAP_WORDS was the literal 1, with the formula only in its doc comment. The engine derives the same quantity as (V16_MAX_PORTFOLIO_ASSETS_N + 63) / 64.

Both are 1 today at 16 assets. If the engine ever raises the asset count above 64, its bitmap grows to two words while a literal 1 here stays put — silently shifting legs and every field after it, including the stale_state / b_stale_state / liquidation_lock flags the transfer gate reads. That is precisely M-1's "wrong lock/stale flags → bypass transfer restrictions", reachable by a single upstream constant change.

And the size assertion would not save us, which is M-1's argument exactly: whoever raised the count would update EXPECTED_PORTFOLIO_ACCOUNT_SIZE to whatever the wrong layout computed, and every assertion would pass.

PORTFOLIO_SOURCE_DOMAIN_CAP was already derived — this closes the asymmetry.

Fix

Derive it, and assert the derivation:

pub const V16_ACTIVE_BITMAP_WORDS: usize = V16_MAX_PORTFOLIO_ASSETS_N.div_ceil(64);

const _: () = assert!(V16_ACTIVE_BITMAP_WORDS == V16_MAX_PORTFOLIO_ASSETS_N.div_ceil(64));
const _: () = assert!(PORTFOLIO_SOURCE_DOMAIN_CAP == 2 * V16_MAX_PORTFOLIO_ASSETS_N);
// the property the formula exists to guarantee, asserted independently of it
const _: () = assert!(V16_ACTIVE_BITMAP_WORDS * 64 >= V16_MAX_PORTFOLIO_ASSETS_N);

Const assertions rather than #[test]s so every build checks them — including cargo build-sbf, and any environment where the dev-dependency graph cannot be built. That last point is not hypothetical: cargo test cannot run on my machine at all (openssl-sys finds no OpenSSL on Windows), while cargo build --lib checks these fine.

div_ceil rather than the engine's (N + 63) / 64 because clippy rejects the manual form under -D warnings; the comment records that the engine writes it the other way, so the two can still be diffed by eye.

Verification

  • cargo build --lib clean; cargo clippy --lib -- -D warnings clean.
  • The assertion bites: hardcoding the bitmap back to a literal 2 fails the build on exactly this new assertion, rather than passing silently.
  • I could not run cargo test (OpenSSL, above), which is why these are const assertions; the compile-time checks they replace are strictly stronger than the test would have been.

On the rest of M-1

The false-claim half is already fixed — cf56ba5 ("M-1 docs") removed the LiteSVM assertions from cpi_v16.rs and slab_types_v16.rs, and README.md:128-130 now states the honest position, naming the deferred runtime validation and pointing at #110H. So option 1 is done; this PR narrows the residual risk that option 2 was meant to cover, without pretending to replace it.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Corrected active bitmap sizing so it automatically covers the configured portfolio asset capacity.
    • Added compile-time validation to ensure bitmap coverage and capacity calculations remain consistent.

…M-1, dcccrypto#160)

M-1 warns that the vendored portfolio mirror is only checked against itself, so
engine drift can mask itself as a compile pass. I compared the mirror to the
live engine field-for-field and it is currently CORRECT -- but the comparison
surfaced one concrete instance of exactly that vector.

V16_ACTIVE_BITMAP_WORDS was the literal `1`, with the formula only in its doc
comment, while the engine derives the same quantity as
`(V16_MAX_PORTFOLIO_ASSETS_N + 63) / 64`. Both are 1 today at 16 assets. If the
engine ever raises the asset count above 64, its bitmap grows to two words while
a literal `1` here stays put -- silently shifting `legs` and every field after
it, including the stale_state / b_stale_state / liquidation_lock flags the
transfer gate reads. That is M-1's stated failure mode, reachable by a single
upstream constant change.

The size assertions would not catch it, which is the point of the issue:
they compare the mirror against itself, so whoever raised the count would
update EXPECTED_PORTFOLIO_ACCOUNT_SIZE to whatever the wrong layout computed
and every assertion would pass.

PORTFOLIO_SOURCE_DOMAIN_CAP was already derived (`2 * V16_MAX_PORTFOLIO_ASSETS_N`);
this closes the asymmetry.

Added as const assertions rather than #[test]s so every build checks them --
including cargo build-sbf, and any environment where the dev-dependency graph
cannot be built. Verified they bite: hardcoding the bitmap back to a literal
fails the build on the new assertion.

`div_ceil` rather than the engine's `(N + 63) / 64` because clippy rejects the
manual form under -D warnings; the comment records that the engine writes it
the other way so the two can still be diffed by eye.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 007d0a32-fa7d-44ae-b6d7-2821877dbefe

📥 Commits

Reviewing files that changed from the base of the PR and between 985f523 and 41c2579.

📒 Files selected for processing (1)
  • src/slab_types_v16.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Changes

V16 bitmap capacity

Layer / File(s) Summary
Bitmap capacity formulas and assertions
src/slab_types_v16.rs
V16_ACTIVE_BITMAP_WORDS now derives its value from V16_MAX_PORTFOLIO_ASSETS_N.div_ceil(64). Compile-time checks validate bitmap sizing, source-domain capacity, and coverage of all portfolio leg slots.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 41c25

The change preserves the current account layout and transfer-control behavior while deriving the bitmap length from the asset capacity and adding compile-time safeguards against future layout drift. No actionable merge-blocking risk remains.

Suggested reviewers: dcccrypto

🚥 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 identifies the v16 hardening change and the derivation of array-length constants from the asset count. This matches the main changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 1 files.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 Clippy (1.97.1)

Clippy execution timed out


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.

@dcccrypto
dcccrypto merged commit 022803a into dcccrypto:main Sep 2, 2026
1 of 2 checks passed
dcccrypto added a commit that referenced this pull request Sep 2, 2026
…ill exists (#187)

README deferred runtime layout validation to "#110H". #110 is CLOSED — it was a
batch of LOW/INFO findings — so the deferral was documented as tracked somewhere
that no longer tracks it. Now points at #160, which is open and is the issue that
raised this.

Small, but this is the failure mode where a known gap stops being known: the doc
says "deferred, see X", a reader checks X, finds it closed, and concludes the
work was done.

Also states plainly what the const_assert!s cannot do, because the reason is
sharper than "they only check internal consistency". Under the exact change they
exist to guard against — raising the asset count — whoever made it would update
EXPECTED_PORTFOLIO_ACCOUNT_SIZE to whatever the wrong struct computed, and the
assertion would pass. It is not weak evidence; in that scenario it is guaranteed
to pass.

#185 (merged today) removed one concrete instance of that vector by deriving
V16_ACTIVE_BITMAP_WORDS from the asset count. The general case still needs a real
portfolio account decoded at runtime, which is what #160 stays open for.

The three false LiteSVM/compile-time claims #160 reported are already corrected in
both README.md and src/slab_types_v16.rs — verified, no LiteSVM reference remains
anywhere in the repo.

nft suite: 11 + 6 + 5 + 5 + 4 passing, 0 failed.

Refs: #160


Claude-Session: https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D

Co-authored-by: dcccrypto <dcccrypto@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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