Skip to content

Verify Token-2022 settlement against the whole existing suite - #121

Draft
kaze-cow wants to merge 1 commit into
kaze/sc-153-token-2022-settlefrom
kaze/sc-153-token-2022-tests
Draft

Verify Token-2022 settlement against the whole existing suite#121
kaze-cow wants to merge 1 commit into
kaze/sc-153-token-2022-settlefrom
kaze/sc-153-token-2022-tests

Conversation

@kaze-cow

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

Copy link
Copy Markdown
Contributor

Stacked on #128, which is stacked on #120 — review those first; this PR targets #128's branch, so the diff shown here is tests only.

Approach

The Token-2022 paths in #120 and #128 are worth little if they only hold for the handful of cases someone thought to write twice. So rather than a parallel Token-2022 suite, this reruns the suite that already exists against the second program.

common::also_under_token_2022!(some_test) sits in front of a test and generates some_test_token_2022, which runs the same body with Token-2022 as the thread-local active program:

common::also_under_token_2022!(settles_a_single_order);
#[test]
fn settles_a_single_order() { .. }

Nothing in the body changes. The token helpers in tests/common/token.rs build against the program that owns the account they are handed (token::program_of), and payer_signed_tx / signed_tx repoint the legacy program id in every instruction they assemble. The active program is a thread-local, which is a per-test value because the harness runs each test on its own thread.

Naming the test in front of it, rather than wrapping the body, keeps the body's indentation and makes a stale name a compile error instead of a test that quietly stopped being generated.

The settlement pair is the exception

BeginSettle and FinalizeSettle name their token programs as slots by value, not as accounts to be rewritten. Repointing the legacy slot in place would leave the pair in an order TokenPrograms::validate rejects — slot 0 must hold SPL Token or the placeholder, never Token-2022.

So common::token_programs() names the slots the active program wants, and the generated tests pass that where they used to write TokenPrograms::SPL_TOKEN. aim_at_active_token_program still handles the single-slot instructions, CreateBuffer and ReclaimBuffer.

Coverage

75 tests are generated this way, across buffer creation, buffer reclamation, order settlement, pushes, and limit prices — error paths as much as happy ones (wrong bumps, fabricated accounts, owner mismatches, duplicate orders, over-delegation, cancelled and expired orders, push/order count mismatches).

suite before after
create_buffer 18 29
reclaim_buffer 11 19
begin_settle_orders 27 53
finalize_settle_pushes 14 27
settle_limit_prices 17 34

Deliberately excluded, each saying why in place: a test pinned to the legacy native mint, which can only hold under one program; and the suites whose instructions name no token program at all (Initialize, CreateOrder, ReclaimOrder, TransferAuthority), which have nothing to vary. settle_token_programs (added in #128) is likewise left alone — it already names both programs explicitly.

Three tests are Token-2022-only, covering what has no legacy analogue. They use a mint carrying a TransferFeeConfig, which requires a TransferFeeAmount on every account holding it and so forces a token account longer than the base layout — the shape that motivates mint-dependent buffer sizing:

  • creates_buffer_sized_for_a_mint_with_extensions
  • recreating_an_extension_mint_buffer_is_idempotent
  • reclaims_a_buffer_sized_for_an_extension_mint

Unit tests for the dispatch and sizing logic itself live with the implementation in #120 and #128.

Harness notes

The legacy program's instruction builders reject any other program id, so the helpers build against the legacy program and are retargeted on the way out. Both programs encode these instructions identically, so what arrives is exactly what the other program expects.

aim_at_active_token_program only rewrites the legacy address, so an instruction a test deliberately pointed somewhere else — at an unrelated program, to be rejected — is left alone. It can go away once the instruction builders take the token program as a parameter.

common::buffer::ensure_buffer_exists names its program outright rather than leaving it to the active one, because a settlement mixing the two programs builds buffers under both within a single test.

Compute cost

bench-report.json gains readings for every generated test. Token-2022 costs more per instruction, as expected from the longer accounts and the extension-aware transfer path — e.g. settle/settles_multiple_orders 23,257 → 27,350 CU, create_buffers/happy_path_creates_initialized_buffer_token_account 10,361 → 11,695 CU. Transaction bytes and account counts are identical between the two.

Verification

just test        # 19 test binaries, 415 tests, all green
just bench       # reproduces the committed bench-report.json
just lint
just fmt-check
just doc-dev

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

SC-153

@kaze-cow
kaze-cow changed the base branch from kaze/sc-153-token-2022-program to kaze/sc-153-token-2022-settle August 28, 2026 07:22
@kaze-cow
kaze-cow force-pushed the kaze/sc-153-token-2022-tests branch from b667e36 to 9f512c4 Compare August 28, 2026 07:22
The Token-2022 paths are worth little if they only hold for the handful of
cases someone thought to write twice, so instead of a parallel suite this
reruns the suite that already exists against the second program.

`common::also_under_token_2022!(some_test)` sits in front of a test and
generates `some_test_token_2022`, which runs the same body with Token-2022 as
the thread-local active program. Nothing in the body changes: the token helpers
build against the program that owns the account they are handed, and
`payer_signed_tx` / `signed_tx` repoint the legacy program id in every
instruction they assemble. 75 tests are covered this way, across buffer
creation, reclamation, order settlement, pushes, and limit prices -- error
paths as much as happy ones.

A settlement pair is the exception to that repointing. Its token programs are
slots named by value rather than accounts to be rewritten, and rewriting the
legacy slot in place would leave the pair in an order `TokenPrograms::validate`
rejects. `common::token_programs()` names the slots the active program wants
instead, and the generated tests pass that where they used to write
`TokenPrograms::SPL_TOKEN`.

Naming the test in front of it, rather than wrapping the body, keeps the
indentation and makes a stale name a compile error instead of a test that
quietly stopped being generated. A test that can only hold under one program
(one pinned to the legacy native mint) goes without and says why, as do the
suites whose instructions name no token program at all.

Three tests are Token-2022-only, covering what has no legacy analogue -- a
buffer for a mint with a `TransferFeeConfig`, which needs a `TransferFeeAmount`
on every account holding it and so must be longer than the base layout:

- `creates_buffer_sized_for_a_mint_with_extensions`
- `recreating_an_extension_mint_buffer_is_idempotent`
- `reclaims_a_buffer_sized_for_an_extension_mint`

`bench-report.json` gains the CU, account, and transaction-byte readings for
every generated test. Token-2022 costs more per instruction, as expected from
the longer accounts and the extension-aware transfer path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kaze-cow
kaze-cow force-pushed the kaze/sc-153-token-2022-settle branch from 7af181d to 35bb3fe Compare August 28, 2026 08:29
@kaze-cow
kaze-cow force-pushed the kaze/sc-153-token-2022-tests branch from 9f512c4 to 223bd0d Compare August 28, 2026 08:30
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