Skip to content

Clamp on-chain numUsedAccounts before the warmup account-index bound - #2496

Open
0x-SquidSol wants to merge 1 commit into
dcccrypto:playgroundfrom
0x-SquidSol:fix/warmup-account-index-clamp
Open

Clamp on-chain numUsedAccounts before the warmup account-index bound#2496
0x-SquidSol wants to merge 1 commit into
dcccrypto:playgroundfrom
0x-SquidSol:fix/warmup-account-index-clamp

Conversation

@0x-SquidSol

Copy link
Copy Markdown
Contributor

What

Clamp engine.numUsedAccounts with sanitizeAccountCount before using it as the
accountIdx bound in warmup/[slab]/[accountIdx].

Why

The guard accountIdx >= engine.numUsedAccounts trusted the raw on-chain field, and
accountIdx has no upper bound (only >= 0). A sentinel/garbage numUsedAccounts
(u64::MAX / uninitialized slab) is huge, so the guard passed and parseAccount read
an out-of-range slot. sanitizeAccountCount clamps garbage (> 4096 / negative /
sentinel) to 0, so the bound correctly rejects.

Changes

  • warmup: const numUsed = sanitizeAccountCount(Number(engine.numUsedAccounts)) as the bound.
  • regression test: sentinel/over-cap/negative → 0; legit preserved; guard rejects a
    garbage-count index that previously passed.

Testing

  • npx tsc --noEmit — clean.
  • New test + existing warmup suites — 3 files, 26 tests, all pass.

Notes

  • Frontend + devnet scope; v12-only (v17 returns 501). No program/keeper/mainnet changes.

…bound

warmup/[slab]/[accountIdx] gated `accountIdx >= engine.numUsedAccounts` with the
raw on-chain field, and accountIdx has no upper bound (only >= 0). A sentinel/
garbage numUsedAccounts (u64::MAX / uninitialized slab) is huge, so the guard
passes and parseAccount reads an out-of-range slot. Clamp the count with
sanitizeAccountCount (garbage -> 0) before the check. v12-only (v17 returns 501).

- warmup: sanitizeAccountCount(Number(engine.numUsedAccounts)) as the bound
- add a regression test (sentinel/over-cap/negative -> 0; legit preserved; guard
  rejects a garbage-count index that previously passed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@0x-SquidSol
0x-SquidSol requested a review from dcccrypto as a code owner August 5, 2026 20:02
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@0x-SquidSol is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@0x-SquidSol, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 424d7933-8a94-447f-b395-23959832bdb6

📥 Commits

Reviewing files that changed from the base of the PR and between f2a3bbe and 37404a2.

📒 Files selected for processing (2)
  • app/__tests__/api/warmup-account-index-clamp.test.ts
  • app/app/api/warmup/[slab]/[accountIdx]/route.ts

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 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Correct, and it does close the reported holeNumber(u64::MAX) is ~1.8e19,
above the 4096 default cap, so sanitizeAccountCount returns 0 and every index
is rejected. Suite: 2922 passed / 0 failed.

But it's weaker than it needs to be, and the stronger form is two lines away

sanitizeAccountCount takes an optional per-slab cap:

export function sanitizeAccountCount(count: number, maxAccounts?: number): number {
  if (count < 0) return 0;
  const cap = maxAccounts != null && maxAccounts > 0 ? maxAccounts : MAX_SLAB_ACCOUNTS; // 4096
  if (count > cap) return 0;
  return count;
}

This PR omits the second argument, so it falls back to the hardcoded 4096. The
route already parses the real value two lines above the guard:

const engine = parseEngine(data);
const riskParams = parseParams(data);      // <-- has maxAccounts, currently unused here

and the codebase already uses the stronger form elsewhere —
MarketStatsCard.tsx:244:

sanitizeAccountCount(engine.numUsedAccounts, params ? Number(params.maxAccounts) : undefined)

The gap this leaves: for a slab whose real maxAccounts is below 4096 — say 512
— a garbage numUsedAccounts anywhere in 512…4096 still passes the clamp, and
parseAccount still reads a slot beyond the slab's actual account array. That is
the same out-of-range read the PR is closing, just at a less extreme sentinel
value. u64::MAX is the easy case; a corrupted-but-plausible count is the one
worth bounding.

Suggested:

const numUsed = sanitizeAccountCount(
  Number(engine.numUsedAccounts),
  Number(riskParams.maxAccounts),
);

That also makes riskParams load-bearing rather than parsed-and-ignored on this
path.

Test doesn't bind the fix

I replaced the clamp with a bare Number(engine.numUsedAccounts) — the bug fully
restored — and warmup-account-index-clamp.test.ts stayed 3/3 green.

Binding it is cheap here: mock @percolatorct/sdk's parseEngine to return
numUsedAccounts: 2n ** 64n - 1n and parseParams to return a small
maxAccounts, drive GET, and assert 404 — plus a control with a sane count
where the same index resolves. That would also cover the maxAccounts point
above, since the sane-cap case is exactly what the current default misses.

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