Skip to content

Support Token-2022 in CreateBuffer and ReclaimBuffer - #120

Draft
kaze-cow wants to merge 2 commits into
mainfrom
kaze/sc-153-token-2022-program
Draft

Support Token-2022 in CreateBuffer and ReclaimBuffer#120
kaze-cow wants to merge 2 commits into
mainfrom
kaze/sc-153-token-2022-program

Conversation

@kaze-cow

@kaze-cow kaze-cow commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Adds Token-2022 support to buffer creation and reclamation.

What changes

CreateBuffer and ReclaimBuffer each take a token_program account and, until now, rejected anything that wasn't the legacy SPL Token program. They now accept Token-2022 too and issue all of their CPIs — InitializeAccount3, CloseAccount — against whichever of the two they were handed.

Token2022 accounts may have dynamic length. Previously, a buffer could only hold 165 bytes (the length of SPL token account), but now it is now allocated at the length its mint actually needs. GetAccountDataSize is used to verify the length of the token required before creating it.

Library Handling Changes

spl-token-2022-interface = "3", a library published by anza-team, and pinocchio-token-2022 = "3", another library published by anza-team, are added for hopefully obvious reasons.

spl-token-interface was removed because it is no longer needed.

pinocchio-token is bumped to 0.7 in order to gain access to the invoke_with_unverified_program(token_program) instruction builder function (prior to this release, there was no way to specify an alternative program). Its also the version that pinnochio-token-2022 transitively depends on.

Most of the functions in both pinnochio-token and pinnochio-token-2022 are close to identical. For now most of the interfaces continue to use pinnochio-token because we never actually work with token 2022 tokens directly and the interfaces usually have slightly less dependencies (ex. not specifying the extension information).

Out of Scope

BeginSettle / FinalizeSettle require a different methodology due to the use case, so those follow in #128.

The integration tests for both this PR and #128 are in #121, as we want to expand coverage with 2022 across as many tests as possible.

At this time, only one token program can be supplied to the buffer functions. Two separate calls to CreateBuffers is required if it is necessary to create buffers for tokens on two separate prgorams.

Compute cost

bench-report.json is regenerated. The buffer instructions shift by roughly +0.2% to +0.4% from the added dispatch (reclaim_buffer/max_buffers_in_one_instruction 136,501 → 137,046 is the largest); the one- and two-unit drift on the unrelated settle and transfer-authority lines is codegen, not behaviour.

Test Plan

Verify the methodology. In particular, it would be good to verify the library dependency status as described above, because it is a bit awckward.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

SC-153

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpinocchio-token-2022@​0.4.010010093100100
Addedspl-token-2022-interface@​3.1.110010093100100
Updatedpinocchio-token@​0.6.0 ⏵ 0.7.010010093100100

View full report

`CreateBuffer` and `ReclaimBuffer` each took a `token_program` account and
rejected anything that wasn't the legacy SPL Token program. They now accept
Token-2022 as well and issue all of their CPIs against whichever of the two
they were handed, so a buffer can be allocated, initialized and closed under
either.

Token-2022 encodes the instructions this program issues exactly as the legacy
program does, so only the CPI target changes. Two things do differ:

- Account data. A Token-2022 account carrying extensions is longer than the
  base layout, so the legacy reader (exact length, legacy owner) rejects it.
  `token::read_token_account` dispatches on the validated program and reads by
  value, which also drops the borrow before `ReclaimBuffer` closes the same
  account.

- Buffer sizing. A buffer now gets the length its mint actually needs: a mint
  with no extension data keeps the base layout, and anything longer is priced
  by asking the token program via `GetAccountDataSize`, the way the
  associated-token-account program does. That keeps the answer authoritative at
  run time rather than freezing a mint-extension-to-account-extension table
  into the program.

The program account is shared by the whole instruction, so the mints one
instruction touches must all live under the same token program; splitting a
mixed batch across two instructions is the caller's job.

Adds `SettlementError::BufferSizeUnavailable` (35), reachable only defensively:
a token program that fails the size query aborts the instruction on its own.

`BeginSettle` and `FinalizeSettle` keep rejecting everything but the legacy
program; Token-2022 for the settlement pair follows separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaze-cow
kaze-cow force-pushed the kaze/sc-153-token-2022-program branch from 3ef114e to aa75ae4 Compare August 28, 2026 07:22
@kaze-cow kaze-cow changed the title Support Token-2022 in every token-moving instruction Support Token-2022 in CreateBuffer and ReclaimBuffer Aug 28, 2026
`spl-token-interface` was a dependency of the interface crate for one
thing: a 32-byte constant naming a program this code never calls
directly. `spl-token-2022-interface` already carries that address in
`inline_spl_token`, which exists precisely so a program that has to
recognize both doesn't grow a second dependency for the one it only
compares against. Both ids now come from there, and the legacy crate
leaves the interface crate's dependency graph -- and with it the
settlement program's.

`instruction::settle` re-exported the same id straight from the legacy
crate, so it moves to `token_program` too. That was the second import
keeping the dependency alive, not a cosmetic change.

The `.so` is byte-identical at 51,056 bytes: the constant was already
inlined, so this narrows the dependency graph rather than the program.

The program crate keeps `pinocchio-token`. Its state readers are not
parameterized over the token program the way 0.7's instruction builders
are -- `pinocchio_token_2022::state::Account` hardcodes an owner check
against Token-2022 in all of its safe constructors -- so reading a legacy
account without it means `from_bytes_unchecked` plus hand-rolled owner
and length checks, in exchange for a crate `pinocchio-token-2022` depends
on anyway.

`test-cli` keeps the legacy crate as well. It talks to no other program,
and the two crates' `native_mint` are different addresses, so swapping
them there is a change to make deliberately rather than in passing.

Co-Authored-By: Claude Opus 5 (1M context) <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.

1 participant