Skip to content

add pinocchio token-2022 mint-close-authority example#624

Open
MarkFeder wants to merge 1 commit into
solana-foundation:mainfrom
MarkFeder:tokens-token-2022-mint-close-authority-pinocchio
Open

add pinocchio token-2022 mint-close-authority example#624
MarkFeder wants to merge 1 commit into
solana-foundation:mainfrom
MarkFeder:tokens-token-2022-mint-close-authority-pinocchio

Conversation

@MarkFeder

Copy link
Copy Markdown
Contributor

Adds a Pinocchio port of the tokens/token-2022/mint-close-authority example, alongside the existing anchor and native versions.

What it does

A single instruction creates an SPL Token-2022 mint carrying the MintCloseAuthority extension, so a designated authority can later close the mint and reclaim its rent.

Notes

Token-2022 has no Pinocchio wrapper crate (pinocchio-token targets the legacy SPL Token program), so the Token-2022 instructions are built by hand and CPI'd:

  1. CreateAccount for the mint, sized to 202 bytes (base Account length 165 + account-type byte + one MintCloseAuthority TLV entry) and owned by the Token-2022 program.
  2. InitializeMintCloseAuthority (variant 25) — must run before the mint is initialized.
  3. InitializeMint (variant 0).

The bankrun test asserts the resulting mint is owned by Token-2022, is exactly 202 bytes, has the correct decimals, and that the extension header (account type Mint, TLV type MintCloseAuthority) and stored close authority were written correctly.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a Pinocchio port of the tokens/token-2022/mint-close-authority example, hand-building and CPI-ing three Token-2022 instructions (CreateAccount, InitializeMintCloseAuthority, InitializeMint) since no pinocchio-token-2022 crate exists. The MINT_SIZE calculation (202 bytes), COption instruction encoding (1-byte tag), and account ordering are all correct and match the Token-2022 program's expected wire format.

  • The Rust program correctly sequences the three CPIs with the right account/data layouts, and the MINT_SIZE = 202 derivation (165-byte padded base + 1 account-type byte + 4-byte TLV header + 32-byte OptionalNonZeroPubkey) is accurate.
  • The bankrun test verifies owner, size, decimals, account-type byte, and TLV extension type and value, but uses payer for all three authority roles and omits a check of the TLV length field at bytes 168–169.

Confidence Score: 4/5

Safe to merge; the hand-rolled CPI logic is correct and the bankrun test exercises the full happy path end-to-end.

The instruction encoding, account layout, and MINT_SIZE arithmetic are all correct. The test is functional but uses a single key for all three roles, which leaves a class of account-swap bugs untestable, and the TLV length field is never explicitly asserted.

tokens/token-2022/mint-close-authority/pinocchio/tests/test.ts — the test coverage is narrow; using distinct keypairs for mint authority and close authority would catch account-ordering mistakes.

Important Files Changed

Filename Overview
tokens/token-2022/mint-close-authority/pinocchio/program/src/instructions/create_mint.rs Core instruction logic; correct three-step CPI sequence with accurate 1-byte COption encoding. Token-2022 program ID is not validated at runtime.
tokens/token-2022/mint-close-authority/pinocchio/program/src/instructions/mod.rs Defines TOKEN_2022_PROGRAM_ID, MINT_SIZE (202 bytes = 165 + 1 + 2 + 2 + 32), and CreateTokenArgs; layout math is correct and well-documented.
tokens/token-2022/mint-close-authority/pinocchio/program/src/lib.rs Boilerplate entrypoint; registers process_instruction and the no_std panic handler correctly.
tokens/token-2022/mint-close-authority/pinocchio/program/src/processor.rs Thin dispatch layer; no discriminator needed because there is only one instruction. Routes directly to create_mint.
tokens/token-2022/mint-close-authority/pinocchio/tests/test.ts Bankrun integration test; verifies owner, account size, decimals, account-type byte, TLV type, and close authority pubkey. Usefulness is limited by using payer for all three authority roles, and the TLV length field is never asserted.
tokens/token-2022/mint-close-authority/pinocchio/program/Cargo.toml Declares pinocchio, pinocchio-log, and pinocchio-system dependencies; no pinocchio-token2022 crate available (expected, per PR description).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as Client (test.ts)
    participant Program as Pinocchio Program
    participant System as System Program
    participant T22 as Token-2022 Program

    Client->>Program: "CreateMint(decimals=9)"
    Program->>System: "CreateAccount space=202 owner=Token-2022"
    System-->>Program: mint account funded
    Program->>T22: InitializeMintCloseAuthority variant 25
    T22-->>Program: MintCloseAuthority TLV written
    Program->>T22: InitializeMint variant 0
    T22-->>Program: base mint fields initialized
    Program-->>Client: Ok(())
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as Client (test.ts)
    participant Program as Pinocchio Program
    participant System as System Program
    participant T22 as Token-2022 Program

    Client->>Program: "CreateMint(decimals=9)"
    Program->>System: "CreateAccount space=202 owner=Token-2022"
    System-->>Program: mint account funded
    Program->>T22: InitializeMintCloseAuthority variant 25
    T22-->>Program: MintCloseAuthority TLV written
    Program->>T22: InitializeMint variant 0
    T22-->>Program: base mint fields initialized
    Program-->>Client: Ok(())
Loading

Reviews (1): Last reviewed commit: "add pinocchio token-2022 mint-close-auth..." | Re-trigger Greptile

Comment on lines +43 to +90
it("Creates a Token-2022 mint with a close authority", async () => {
const decimals = 9;
const mintKeypair = Keypair.generate();

const data = Buffer.from(borsh.serialize(CreateTokenArgsSchema, { token_decimals: decimals }));

const ix = new TransactionInstruction({
programId: PROGRAM_ID,
keys: [
{ pubkey: mintKeypair.publicKey, isSigner: true, isWritable: true }, // mint account
{ pubkey: payer.publicKey, isSigner: false, isWritable: false }, // mint authority
{ pubkey: payer.publicKey, isSigner: false, isWritable: false }, // close authority
{ pubkey: payer.publicKey, isSigner: true, isWritable: true }, // payer
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, // rent sysvar
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // system program
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false }, // Token-2022 program
],
data,
});

const tx = new Transaction();
tx.feePayer = payer.publicKey;
tx.recentBlockhash = context.lastBlockhash;
tx.add(ix);
tx.sign(payer, mintKeypair);
await client.processTransaction(tx);

const mintAccount = await client.getAccount(mintKeypair.publicKey);
if (mintAccount === null) throw new Error("Mint account not found");
const mintData = Buffer.from(mintAccount.data);

// Owned by Token-2022, and sized for exactly one extension.
assert.deepEqual(mintAccount.owner.toBytes(), TOKEN_2022_PROGRAM_ID.toBytes());
assert.equal(mintData.length, EXTENDED_MINT_SIZE);

// Base mint fields were initialized.
assert.equal(mintData[DECIMALS_OFFSET], decimals);

// The extension header marks this as a Mint carrying MintCloseAuthority.
assert.equal(mintData[ACCOUNT_TYPE_OFFSET], ACCOUNT_TYPE_MINT);
assert.equal(mintData.readUInt16LE(TLV_TYPE_OFFSET), MINT_CLOSE_AUTHORITY_EXTENSION);

// The configured close authority was stored in the extension.
const storedCloseAuthority = mintData.subarray(TLV_VALUE_OFFSET, TLV_VALUE_OFFSET + 32);
assert.deepEqual(new Uint8Array(storedCloseAuthority), payer.publicKey.toBytes());

console.log("Mint address:", mintKeypair.publicKey.toBase58());
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Single test case conflates three distinct roles

The test uses payer.publicKey for the mint authority, close authority, and payer simultaneously. This means the test cannot verify that the close authority stored in the TLV is correctly sourced from account index 2 (close authority) rather than index 1 (mint authority) or the payer — all three are identical addresses. A bug that swapped accounts 1 and 2 would go undetected. Using a distinct keypair for at least the close authority would make the assertion at line 87 meaningful.

Comment on lines +28 to +31
const TLV_TYPE_OFFSET = 166; // u16 LE, 3 == MintCloseAuthority
const TLV_VALUE_OFFSET = 170; // 32-byte close authority pubkey
const DECIMALS_OFFSET = 44; // in the base mint layout
const MINT_CLOSE_AUTHORITY_EXTENSION = 3;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TLV length field is asserted indirectly, not explicitly

The test validates TLV_TYPE_OFFSET (type = 3) and TLV_VALUE_OFFSET (close authority bytes), but never reads the 2-byte TLV length at offset 168 (TLV_TYPE_OFFSET + 2). Adding assert.equal(mintData.readUInt16LE(168), 32) would fully exercise the on-chain TLV header and catch any future change where Token-2022 alters the MintCloseAuthority value size.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +37 to +41
let [mint_account, mint_authority, close_authority, payer, rent_sysvar, _system_program, _token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Token-2022 program ID is never validated

_token_program (account index 6) is accepted and silently forwarded to all three CPIs as the callee program, but its address is never compared against TOKEN_2022_PROGRAM_ID. A caller that passes a spoofed program at index 6 would cause the CPIs to target an arbitrary program. For an educational example this is a known trade-off, but adding even a one-line equality check would demonstrate the secure pattern to learners.

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