add pinocchio create-token example#599
Conversation
6e08f7a to
e995793
Compare
Greptile SummaryAdds a complete Pinocchio implementation of the
Confidence Score: 5/5Safe to merge — new teaching example with no changes to existing logic. All three CPIs use correct account order and privilege flags; the hand-built Borsh payload matches Metaplex wire format; parsing is bounds-checked; the bankrun test covers the full end-to-end flow for both SPL token and NFT paths. No files require special attention. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client as Client (Test)
participant Program as create-token (Pinocchio)
participant SysProg as System Program
participant TokenProg as SPL Token Program
participant MetaplexProg as Metaplex Token Metadata
Client->>Program: create_token(mint, mint_authority, metadata, payer, ...)
Program->>Program: parse CreateTokenArgs (name, symbol, uri, decimals)
Program->>Program: Rent::get().try_minimum_balance(Mint::LEN)
Program->>SysProg: "CreateAccount (mint, lamports, space, owner=TokenProgram)"
SysProg-->>Program: ok
Program->>TokenProg: InitializeMint2 (decimals, mint_authority, freeze_authority)
TokenProg-->>Program: ok
Program->>Program: "build_metadata_data() -> Vec<u8> (discriminator=33 + DataV2 borsh)"
Program->>MetaplexProg: CreateMetadataAccountV3 CPI (InstructionView + invoke)
MetaplexProg-->>Program: ok
Program-->>Client: Ok(())
%%{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)
participant Program as create-token (Pinocchio)
participant SysProg as System Program
participant TokenProg as SPL Token Program
participant MetaplexProg as Metaplex Token Metadata
Client->>Program: create_token(mint, mint_authority, metadata, payer, ...)
Program->>Program: parse CreateTokenArgs (name, symbol, uri, decimals)
Program->>Program: Rent::get().try_minimum_balance(Mint::LEN)
Program->>SysProg: "CreateAccount (mint, lamports, space, owner=TokenProgram)"
SysProg-->>Program: ok
Program->>TokenProg: InitializeMint2 (decimals, mint_authority, freeze_authority)
TokenProg-->>Program: ok
Program->>Program: "build_metadata_data() -> Vec<u8> (discriminator=33 + DataV2 borsh)"
Program->>MetaplexProg: CreateMetadataAccountV3 CPI (InstructionView + invoke)
MetaplexProg-->>Program: ok
Program-->>Client: Ok(())
Reviews (3): Last reviewed commit: "create-token pinocchio: address review f..." | Re-trigger Greptile |
e995793 to
ac589b7
Compare
|
@Perelyn-sama @dev-jodee — rebased onto latest main (picks up the ASM sbpf/Solana pin from #625), CI is now fully green. Ready for review whenever you have a chance 🙏 |
dev-jodee
left a comment
There was a problem hiding this comment.
thanks for the work, couple of comments !
| try { | ||
| mkdirSync(outputDir, { recursive: true }); | ||
| // Point the Solana CLI at mainnet, where the canonical program lives. | ||
| execSync("solana config set -um", { stdio: "inherit" }); |
There was a problem hiding this comment.
don't think we should change the config of the user's cli, program dump can take the url as a parameter, better do it then
There was a problem hiding this comment.
Good call — dropped the solana config set -um and now pass the cluster straight to the dump: solana program dump -um <id> <file>. No more touching the user's CLI config.
|
|
||
| /// Reads a Borsh `string` (a 4-byte little-endian length prefix followed by that | ||
| /// many UTF-8 bytes) starting at `*offset`, advancing `offset` past it. | ||
| fn read_borsh_string<'a>(data: &'a [u8], offset: &mut usize) -> Result<&'a [u8], ProgramError> { |
There was a problem hiding this comment.
this shoudl probably be in a util file instead of the mod.rs, would be cleaner
There was a problem hiding this comment.
Done — the borsh string helpers (read_borsh_string/push_borsh_string) now live in a new util.rs, so mod.rs is just module wiring.
| } | ||
|
|
||
| impl<'a> CreateTokenArgs<'a> { | ||
| /// Parses the instruction data: three Borsh strings followed by a `u8`. |
There was a problem hiding this comment.
think this should be in create_token.rs file as well since its related to that specific ix
There was a problem hiding this comment.
Agreed — moved CreateTokenArgs (struct + parse) into create_token.rs since it's specific to that instruction. The Metaplex program-id const went with it.
| pub use create_token::*; | ||
|
|
||
| /// Size (in bytes) of an SPL Token mint account. | ||
| pub const MINT_SIZE: usize = 82; |
There was a problem hiding this comment.
Does pinocchio have that as a const already? might be able to reuse
There was a problem hiding this comment.
Yes it does — switched to pinocchio_token::state::Mint::LEN and removed the hand-rolled MINT_SIZE const.
| )[0]; | ||
| } | ||
|
|
||
| describe("Create Token (Pinocchio)", async () => { |
There was a problem hiding this comment.
I think this runs 0 test, since describe is async, it doesn't wait for anything ? All the setup (async) should be in before()
There was a problem hiding this comment.
Nice catch — you're right, the async describe callback registered the it blocks too late. Moved the async bankrun setup into a before() hook and kept describe synchronous.
- prepare.mjs: dump program via 'solana program dump -um' instead of mutating the user's Solana CLI config - reuse pinocchio_token Mint::LEN instead of a hand-rolled MINT_SIZE const - move CreateTokenArgs into create_token.rs (ix-specific) and the borsh string helpers into a util module; slim mod.rs to module wiring - tests: run async bankrun setup in a before() hook so the it() blocks actually register (a describe callback runs synchronously)
|
@dev-jodee thanks for the review! Pushed a commit addressing all five points:
Ready for another look when you have a moment. |
…g tests The tests were previously registered inside an async describe callback, so mocha ran 0 of them and CI passed without ever executing the program. With the before() hook they actually run — and surfaced a real failure: Program log: Instruction: CreateToken failed: unsupported BPF instruction Root cause: Rent::try_minimum_balance() takes a floating-point path for the exemption threshold (an f64), which the current platform-tools lower to a float instruction the solana-bankrun VM rejects. Compute the rent-exempt minimum with integer math using pinocchio's default rent constants instead (DEFAULT_LAMPORTS_PER_BYTE already folds in the 2-year threshold). While here, make the program alloc-free: build the Metaplex instruction data in a fixed stack buffer and switch to program_entrypoint! + no_allocator!, matching the other pinocchio examples. Verified with cargo build-sbf (Solana 4.1.1 / platform-tools v1.54) + the bankrun test: both cases pass.
Adds a Pinocchio implementation of
tokens/create-token, continuing the Pinocchio token-examples lane aftertransfer-tokens(#596) andescrow(#598).What it does
Creates an SPL Token mint and attaches an on-chain Metaplex metadata account (name, symbol, URI) — the same flow as the existing
anchorandnativeoptions.Notes
CreateMetadataAccountV3CPI is built by hand (rawInstructionView/cpi::invoke), matching the on-chain wire format.[name, symbol, uri, decimals], identical to thenativeexample's wire format.token_metadata.sofixture via apostinstallscript, the same approach as theanchorexample.nativeexample.Files
tokens/create-token/pinocchio/(program + bankrun test)Cargo.toml(workspace member),README.md(pinocchio link),Cargo.lock