add pinocchio pda-mint-authority example - #612
Conversation
Greptile SummaryThis PR adds a Pinocchio port of the
Confidence Score: 4/5
Important Files Changed
Reviews (11): Last reviewed commit: "tokens/pda-mint-authority pinocchio: add..." | Re-trigger Greptile |
| 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.
solana config set -um is never reverted
pnpm install runs this script as a postinstall hook, permanently redirecting the developer's Solana CLI cluster to mainnet. A developer who normally works against devnet or localnet will silently end up with a wrong cluster config after installing. Saving the current cluster with solana config get json_rpc_url, pointing to mainnet only for the dump, then restoring it afterward would avoid the side-effect.
There was a problem hiding this comment.
Good catch — fixed in 1cfe23d. prepare.mjs now dumps the program with a per-command solana program dump -um … instead of solana config set -um, so pnpm install no longer mutates the developer's global Solana CLI cluster.
| let bump = *data.first().ok_or(ProgramError::InvalidInstructionData)?; | ||
|
|
||
| // Verify the supplied account is the canonical PDA for this bump. | ||
| let pda = derive_address( | ||
| &[MintAuthorityPda::SEED_PREFIX], | ||
| Some(bump), | ||
| program_id.as_array(), | ||
| ); |
There was a problem hiding this comment.
Non-canonical bump is accepted without validation
The bump is taken directly from instruction data and passed to derive_address with Some(bump), which computes a PDA for whatever bump value was supplied rather than requiring the canonical one. A caller who deliberately (or accidentally) provides a non-canonical bump will create the mint-authority account at a different address than findProgramAddressSync would derive client-side. Downstream clients that recompute the PDA without knowing which bump was stored will then resolve a different address and be unable to interact with the mints this program created. Since pinocchio_pubkey::derive_address with bump: None finds the canonical bump on-chain, using it here would prevent this class of error.
There was a problem hiding this comment.
Leaving this as-is, for two reasons:
-
Consistency with the established pinocchio pattern. This is exactly what the already-merged
tokens/escrow/pinocchiodoes (make_offer.rs): take the bump from instruction data, thenderive_address(&[seed], Some(bump), program_id)and reject the tx unless the supplied account equals that PDA. Every pinocchio example in the repo follows this; matching it keeps the teaching examples uniform. -
Toolchain constraint. Deriving the canonical bump on-chain needs
find_program_address/create_program_address, whose off-target implementation insolana-addressis gated behind thecurve25519feature. CI lint runscargo clippy -- -D warningson the host target (no--target sbf), so referencing it there fails to compile unless we pull in the curve25519 dependency — which the lightweight pinocchio stack intentionally avoids.derive_address(frompinocchio-pubkey) is the host-compatible primitive, and it only derives for a given bump.
On safety: the supplied mint-authority account is validated against derive_address(Some(bump)), and create/mint re-derive the signer seeds from the bump persisted in that account, so the program is internally consistent. A client that deliberately passes a non-canonical bump only affects its own address derivation; the test (and any normal client) sources the bump from findProgramAddressSync, which always returns the canonical one.
0be01f1 to
0f6fa01
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 🙏 |
|
Applied the |
|
Applied the same refinements @dev-jodee asked for on #624 (now merged):
|
|
Still want this one. Heads up though — #656 changed the ground rules: pinocchio is 0.11 (workspace deps), and tests are mocha+tsx with |
Use 'solana program dump -um' instead of 'solana config set -um', so running pnpm install no longer permanently switches the developer's Solana CLI cluster to mainnet.
Move the async bankrun setup out of the `describe` callback and into a
`before` hook so Mocha collects the `it` blocks (an async `describe` body
registers tests after the suite is already collected, so nothing ran).
With the test now executing, replace `Rent::try_minimum_balance` (both the
mint and the PDA account) with the integer rent formula: its floating-point
exemption-threshold path emits an opcode the bankrun VM rejects ("unsupported
BPF instruction"). Matches the create-token example.
Apply the kit + litesvm template from the mint-close-authority example: build and sign transactions with @solana/kit and run them on litesvm, dropping @solana/web3.js and solana-bankrun entirely. The mint-authority PDA and the metadata/master-edition/ATA addresses are derived with getProgramDerivedAddress; the Metaplex Token Metadata program is still dumped from mainnet by prepare.mjs and loaded into LiteSVM via addProgramFromFile. - deps: drop @solana/web3.js + solana-bankrun, add litesvm; pin @solana/kit to ^6.10.0 (litesvm's kit major) so there is a single kit in the tree - tsconfig: bump typescript to ^5 and lib to es2022+dom (kit's types), add @types/node; the suite is type-clean under tsc --noEmit
Applies dev-jodee's solana-foundation#624 review refinements on top of the kit + litesvm test: - program: compute rent with Rent::get()?.try_minimum_balance(..)? in both the Init (PDA account) and Create (mint) instructions, instead of the integer-math workaround (only needed to dodge the f64 opcode the old bankrun VM rejected; litesvm runs the real syscall). - test: source the token, associated-token and system program ids from the official @solana-program/token and @solana-program/system packages, and read the minted amount with getTokenDecoder().decode(...).amount instead of a raw byte offset. Token Metadata has no official @solana-program client, so its id stays hand-rolled. - tsconfig: moduleResolution bundler for the packages' subpath exports. Verified locally: cargo build-sbf + ts-mocha -> 3 passing, tsc --noEmit and biome clean, frozen-lockfile OK.
Rebased onto main, which bumped pinocchio 0.10 -> 0.11 and dropped the pinocchio-pubkey crate. Adapt to the 0.11 API: - Derive the mint-authority PDA on-chain with Address::find_program_address (via solana-address's curve25519 feature, matching the merged block-list example) instead of pinocchio_pubkey::derive_address. init now rejects a non-canonical bump, addressing the earlier review comment. - process_instruction / instruction handlers take &mut [AccountView]; pass copied AccountViews into the metadata/edition CPIs. - pinocchio-token MintTo gained multisig_signers (&[]).
e076fe1 to
8fc7975
Compare
|
@dev-jodee #624 is merged, so I've rebased this onto latest That migration also resolves the earlier non-canonical-bump comment: Host |
…ts, tsconfig) with repo prettier 3.9.6
| // Recover the PDA bump recorded by `init` and confirm the supplied account is | ||
| // the canonical mint-authority PDA. | ||
| let bump = MintAuthorityPda::deserialize(&mint_authority.try_borrow()?)?.bump; | ||
| let (pda, _) = Address::find_program_address(&[MintAuthorityPda::SEED_PREFIX], program_id); |
There was a problem hiding this comment.
You can use create_program_address instead, provide the bump and then it doesnt need to find the pda address, it directly derives it
There was a problem hiding this comment.
Done in a500411 — create now derives the PDA directly with create_program_address(&[SEED, &[bump]], program_id). The canonical bump is already persisted in the mint-authority account by init, so this no longer searches with find_program_address.
| // Recover the PDA bump recorded by `init` and confirm the supplied account is | ||
| // the canonical mint-authority PDA. | ||
| let bump = MintAuthorityPda::deserialize(&mint_authority.try_borrow()?)?.bump; | ||
| let (pda, _) = Address::find_program_address(&[MintAuthorityPda::SEED_PREFIX], program_id); |
There was a problem hiding this comment.
You can use create_program_address instead, provide the bump and then it doesnt need to find the pda address, it directly derives it
There was a problem hiding this comment.
Done in a500411 — same change here: mint derives the PDA with create_program_address using the bump stored by init, no more find_program_address.
|
|
||
| // Borsh schema for the Create instruction data, matching the program's | ||
| // `CreateTokenArgs` (and the native example's wire format). | ||
| const CreateTokenArgsSchema: borsh.Schema = { |
There was a problem hiding this comment.
Done in a500411 — the Create instruction data is now built with @solana/kit codecs (getStructEncoder + getU8Encoder, and addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder()) for the borsh strings), and the borsh package is dropped — matching @amilz's approach in #675. Also moved the test stack to mocha + tsx and @solana/kit ^7 per AGENTS.md.
| return edition; | ||
| } | ||
|
|
||
| async function getAssociatedTokenAddress(mint: ReturnType<typeof address>, owner: ReturnType<typeof address>) { |
There was a problem hiding this comment.
pretty sure there's already built in functions ot get ATAs, Master edition pda, etc
There was a problem hiding this comment.
Done in a500411 — the ATA is now derived with findAssociatedTokenPda from @solana-program/token instead of hand-rolling the seeds. The metadata and master-edition PDAs stay on getProgramDerivedAddress (the kit built-in): there is no official @solana-program client for Metaplex Token Metadata (same reason its program id is hand-rolled here), and the umi-based @metaplex-foundation/mpl-token-metadata does not interop with the @solana/kit address type this test uses.
- create/mint: derive the mint-authority PDA directly with create_program_address (the canonical bump is already known and stored) instead of searching with find_program_address - test: build instruction data with @solana/kit codecs instead of the borsh package, and derive the ATA with findAssociatedTokenPda instead of hand-rolling the seeds (follows solana-foundation#675) - test stack: switch to mocha + tsx and @solana/kit ^7 per AGENTS.md / solana-foundation#656
Adds a Pinocchio port of the
tokens/pda-mint-authorityexample, alongside the existinganchorandnativeversions.What it does
A program-derived address — not a wallet — is the mint and freeze authority for every NFT this program creates. Three instructions, dispatched by a leading discriminator byte (matching the native
MyInstructionenum):0) — creates the mint-authority PDA ([b"mint_authority"]), signed by its own seeds, and persists the canonical bump in the account.1) — creates a 0-decimal SPL mint whose authority is the PDA, then attaches a Metaplex metadata account via a hand-rolledCreateMetadataAccountV3CPI. The metadata CPI is authorized with the PDA's seeds viainvoke_signed.2) — creates the payer's associated token account (idempotent), mints the single token, then creates the master edition via a hand-rolledCreateMasterEditionV3CPI (max_supply = Some(1)). Both theMintToand master-edition CPIs are signed by the PDA.The new building block here versus the other token examples is PDA-as-signer:
Init, the metadata CPI, theMintTo, and the master-edition CPI all sign as the PDA usingpinocchio::cpi::{Seed, Signer}andinvoke_signed, rather than relying on a wallet signature. The bump recorded byInitis read back from the PDA account to rebuild the signer seeds without re-deriving the address on-chain.Tests
tests/test.tsruns undersolana-bankrun, loading the program plus the Token Metadata program (dumped from mainnet intotests/fixturesbyprepare.mjs). Three cases:CreateMasterEditionV3CPI succeeded).