Skip to content

fix(slab): decode PortfolioMatcherConfigV16 trailer in parsePortfolioV17 - #347

Open
Morenikeoa wants to merge 1 commit into
dcccrypto:mainfrom
Morenikeoa:fix/portfoliov17-matcher-config-trailer
Open

fix(slab): decode PortfolioMatcherConfigV16 trailer in parsePortfolioV17#347
Morenikeoa wants to merge 1 commit into
dcccrypto:mainfrom
Morenikeoa:fix/portfoliov17-matcher-config-trailer

Conversation

@Morenikeoa

Copy link
Copy Markdown

Problem

parsePortfolioV17 is the SDK's current, correct decoder for v17 standalone portfolio accounts — but it never decodes matcherProgram/matcherContext/matcherDelegate/enabled, even though every v17 portfolio account contains this data. Per v16_program.rs, PortfolioMatcherConfigV16 (104 bytes: matcher_program[32] + matcher_context[32] + matcher_delegate[32] + enabled:u64) sits in a trailing region immediately after PortfolioAccountV16Account, at PORTFOLIO_MATCHER_CONFIG_OFF = HEADER_LEN + PORTFOLIO_STATE_LEN.

The only function in slab.ts that does decode matcherProgram/matcherContext is parseAccount — the legacy pre-v16 shared-slab decoder, a completely different, no-longer-live account model (fixed-size accounts packed into one slab, vs. v17's one-account-per-portfolio). Anyone wanting a v17 portfolio's matcher config — which TradeCpi/BatchTradeCpi/SetMatcherConfig all depend on — had no SDK function to call and would have to hand-roll the byte offset themselves. That's exactly the kind of duplication that produced the encodeMatcherInitPassive wire-layout bug fixed in #336.

Fix

Adds matcherProgram/matcherContext/matcherDelegate/matcherEnabled to the PortfolioV17 interface and decodes them in parsePortfolioV17 at V17_PORTFOLIO_ACCOUNT_LEN - 104.

This offset is anchored from the end of the account rather than chained through the intervening HealthCert/lock/CloseProgress/ResolvedPayoutReceipt regions — none of which parsePortfolioV17 decodes either, and whose sizes are already inconsistently commented in this file (CloseProgressLedgerV16Account is labeled 188 bytes in one comment, but its own field breakdown two lines below sums to 184). Anchoring from the end avoids depending on that chain at all.

The same V17_PORTFOLIO_ACCOUNT_LEN = 9347 value (and the 184-byte correction) is independently confirmed by percolator-keeper's crank.ts, which derives it from the same struct for its own getProgramAccounts dataSize filter and explicitly notes "CloseProgressLedgerV16Account is 184 bytes, not 188."

Uses the same length-gated graceful-default pattern already established for every other field added to this function after the original v17 shape (data.length >= offset + size ? read(...) : default), so existing short-buffer callers are unaffected.

Proof of Fix

  • New test constructs a full-length (V17_PORTFOLIO_ACCOUNT_LEN) buffer with sentinel pubkeys/enabled flag written at the matcher-config offset and asserts parsePortfolioV17 decodes them correctly.
  • New test confirms short buffers (shorter than the matcher-config region) still default gracefully (PublicKey.default/false) rather than throw, matching existing behavior for other late-added fields.
  • Full suite passes: npm test → 851 passed, 31 skipped (pre-existing, RPC-dependent). npm run lint clean.

Related

Builds on the matcher-ABI audit in #336; no overlap — that PR fixes encoder-side wire bugs in instructions.ts, this one fixes a decoder coverage gap in slab.ts.

🤖 Generated with Claude Code

…olioV17

parsePortfolioV17 is the SDK's current, correct decoder for v17 standalone
portfolio accounts, but it never read matcherProgram/matcherContext/
matcherDelegate/enabled even though every v17 portfolio account has this
data: PortfolioMatcherConfigV16 (104 bytes: matcher_program[32] +
matcher_context[32] + matcher_delegate[32] + enabled:u64) sits in a
trailing region immediately after PortfolioAccountV16Account, per
PORTFOLIO_MATCHER_CONFIG_OFF = HEADER_LEN + PORTFOLIO_STATE_LEN in
v16_program.rs.

The only function in this file that *does* decode matcherProgram/
matcherContext is parseAccount, the legacy pre-v16 shared-slab decoder —
a completely different, no-longer-live account model. Anyone wanting a
v17 portfolio's matcher config (which TradeCpi/BatchTradeCpi/
SetMatcherConfig all depend on) had no SDK function to call and would
have to hand-roll the byte offset themselves, exactly the kind of
duplication that produced the encodeMatcherInitPassive layout bug fixed
in dcccrypto#336.

Adds matcherProgram/matcherContext/matcherDelegate/matcherEnabled to
PortfolioV17 and decodes them from V17_PORTFOLIO_ACCOUNT_LEN - 104,
anchored from the end of the account rather than chaining through the
intervening HealthCert/lock/CloseProgress/ResolvedPayoutReceipt regions
(none of which parsePortfolioV17 decodes either, and whose sizes are
already inconsistently commented in this file — 188 bytes labeled vs.
184 from the field breakdown directly below it). The same offset was
independently confirmed via percolator-keeper's crank.ts, which derives
and documents V17_PORTFOLIO_ACCOUNT_LEN = 9347 from the same struct for
its own getProgramAccounts dataSize filter.

Uses the same length-gated graceful-default pattern already established
for every other field added to this function after the original v17
shape, so existing short-buffer callers are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

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

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

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

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

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b04ec316-1fce-4a41-94a0-c7e6dbd30fc2

📥 Commits

Reviewing files that changed from the base of the PR and between 673bc47 and d34b3c1.

⛔ Files ignored due to path filters (3)
  • dist/index.js is excluded by !**/dist/**
  • dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • dist/solana/slab.d.ts is excluded by !**/dist/**
📒 Files selected for processing (2)
  • src/solana/slab.ts
  • test/slab.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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.

1 participant