Add Token-2022 transfer-fee Pinocchio example#653
Conversation
Ports the transfer-fee Token-2022 example to Pinocchio, matching the mint-close-authority template (kit + litesvm, official @solana-program packages, real Rent::get()). The program hand-rolls three Token-2022 CPIs to create a mint with the TransferFeeConfig extension: it initializes the fee config to 1% before InitializeMint, then (mirroring the native example) updates it to 10% afterwards via SetTransferFee, which the transfer-fee config authority signs. Both the config and withdraw-withheld authorities are set to the payer. Mint size is 278 bytes (a 108-byte TransferFeeConfig TLV value). The litesvm test decodes the mint with the official Token-2022 codec and asserts the stored authorities and the newer transfer fee (10%, max 5 tokens).
Greptile SummaryAdds a Pinocchio Token-2022 transfer-fee example.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported fee-scaling overflow now returns InvalidInstructionData through checked arithmetic. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Example as Pinocchio Program
participant System as System Program
participant Token as Token-2022
Client->>Example: CreateMint(decimals)
Example->>System: CreateAccount(278 bytes)
Example->>Token: InitializeTransferFeeConfig(1%, max fee)
Example->>Token: InitializeMint(decimals, authorities)
Example->>Token: SetTransferFee(10%, max fee)
Example-->>Client: Mint created
Reviews (3): Last reviewed commit: "token-2022 transfer-fee pinocchio: guard..." | Re-trigger Greptile |
| .invoke()?; | ||
|
|
||
| // The max fee is 5 tokens, scaled by the mint's decimals. | ||
| let max_fee = 5u64 * 10u64.pow(args.decimals as u32); |
There was a problem hiding this comment.
Unchecked fee scaling overflows
When the instruction supplies decimals of 19 or greater, this unvalidated u8 causes the u64 fee calculation to overflow under the enabled release overflow checks, panicking the program and aborting the create-mint transaction instead of returning a controlled ProgramError.
Knowledge Base Used: Tokens Directory Overview
There was a problem hiding this comment.
Good catch — fixed in c81a76e. decimals is unvalidated instruction input and the workspace sets overflow-checks = true, so 5 * 10^decimals would panic and abort the transaction for decimals >= 19. It now scales with checked_pow/checked_mul and returns a controlled ProgramError::InvalidInstructionData:
let max_fee = 10u64
.checked_pow(args.decimals as u32)
.and_then(|scale| scale.checked_mul(5))
.ok_or(ProgramError::InvalidInstructionData)?;Verified: 1 test passing (decimals 9 → max fee unchanged), plus tsc/biome/fmt/clippy and frozen-lockfile all clean.
decimals is unvalidated u8 instruction input and the workspace enables overflow-checks, so 5 * 10^decimals would panic and abort the transaction for large decimals (>= 19). Compute the max fee with checked_pow/checked_mul and return a controlled ProgramError::InvalidInstructionData instead.
4fe6423 to
c81a76e
Compare
What
Adds a Pinocchio implementation of the Token-2022
transfer-feeexample, alongside the existinganchorandnativeversions. Continues the Token-2022 Pinocchio sub-series (aftermint-close-authority#624,non-transferable#630, anddefault-account-state#652), following the same kit + litesvm + official-@solana-program-packages template.How it works
The single instruction creates a Token-2022 mint carrying the
TransferFeeConfigextension. Transfers of the mint's tokens then withhold a fee — a percentage of the transferred amount capped atmaximum_fee— which the withdraw authority can later collect.To exercise both transfer-fee instructions (mirroring the
nativeexample), the program hand-rolls three CPIs. The transfer-fee instructions are namespaced under theTransferFeeExtensionwrapper (Token-2022 instruction variant26) with a second discriminator byte:InitializeTransferFeeConfig(sub0) — fee 1% (100 bp), max fee 5 tokens, with the payer as both the config and withdraw-withheld authority. BeforeInitializeMint.InitializeMint.SetTransferFee(sub5) — update the fee to 10% (1000 bp), afterInitializeMint, signed by the transfer-fee config authority.The mint authority doubles as the freeze authority and is supplied as the same key as the payer, so a single signature covers funding the account and signing the fee update. The extended mint is 278 bytes (base 166 + a 112-byte TLV entry whose 108-byte value holds the two optional authorities, the withheld amount, and the older/newer
TransferFees).Test
litesvm+@solana/kit. The test builds the mint, then decodes it with the official@solana-program/token-2022codec and asserts:decimals,Verified locally:
cargo build-sbf,ts-mocha,tsc --noEmit, biome,cargo fmt --check, clippy, andpnpm install --frozen-lockfileall clean.