├── sdk ├── README.md ├── Cargo.toml └── src │ └── sysvars │ ├── fees.rs │ ├── slot_hashes │ └── test_edge.rs │ └── mod.rs ├── .gitignore ├── rust-toolchain.toml ├── scripts ├── setup │ ├── spellcheck.toml │ ├── members.mts │ ├── ci.mts │ ├── solana.dic │ └── shared.mts ├── lint.mts ├── miri.mts ├── build-sbf.mts ├── test.mts ├── hack.mts ├── doc.mts ├── semver.mts ├── format.mts ├── clippy.mts ├── publish.mts └── audit.mts ├── programs ├── associated-token-account │ ├── src │ │ ├── lib.rs │ │ └── instructions │ │ │ ├── mod.rs │ │ │ ├── create.rs │ │ │ ├── create_idempotent.rs │ │ │ └── recover_nested.rs │ ├── Cargo.toml │ └── README.md ├── token │ ├── src │ │ ├── state │ │ │ ├── mod.rs │ │ │ ├── account_state.rs │ │ │ └── multisig.rs │ │ ├── lib.rs │ │ └── instructions │ │ │ ├── sync_native.rs │ │ │ ├── mod.rs │ │ │ ├── revoke.rs │ │ │ ├── initialize_account.rs │ │ │ ├── thaw_account.rs │ │ │ ├── close_account.rs │ │ │ ├── freeze_account.rs │ │ │ ├── initialize_account_3.rs │ │ │ ├── initialize_account_2.rs │ │ │ ├── transfer.rs │ │ │ ├── approve.rs │ │ │ ├── mint_to.rs │ │ │ ├── burn.rs │ │ │ ├── mint_to_checked.rs │ │ │ ├── burn_checked.rs │ │ │ ├── approve_checked.rs │ │ │ ├── initialize_mint_2.rs │ │ │ ├── transfer_checked.rs │ │ │ ├── initialize_mint.rs │ │ │ ├── set_authority.rs │ │ │ ├── initialize_multisig_2.rs │ │ │ └── initialize_multisig.rs │ ├── Cargo.toml │ └── README.md ├── token-2022 │ ├── src │ │ ├── state │ │ │ ├── mod.rs │ │ │ ├── account_state.rs │ │ │ └── multisig.rs │ │ ├── lib.rs │ │ └── instructions │ │ │ ├── mod.rs │ │ │ ├── sync_native.rs │ │ │ ├── revoke.rs │ │ │ ├── initialize_account.rs │ │ │ ├── thaw_account.rs │ │ │ ├── close_account.rs │ │ │ ├── freeze_account.rs │ │ │ ├── initialize_account_3.rs │ │ │ ├── initialize_account_2.rs │ │ │ ├── transfer.rs │ │ │ ├── approve.rs │ │ │ ├── mint_to.rs │ │ │ ├── burn.rs │ │ │ ├── mint_to_checked.rs │ │ │ ├── burn_checked.rs │ │ │ ├── initialize_mint_2.rs │ │ │ ├── approve_checked.rs │ │ │ ├── transfer_checked.rs │ │ │ ├── initialize_mint.rs │ │ │ ├── set_authority.rs │ │ │ ├── initialize_multisig_2.rs │ │ │ └── initialize_multisig.rs │ ├── Cargo.toml │ └── README.md ├── memo │ ├── src │ │ ├── lib.rs │ │ └── instructions │ │ │ └── mod.rs │ ├── Cargo.toml │ └── README.md └── system │ ├── Cargo.toml │ ├── src │ ├── instructions │ │ ├── mod.rs │ │ ├── upgrade_nonce_account.rs │ │ ├── allocate.rs │ │ ├── assign.rs │ │ ├── transfer.rs │ │ ├── advance_nonce_account.rs │ │ ├── authorize_nonce_account.rs │ │ ├── initialize_nonce_account.rs │ │ ├── assign_with_seed.rs │ │ ├── create_account.rs │ │ ├── withdraw_nonce_account.rs │ │ ├── transfer_with_seed.rs │ │ ├── allocate_with_seed.rs │ │ └── create_account_with_seed.rs │ └── lib.rs │ └── README.md ├── .prettierrc ├── package.json ├── Cargo.toml ├── .github ├── cliff.toml ├── workflows │ ├── main.yml │ └── publish.yml └── actions │ └── setup │ └── action.yml └── Cargo.lock /sdk/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /target 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.84.1" 3 | -------------------------------------------------------------------------------- /scripts/setup/spellcheck.toml: -------------------------------------------------------------------------------- 1 | [Hunspell] 2 | use_builtin = true 3 | skip_os_lookups = false 4 | search_dirs = ["."] 5 | extra_dictionaries = ["solana.dic"] 6 | -------------------------------------------------------------------------------- /programs/associated-token-account/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod instructions; 4 | 5 | solana_address::declare_id!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80 9 | } 10 | -------------------------------------------------------------------------------- /programs/associated-token-account/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | mod create; 2 | mod create_idempotent; 3 | mod recover_nested; 4 | 5 | pub use create::*; 6 | pub use create_idempotent::*; 7 | pub use recover_nested::*; 8 | -------------------------------------------------------------------------------- /programs/token/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod account_state; 2 | mod mint; 3 | mod multisig; 4 | mod token; 5 | 6 | pub use account_state::*; 7 | pub use mint::*; 8 | pub use multisig::*; 9 | pub use token::*; 10 | -------------------------------------------------------------------------------- /programs/token-2022/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod account_state; 2 | mod mint; 3 | mod multisig; 4 | mod token; 5 | 6 | pub use account_state::*; 7 | pub use mint::*; 8 | pub use multisig::*; 9 | pub use token::*; 10 | -------------------------------------------------------------------------------- /scripts/setup/members.mts: -------------------------------------------------------------------------------- 1 | import 'zx/globals'; 2 | import { getCargo } from './shared.mts'; 3 | 4 | const members = getCargo().workspace['members'] as string[]; 5 | await $`echo members=${JSON.stringify(members)} >> $GITHUB_OUTPUT`; 6 | -------------------------------------------------------------------------------- /scripts/lint.mts: -------------------------------------------------------------------------------- 1 | import 'zx/globals'; 2 | 3 | const args = process.argv.slice(2); 4 | 5 | await Promise.all([ 6 | $`tsx ./scripts/clippy.mts ${args}`, 7 | $`tsx ./scripts/doc.mts ${args}`, 8 | $`tsx ./scripts/hack.mts ${args}`, 9 | ]); 10 | -------------------------------------------------------------------------------- /scripts/miri.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, getToolchainArgument } from './setup/shared.mts'; 4 | 5 | const args = cliArguments(); 6 | const toolchain = getToolchainArgument('lint'); 7 | 8 | await $`cargo ${toolchain} miri test ${args}`; 9 | -------------------------------------------------------------------------------- /programs/memo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod instructions; 4 | 5 | /// Legacy symbols from Memo version 1 6 | pub mod v1 { 7 | solana_address::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"); 8 | } 9 | 10 | solana_address::declare_id!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); 11 | -------------------------------------------------------------------------------- /scripts/build-sbf.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from './setup/shared.mts'; 4 | 5 | const [folder, ...args] = cliArguments(); 6 | 7 | const buildArgs = [...args, '--', '--all-targets', '--all-features']; 8 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 9 | 10 | await $`cargo-build-sbf --manifest-path ${manifestPath} ${buildArgs}`; 11 | -------------------------------------------------------------------------------- /scripts/setup/ci.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import { getSolanaVersion, getToolchain } from './shared.mts'; 3 | 4 | await $`echo "SOLANA_VERSION=${getSolanaVersion()}" >> $GITHUB_ENV`; 5 | await $`echo "TOOLCHAIN_BUILD=${getToolchain('build')}" >> $GITHUB_ENV`; 6 | await $`echo "TOOLCHAIN_FORMAT=${getToolchain('format')}" >> $GITHUB_ENV`; 7 | await $`echo "TOOLCHAIN_LINT=${getToolchain('lint')}" >> $GITHUB_ENV`; 8 | await $`echo "TOOLCHAIN_TEST=${getToolchain('test')}" >> $GITHUB_ENV`; 9 | -------------------------------------------------------------------------------- /programs/token/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod instructions; 4 | pub mod state; 5 | 6 | use core::mem::MaybeUninit; 7 | 8 | solana_address::declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); 9 | 10 | const UNINIT_BYTE: MaybeUninit = MaybeUninit::::uninit(); 11 | 12 | #[inline(always)] 13 | fn write_bytes(destination: &mut [MaybeUninit], source: &[u8]) { 14 | for (d, s) in destination.iter_mut().zip(source.iter()) { 15 | d.write(*s); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /programs/token-2022/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod instructions; 4 | pub mod state; 5 | 6 | use core::mem::MaybeUninit; 7 | 8 | solana_address::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); 9 | 10 | const UNINIT_BYTE: MaybeUninit = MaybeUninit::::uninit(); 11 | 12 | #[inline(always)] 13 | fn write_bytes(destination: &mut [MaybeUninit], source: &[u8]) { 14 | for (d, s) in destination.iter_mut().zip(source.iter()) { 15 | d.write(*s); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/test.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | workingDirectory, 7 | } from './setup/shared.mts'; 8 | 9 | const [folder, ...args] = cliArguments(); 10 | 11 | const testArgs = ['--all-features', ...args]; 12 | const toolchain = getToolchainArgument('test'); 13 | 14 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 15 | 16 | await $`cargo ${toolchain} test --manifest-path ${manifestPath} ${testArgs}`; 17 | -------------------------------------------------------------------------------- /programs/system/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio-system" 3 | description = "Pinocchio helpers to invoke System program instructions" 4 | version = "0.4.0" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | rust-version = { workspace = true } 10 | 11 | [lib] 12 | crate-type = ["rlib"] 13 | 14 | [dependencies] 15 | pinocchio = { workspace = true, features = ["cpi"] } 16 | solana-address = { workspace = true, features = ["decode"] } 17 | -------------------------------------------------------------------------------- /scripts/hack.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | workingDirectory, 7 | } from './setup/shared.mts'; 8 | 9 | const [folder, ...args] = cliArguments(); 10 | const checkArgs = ['--all-targets', '--feature-powerset', ...args]; 11 | 12 | const toolchain = getToolchainArgument('lint'); 13 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 14 | 15 | await $`cargo ${toolchain} hack check --manifest-path ${manifestPath} ${checkArgs}`; 16 | -------------------------------------------------------------------------------- /scripts/doc.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | workingDirectory, 7 | } from './setup/shared.mts'; 8 | 9 | const [folder, ...args] = cliArguments(); 10 | const docArgs = ['--all-features', '--no-deps', ...args]; 11 | 12 | const toolchain = getToolchainArgument('lint'); 13 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 14 | 15 | $.env['RUSTDOCFLAGS'] = '--cfg docsrs -D warnings'; 16 | await $`cargo ${toolchain} doc --manifest-path ${manifestPath} ${docArgs}`; 17 | -------------------------------------------------------------------------------- /programs/token-2022/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio-token-2022" 3 | description = "Pinocchio helpers to invoke Token-2022 program instructions" 4 | version = "0.1.0" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | 10 | [lib] 11 | crate-type = ["rlib"] 12 | 13 | [dependencies] 14 | solana-account-view = { workspace = true } 15 | solana-address = { workspace = true, features = ["decode"] } 16 | solana-instruction-view = { workspace = true, features = ["cpi"] } 17 | solana-program-error = { workspace = true } 18 | -------------------------------------------------------------------------------- /programs/memo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio-memo" 3 | description = "Pinocchio helpers to invoke Memo program instructions" 4 | version = "0.2.0" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | rust-version = { workspace = true } 10 | 11 | [lib] 12 | crate-type = ["rlib"] 13 | 14 | [dependencies] 15 | solana-account-view = { workspace = true } 16 | solana-address = { workspace = true, features = ["decode"] } 17 | solana-instruction-view = { workspace = true, features = ["cpi"] } 18 | solana-program-error = { workspace = true } 19 | -------------------------------------------------------------------------------- /scripts/semver.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, getCargo, workingDirectory } from './setup/shared.mts'; 4 | 5 | const [folder, ...args] = cliArguments(); 6 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 7 | 8 | const isProcMacro = getCargo(folder).lib['proc-macro'] === true; 9 | 10 | if (isProcMacro) { 11 | echo( 12 | chalk.yellow.bold('[ SKIPPED ]'), 13 | 'Proc-macro found: only library targets with API surface that can be checked for semver.' 14 | ); 15 | } else { 16 | await $`cargo semver-checks --manifest-path ${manifestPath} ${args}`; 17 | } 18 | -------------------------------------------------------------------------------- /programs/token/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio-token" 3 | description = "Pinocchio helpers to invoke Token program instructions" 4 | version = "0.4.0" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | rust-version = { workspace = true } 10 | 11 | [lib] 12 | crate-type = ["rlib"] 13 | 14 | [dependencies] 15 | solana-account-view = { workspace = true } 16 | solana-address = { workspace = true, features = ["decode"] } 17 | solana-instruction-view = { workspace = true, features = ["cpi"] } 18 | solana-program-error = { workspace = true } 19 | -------------------------------------------------------------------------------- /programs/associated-token-account/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio-associated-token-account" 3 | description = "Pinocchio helpers to invoke Associated Token Account program instructions" 4 | version = "0.2.0" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | rust-version = { workspace = true } 10 | 11 | [lib] 12 | crate-type = ["rlib"] 13 | 14 | [dependencies] 15 | solana-account-view = { workspace = true } 16 | solana-address = { workspace = true, features = ["decode"] } 17 | solana-instruction-view = { workspace = true, features = ["cpi"] } 18 | solana-program-error = { workspace = true } 19 | -------------------------------------------------------------------------------- /scripts/format.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | partitionArguments, 7 | popArgument, 8 | workingDirectory, 9 | } from './setup/shared.mts'; 10 | 11 | const [folder, ...formatArgs] = cliArguments(); 12 | 13 | const toolchain = getToolchainArgument('format'); 14 | const fix = popArgument(formatArgs, '--fix'); 15 | 16 | const [cargoArgs, fmtArgs] = partitionArguments(formatArgs, '--'); 17 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 18 | 19 | // Format the client. 20 | if (fix) { 21 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- ${fmtArgs}`; 22 | } else { 23 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- --check ${fmtArgs}`; 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "cargo-audit": "tsx ./scripts/audit.mts", 5 | "build-sbf": "tsx ./scripts/build-sbf.mts", 6 | "clippy": "tsx ./scripts/clippy.mts", 7 | "doc": "tsx ./scripts/doc.mts", 8 | "format": "tsx ./scripts/format.mts", 9 | "hack": "tsx ./scripts/hack.mts", 10 | "lint": "tsx ./scripts/lint.mts", 11 | "miri": "tsx ./scripts/miri.mts", 12 | "semver": "tsx ./scripts/semver.mts", 13 | "spellcheck": "cargo spellcheck -j1 --code 1", 14 | "test": "tsx ./scripts/test.mts" 15 | }, 16 | "devDependencies": { 17 | "@iarna/toml": "^2.2.5", 18 | "tsx": "^4.19.3", 19 | "typescript": "^5.7.3", 20 | "zx": "^8.3.2" 21 | }, 22 | "engines": { 23 | "node": ">=v20.0.0" 24 | }, 25 | "packageManager": "pnpm@9.1.0" 26 | } 27 | -------------------------------------------------------------------------------- /programs/system/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | mod advance_nonce_account; 2 | mod allocate; 3 | mod allocate_with_seed; 4 | mod assign; 5 | mod assign_with_seed; 6 | mod authorize_nonce_account; 7 | mod create_account; 8 | mod create_account_with_seed; 9 | mod initialize_nonce_account; 10 | mod transfer; 11 | mod transfer_with_seed; 12 | mod upgrade_nonce_account; 13 | mod withdraw_nonce_account; 14 | 15 | pub use advance_nonce_account::*; 16 | pub use allocate::*; 17 | pub use allocate_with_seed::*; 18 | pub use assign::*; 19 | pub use assign_with_seed::*; 20 | pub use authorize_nonce_account::*; 21 | pub use create_account::*; 22 | pub use create_account_with_seed::*; 23 | pub use initialize_nonce_account::*; 24 | pub use transfer::*; 25 | pub use transfer_with_seed::*; 26 | pub use upgrade_nonce_account::*; 27 | pub use withdraw_nonce_account::*; 28 | -------------------------------------------------------------------------------- /scripts/clippy.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | popArgument, 7 | workingDirectory, 8 | } from './setup/shared.mts'; 9 | 10 | const [folder, ...args] = cliArguments(); 11 | 12 | const lintArgs = [ 13 | '-Zunstable-options', 14 | '--all-targets', 15 | '--all-features', 16 | '--no-deps', 17 | '--', 18 | '--deny=warnings', 19 | ...args, 20 | ]; 21 | 22 | const fix = popArgument(lintArgs, '--fix'); 23 | const toolchain = getToolchainArgument('lint'); 24 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 25 | 26 | // Check the client using Clippy. 27 | if (fix) { 28 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} --fix ${lintArgs}`; 29 | } else { 30 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} ${lintArgs}`; 31 | } 32 | -------------------------------------------------------------------------------- /scripts/setup/solana.dic: -------------------------------------------------------------------------------- 1 | 1000 2 | config 3 | metadata 4 | json 5 | uri 6 | ui 7 | cli 8 | readme/S 9 | arg/S 10 | vec/S 11 | enum/S 12 | noop/S 13 | realloc/S 14 | overallocate/SGD 15 | namespace 16 | serde 17 | deserialize/SRGD 18 | deserialization 19 | struct/S 20 | param/S 21 | tuple/S 22 | metas 23 | infos 24 | async 25 | subcommand 26 | repo 27 | init 28 | solana 29 | sol/S 30 | blockchain/S 31 | permissionless 32 | composability 33 | runtime 34 | onchain 35 | offchain 36 | keypair/S 37 | decrypt/SGD 38 | lamport/S 39 | validator/S 40 | pubkey/S 41 | sysvar/S 42 | timestamp/S 43 | entrypoint/S 44 | spl 45 | pda/S 46 | PDA/S 47 | multisignature/S 48 | multisig/S 49 | staker/S 50 | APY 51 | codama 52 | autogenerated 53 | sdk 54 | blockhash/S 55 | VM 56 | SVM 57 | BPF 58 | SBF 59 | inlined 60 | CU/S 61 | borrowable 62 | callee 63 | RPC 64 | ed25519 65 | performant 66 | syscall/S 67 | bitmask 68 | pinocchio 69 | mainnet 70 | getters 71 | PRNG 72 | middleware 73 | MiB 74 | -------------------------------------------------------------------------------- /programs/system/src/instructions/upgrade_nonce_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::invoke, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, ProgramResult, 5 | }; 6 | 7 | /// One-time idempotent upgrade of legacy nonce versions in order to bump 8 | /// them out of chain blockhash domain. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` Nonce account 12 | pub struct UpgradeNonceAccount<'a> { 13 | /// Nonce account. 14 | pub account: &'a AccountView, 15 | } 16 | 17 | impl UpgradeNonceAccount<'_> { 18 | #[inline(always)] 19 | pub fn invoke(&self) -> ProgramResult { 20 | // Instruction accounts 21 | let instruction_accounts: [InstructionAccount; 1] = 22 | [InstructionAccount::writable(self.account.address())]; 23 | 24 | // instruction 25 | let instruction = InstructionView { 26 | program_id: &crate::ID, 27 | accounts: &instruction_accounts, 28 | data: &[12, 0, 0, 0], 29 | }; 30 | 31 | invoke(&instruction, &[self.account]) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /programs/token/src/instructions/sync_native.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 3 | use solana_program_error::ProgramResult; 4 | 5 | /// Given a native token account updates its amount field based 6 | /// on the account's underlying `lamports`. 7 | /// 8 | /// ### Accounts: 9 | /// 0. `[WRITE]` The native token account to sync with its underlying 10 | /// lamports. 11 | pub struct SyncNative<'a> { 12 | /// Native Token Account 13 | pub native_token: &'a AccountView, 14 | } 15 | 16 | impl SyncNative<'_> { 17 | #[inline(always)] 18 | pub fn invoke(&self) -> ProgramResult { 19 | // Instruction accounts 20 | let instruction_accounts: [InstructionAccount; 1] = 21 | [InstructionAccount::writable(self.native_token.address())]; 22 | 23 | let instruction = InstructionView { 24 | program_id: &crate::ID, 25 | accounts: &instruction_accounts, 26 | data: &[17], 27 | }; 28 | 29 | invoke(&instruction, &[self.native_token]) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "programs/associated-token-account", 5 | "programs/memo", 6 | "programs/system", 7 | "programs/token", 8 | "programs/token-2022", 9 | "sdk", 10 | ] 11 | 12 | [workspace.package] 13 | edition = "2021" 14 | license = "Apache-2.0" 15 | repository = "https://github.com/anza-xyz/pinocchio" 16 | rust-version = "1.84" 17 | 18 | [workspace.dependencies] 19 | five8_const = "0.1.4" 20 | pinocchio = { version = "0.9", default-features = false, path = "sdk" } 21 | solana-account-view = "1.0" 22 | solana-address = "2.0" 23 | solana-define-syscall = "4.0" 24 | solana-instruction-view = "1.0" 25 | solana-program-error = "3.0" 26 | 27 | [workspace.metadata.cli] 28 | solana = "2.3.0" 29 | 30 | [workspace.metadata.toolchains] 31 | build = "1.84.1" 32 | format = "nightly-2025-02-16" 33 | lint = "nightly-2025-02-16" 34 | test = "1.84.1" 35 | 36 | [workspace.metadata.release] 37 | pre-release-commit-message = "Publish {{crate_name}} v{{version}}" 38 | tag-message = "Publish {{crate_name}} v{{version}}" 39 | consolidate-commits = false 40 | 41 | [workspace.metadata.spellcheck] 42 | config = "scripts/setup/spellcheck.toml" 43 | -------------------------------------------------------------------------------- /programs/token/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | mod approve; 2 | mod approve_checked; 3 | mod burn; 4 | mod burn_checked; 5 | mod close_account; 6 | mod freeze_account; 7 | mod initialize_account; 8 | mod initialize_account_2; 9 | mod initialize_account_3; 10 | mod initialize_mint; 11 | mod initialize_mint_2; 12 | mod initialize_multisig; 13 | mod initialize_multisig_2; 14 | mod mint_to; 15 | mod mint_to_checked; 16 | mod revoke; 17 | mod set_authority; 18 | mod sync_native; 19 | mod thaw_account; 20 | mod transfer; 21 | mod transfer_checked; 22 | 23 | pub use approve::*; 24 | pub use approve_checked::*; 25 | pub use burn::*; 26 | pub use burn_checked::*; 27 | pub use close_account::*; 28 | pub use freeze_account::*; 29 | pub use initialize_account::*; 30 | pub use initialize_account_2::*; 31 | pub use initialize_account_3::*; 32 | pub use initialize_mint::*; 33 | pub use initialize_mint_2::*; 34 | pub use initialize_multisig::*; 35 | pub use initialize_multisig_2::*; 36 | pub use mint_to::*; 37 | pub use mint_to_checked::*; 38 | pub use revoke::*; 39 | pub use set_authority::*; 40 | pub use sync_native::*; 41 | pub use thaw_account::*; 42 | pub use transfer::*; 43 | pub use transfer_checked::*; 44 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | mod approve; 2 | mod approve_checked; 3 | mod burn; 4 | mod burn_checked; 5 | mod close_account; 6 | mod freeze_account; 7 | mod initialize_account; 8 | mod initialize_account_2; 9 | mod initialize_account_3; 10 | mod initialize_mint; 11 | mod initialize_mint_2; 12 | mod initialize_multisig; 13 | mod initialize_multisig_2; 14 | mod mint_to; 15 | mod mint_to_checked; 16 | mod revoke; 17 | mod set_authority; 18 | mod sync_native; 19 | mod thaw_account; 20 | mod transfer; 21 | mod transfer_checked; 22 | 23 | pub use approve::*; 24 | pub use approve_checked::*; 25 | pub use burn::*; 26 | pub use burn_checked::*; 27 | pub use close_account::*; 28 | pub use freeze_account::*; 29 | pub use initialize_account::*; 30 | pub use initialize_account_2::*; 31 | pub use initialize_account_3::*; 32 | pub use initialize_mint::*; 33 | pub use initialize_mint_2::*; 34 | pub use initialize_multisig::*; 35 | pub use initialize_multisig_2::*; 36 | pub use mint_to::*; 37 | pub use mint_to_checked::*; 38 | pub use revoke::*; 39 | pub use set_authority::*; 40 | pub use sync_native::*; 41 | pub use thaw_account::*; 42 | pub use transfer::*; 43 | pub use transfer_checked::*; 44 | -------------------------------------------------------------------------------- /programs/token/src/state/account_state.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Clone, Copy, Debug, PartialEq)] 3 | pub enum AccountState { 4 | /// Account is not yet initialized 5 | Uninitialized, 6 | 7 | /// Account is initialized; the account owner and/or delegate may perform 8 | /// permitted operations on this account 9 | Initialized, 10 | 11 | /// Account has been frozen by the mint freeze authority. Neither the 12 | /// account owner nor the delegate are able to perform operations on 13 | /// this account. 14 | Frozen, 15 | } 16 | 17 | impl From for AccountState { 18 | fn from(value: u8) -> Self { 19 | match value { 20 | 0 => AccountState::Uninitialized, 21 | 1 => AccountState::Initialized, 22 | 2 => AccountState::Frozen, 23 | _ => panic!("invalid account state value: {value}"), 24 | } 25 | } 26 | } 27 | 28 | impl From for u8 { 29 | fn from(value: AccountState) -> Self { 30 | match value { 31 | AccountState::Uninitialized => 0, 32 | AccountState::Initialized => 1, 33 | AccountState::Frozen => 2, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /programs/token-2022/src/state/account_state.rs: -------------------------------------------------------------------------------- 1 | #[repr(u8)] 2 | #[derive(Clone, Copy, Debug, PartialEq)] 3 | pub enum AccountState { 4 | /// Account is not yet initialized 5 | Uninitialized, 6 | 7 | /// Account is initialized; the account owner and/or delegate may perform 8 | /// permitted operations on this account 9 | Initialized, 10 | 11 | /// Account has been frozen by the mint freeze authority. Neither the 12 | /// account owner nor the delegate are able to perform operations on 13 | /// this account. 14 | Frozen, 15 | } 16 | 17 | impl From for AccountState { 18 | fn from(value: u8) -> Self { 19 | match value { 20 | 0 => AccountState::Uninitialized, 21 | 1 => AccountState::Initialized, 22 | 2 => AccountState::Frozen, 23 | _ => panic!("invalid account state value: {value}"), 24 | } 25 | } 26 | } 27 | 28 | impl From for u8 { 29 | fn from(value: AccountState) -> Self { 30 | match value { 31 | AccountState::Uninitialized => 0, 32 | AccountState::Initialized => 1, 33 | AccountState::Frozen => 2, 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/sync_native.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 4 | use solana_program_error::ProgramResult; 5 | 6 | /// Given a native token account updates its amount field based 7 | /// on the account's underlying `lamports`. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE]` The native token account to sync with its underlying 11 | /// lamports. 12 | pub struct SyncNative<'a, 'b> { 13 | /// Native Token Account 14 | pub native_token: &'a AccountView, 15 | /// Token Program 16 | pub token_program: &'b Address, 17 | } 18 | 19 | impl SyncNative<'_, '_> { 20 | #[inline(always)] 21 | pub fn invoke(&self) -> ProgramResult { 22 | // Instruction accounts 23 | let instruction_accounts: [InstructionAccount; 1] = 24 | [InstructionAccount::writable(self.native_token.address())]; 25 | 26 | let instruction = InstructionView { 27 | program_id: self.token_program, 28 | accounts: &instruction_accounts, 29 | data: &[17], 30 | }; 31 | 32 | invoke(&instruction, &[self.native_token]) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pinocchio" 3 | description = "Create Solana programs with no external dependencies attached" 4 | version = "0.9.2" 5 | edition = { workspace = true } 6 | license = { workspace = true } 7 | readme = "./README.md" 8 | repository = { workspace = true } 9 | rust-version = { workspace = true } 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | all-features = true 14 | rustdoc-args = ["--cfg=docsrs"] 15 | 16 | [lib] 17 | crate-type = ["rlib"] 18 | 19 | [lints.rust] 20 | unexpected_cfgs = { level = "warn", check-cfg = [ 21 | 'cfg(target_os, values("solana"))', 22 | ] } 23 | 24 | [features] 25 | alloc = ["solana-instruction-view?/slice-cpi"] 26 | copy = ["solana-account-view/copy", "solana-address/copy"] 27 | cpi = ["dep:solana-instruction-view"] 28 | default = ["alloc"] 29 | 30 | [dependencies] 31 | solana-account-view = { workspace = true } 32 | solana-address = { workspace = true, features = ["syscalls"] } 33 | solana-instruction-view = { workspace = true, features = ["cpi"], optional = true } 34 | solana-program-error = { workspace = true } 35 | 36 | [target.'cfg(any(target_os = "solana", target_arch = "bpf"))'.dependencies] 37 | solana-define-syscall = { workspace = true } 38 | 39 | [dev-dependencies] 40 | solana-address = { workspace = true, features = ["decode"] } 41 | pinocchio = { path = ".", features = ["alloc"] } 42 | -------------------------------------------------------------------------------- /programs/token/src/instructions/revoke.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Revokes the delegate's authority. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` The source account. 12 | /// 1. `[SIGNER]` The source account owner. 13 | pub struct Revoke<'a> { 14 | /// Source Account. 15 | pub source: &'a AccountView, 16 | /// Source Owner Account. 17 | pub authority: &'a AccountView, 18 | } 19 | 20 | impl Revoke<'_> { 21 | #[inline(always)] 22 | pub fn invoke(&self) -> ProgramResult { 23 | self.invoke_signed(&[]) 24 | } 25 | 26 | #[inline(always)] 27 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 28 | // Instruction accounts 29 | let instruction_accounts: [InstructionAccount; 2] = [ 30 | InstructionAccount::writable(self.source.address()), 31 | InstructionAccount::readonly_signer(self.authority.address()), 32 | ]; 33 | 34 | let instruction = InstructionView { 35 | program_id: &crate::ID, 36 | accounts: &instruction_accounts, 37 | data: &[5], 38 | }; 39 | 40 | invoke_signed(&instruction, &[self.source, self.authority], signers) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/revoke.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{ 4 | cpi::{invoke_signed, Signer}, 5 | InstructionAccount, InstructionView, 6 | }; 7 | use solana_program_error::ProgramResult; 8 | 9 | /// Revokes the delegate's authority. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE]` The source account. 13 | /// 1. `[SIGNER]` The source account owner. 14 | pub struct Revoke<'a, 'b> { 15 | /// Source Account. 16 | pub source: &'a AccountView, 17 | /// Source Owner Account. 18 | pub authority: &'a AccountView, 19 | /// Token Program 20 | pub token_program: &'b Address, 21 | } 22 | 23 | impl Revoke<'_, '_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | self.invoke_signed(&[]) 27 | } 28 | 29 | #[inline(always)] 30 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 2] = [ 33 | InstructionAccount::writable(self.source.address()), 34 | InstructionAccount::readonly_signer(self.authority.address()), 35 | ]; 36 | 37 | let instruction = InstructionView { 38 | program_id: self.token_program, 39 | accounts: &instruction_accounts, 40 | data: &[5], 41 | }; 42 | 43 | invoke_signed(&instruction, &[self.source, self.authority], signers) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /programs/system/src/instructions/allocate.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, ProgramResult, 5 | }; 6 | 7 | /// Allocate space in a (possibly new) account without funding. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE, SIGNER]` New account 11 | pub struct Allocate<'a> { 12 | /// Account to be assigned. 13 | pub account: &'a AccountView, 14 | 15 | /// Number of bytes of memory to allocate. 16 | pub space: u64, 17 | } 18 | 19 | impl Allocate<'_> { 20 | #[inline(always)] 21 | pub fn invoke(&self) -> ProgramResult { 22 | self.invoke_signed(&[]) 23 | } 24 | 25 | #[inline(always)] 26 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 27 | // Instruction accounts 28 | let instruction_accounts: [InstructionAccount; 1] = 29 | [InstructionAccount::writable_signer(self.account.address())]; 30 | 31 | // instruction data 32 | // - [0..4 ]: instruction discriminator 33 | // - [4..12]: space 34 | let mut instruction_data = [0; 12]; 35 | instruction_data[0] = 8; 36 | instruction_data[4..12].copy_from_slice(&self.space.to_le_bytes()); 37 | 38 | let instruction = InstructionView { 39 | program_id: &crate::ID, 40 | accounts: &instruction_accounts, 41 | data: &instruction_data, 42 | }; 43 | 44 | invoke_signed(&instruction, &[self.account], signers) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /programs/system/src/instructions/assign.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Assign account to a program 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE, SIGNER]` Assigned account address 11 | pub struct Assign<'a, 'b> { 12 | /// Account to be assigned. 13 | pub account: &'a AccountView, 14 | 15 | /// Program account to assign as owner. 16 | pub owner: &'b Address, 17 | } 18 | 19 | impl Assign<'_, '_> { 20 | #[inline(always)] 21 | pub fn invoke(&self) -> ProgramResult { 22 | self.invoke_signed(&[]) 23 | } 24 | 25 | #[inline(always)] 26 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 27 | // Instruction accounts 28 | let instruction_accounts: [InstructionAccount; 1] = 29 | [InstructionAccount::writable_signer(self.account.address())]; 30 | 31 | // instruction data 32 | // - [0..4 ]: instruction discriminator 33 | // - [4..36]: owner address 34 | let mut instruction_data = [0; 36]; 35 | instruction_data[0] = 1; 36 | instruction_data[4..36].copy_from_slice(self.owner.as_ref()); 37 | 38 | let instruction = InstructionView { 39 | program_id: &crate::ID, 40 | accounts: &instruction_accounts, 41 | data: &instruction_data, 42 | }; 43 | 44 | invoke_signed(&instruction, &[self.account], signers) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /scripts/publish.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getCargo, 6 | popArgument, 7 | workingDirectory, 8 | } from './setup/shared.mts'; 9 | 10 | const [folder, ...args] = cliArguments(); 11 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 12 | 13 | const fix = popArgument(args, '--dry-run'); 14 | const dryRun = argv['dry-run'] ?? false; 15 | 16 | const [level] = args; 17 | if (!level) { 18 | throw new Error('A version level — e.g. "patch" — must be provided.'); 19 | } 20 | 21 | // Get the package information from the crate TOML file. 22 | const crate = getCargo(folder).package; 23 | const previous = crate['version']; 24 | const name = crate['name']; 25 | 26 | // Go to the crate folder to release. 27 | cd(path.dirname(manifestPath)); 28 | 29 | // Publish the new version. 30 | const releaseArgs = dryRun 31 | ? [] 32 | : ['--tag-name', `${name}@v{{version}}`, '--no-confirm', '--execute']; 33 | await $`cargo release ${level} ${releaseArgs}`; 34 | 35 | // Stop here if this is a dry run. 36 | if (dryRun) { 37 | process.exit(0); 38 | } 39 | 40 | // Get the updated version number. 41 | const version = getCargo(folder).package['version']; 42 | 43 | // Git tag for the new and old versions. 44 | const newGitTag = `${name}@v${version}`; 45 | const oldGitTag = `${name}@v${previous}`; 46 | 47 | // Expose the versions to CI if needed. 48 | if (process.env.CI) { 49 | await $`echo "new_git_tag=${newGitTag}" >> $GITHUB_OUTPUT`; 50 | await $`echo "old_git_tag=${oldGitTag}" >> $GITHUB_OUTPUT`; 51 | } 52 | -------------------------------------------------------------------------------- /programs/memo/README.md: -------------------------------------------------------------------------------- 1 |

2 | pinocchio-memo 3 |

4 |

5 | pinocchio-memo 6 |

7 |

8 | 9 | 10 |

11 | 12 | ## Overview 13 | 14 | This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for [SPL Memo](https://github.com/solana-program/memo) program instructions. 15 | 16 | Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. 17 | 18 | This is a `no_std` crate. 19 | 20 | > **Note:** The API defined in this crate is subject to change. 21 | 22 | ## Getting Started 23 | 24 | From your project folder: 25 | 26 | ```bash 27 | cargo add pinocchio-memo 28 | ``` 29 | 30 | This will add the `pinocchio-memo` dependency to your `Cargo.toml` file. 31 | 32 | ## Examples 33 | 34 | Creating a memo: 35 | ```rust 36 | // Both accounts should be signers 37 | Memo { 38 | signers: &[&accounts[0], &accounts[1]], 39 | memo: "hello", 40 | } 41 | .invoke()?; 42 | ``` 43 | 44 | ## License 45 | 46 | The code is licensed under the [Apache License Version 2.0](../LICENSE) 47 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 3 | use solana_program_error::ProgramResult; 4 | 5 | /// Initialize a new Token Account. 6 | /// 7 | /// ### Accounts: 8 | /// 0. `[WRITE]` The account to initialize. 9 | /// 1. `[]` The mint this account will be associated with. 10 | /// 2. `[]` The new account's owner/multi-signature. 11 | /// 3. `[]` Rent sysvar 12 | pub struct InitializeAccount<'a> { 13 | /// New Account. 14 | pub account: &'a AccountView, 15 | /// Mint Account. 16 | pub mint: &'a AccountView, 17 | /// Owner of the new Account. 18 | pub owner: &'a AccountView, 19 | /// Rent Sysvar Account 20 | pub rent_sysvar: &'a AccountView, 21 | } 22 | 23 | impl InitializeAccount<'_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | // Instruction accounts 27 | let instruction_accounts: [InstructionAccount; 4] = [ 28 | InstructionAccount::writable(self.account.address()), 29 | InstructionAccount::readonly(self.mint.address()), 30 | InstructionAccount::readonly(self.owner.address()), 31 | InstructionAccount::readonly(self.rent_sysvar.address()), 32 | ]; 33 | 34 | let instruction = InstructionView { 35 | program_id: &crate::ID, 36 | accounts: &instruction_accounts, 37 | data: &[1], 38 | }; 39 | 40 | invoke( 41 | &instruction, 42 | &[self.account, self.mint, self.owner, self.rent_sysvar], 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /programs/system/src/instructions/transfer.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, ProgramResult, 5 | }; 6 | 7 | /// Transfer lamports. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE, SIGNER]` Funding account 11 | /// 1. `[WRITE]` Recipient account 12 | pub struct Transfer<'a> { 13 | /// Funding account. 14 | pub from: &'a AccountView, 15 | 16 | /// Recipient account. 17 | pub to: &'a AccountView, 18 | 19 | /// Amount of lamports to transfer. 20 | pub lamports: u64, 21 | } 22 | 23 | impl Transfer<'_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | self.invoke_signed(&[]) 27 | } 28 | 29 | #[inline(always)] 30 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 2] = [ 33 | InstructionAccount::writable_signer(self.from.address()), 34 | InstructionAccount::writable(self.to.address()), 35 | ]; 36 | 37 | // instruction data 38 | // - [0..4 ]: instruction discriminator 39 | // - [4..12]: lamports amount 40 | let mut instruction_data = [0; 12]; 41 | instruction_data[0] = 2; 42 | instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); 43 | 44 | let instruction = InstructionView { 45 | program_id: &crate::ID, 46 | accounts: &instruction_accounts, 47 | data: &instruction_data, 48 | }; 49 | 50 | invoke_signed(&instruction, &[self.from, self.to], signers) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /programs/token/src/instructions/thaw_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Thaw a frozen account using the Mint's freeze authority. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` The account to thaw. 12 | /// 1. `[]` The token mint. 13 | /// 2. `[SIGNER]` The mint freeze authority. 14 | pub struct ThawAccount<'a> { 15 | /// Token Account to thaw. 16 | pub account: &'a AccountView, 17 | /// Mint Account. 18 | pub mint: &'a AccountView, 19 | /// Mint Freeze Authority Account 20 | pub freeze_authority: &'a AccountView, 21 | } 22 | 23 | impl ThawAccount<'_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | self.invoke_signed(&[]) 27 | } 28 | 29 | #[inline(always)] 30 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 3] = [ 33 | InstructionAccount::writable(self.account.address()), 34 | InstructionAccount::readonly(self.mint.address()), 35 | InstructionAccount::readonly_signer(self.freeze_authority.address()), 36 | ]; 37 | 38 | let instruction = InstructionView { 39 | program_id: &crate::ID, 40 | accounts: &instruction_accounts, 41 | data: &[11], 42 | }; 43 | 44 | invoke_signed( 45 | &instruction, 46 | &[self.account, self.mint, self.freeze_authority], 47 | signers, 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /programs/token/src/instructions/close_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Close an account by transferring all its SOL to the destination account. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` The account to close. 12 | /// 1. `[WRITE]` The destination account. 13 | /// 2. `[SIGNER]` The account's owner. 14 | pub struct CloseAccount<'a> { 15 | /// Token Account. 16 | pub account: &'a AccountView, 17 | /// Destination Account 18 | pub destination: &'a AccountView, 19 | /// Owner Account 20 | pub authority: &'a AccountView, 21 | } 22 | 23 | impl CloseAccount<'_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | self.invoke_signed(&[]) 27 | } 28 | 29 | #[inline(always)] 30 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 3] = [ 33 | InstructionAccount::writable(self.account.address()), 34 | InstructionAccount::writable(self.destination.address()), 35 | InstructionAccount::readonly_signer(self.authority.address()), 36 | ]; 37 | 38 | let instruction = InstructionView { 39 | program_id: &crate::ID, 40 | accounts: &instruction_accounts, 41 | data: &[9], 42 | }; 43 | 44 | invoke_signed( 45 | &instruction, 46 | &[self.account, self.destination, self.authority], 47 | signers, 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /programs/token/src/instructions/freeze_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Freeze an initialized account using the Mint's freeze authority. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` The account to freeze. 12 | /// 1. `[]` The token mint. 13 | /// 2. `[SIGNER]` The mint freeze authority. 14 | pub struct FreezeAccount<'a> { 15 | /// Token Account to freeze. 16 | pub account: &'a AccountView, 17 | /// Mint Account. 18 | pub mint: &'a AccountView, 19 | /// Mint Freeze Authority Account 20 | pub freeze_authority: &'a AccountView, 21 | } 22 | 23 | impl FreezeAccount<'_> { 24 | #[inline(always)] 25 | pub fn invoke(&self) -> ProgramResult { 26 | self.invoke_signed(&[]) 27 | } 28 | 29 | #[inline(always)] 30 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 3] = [ 33 | InstructionAccount::writable(self.account.address()), 34 | InstructionAccount::readonly(self.mint.address()), 35 | InstructionAccount::readonly_signer(self.freeze_authority.address()), 36 | ]; 37 | 38 | let instruction = InstructionView { 39 | program_id: &crate::ID, 40 | accounts: &instruction_accounts, 41 | data: &[10], 42 | }; 43 | 44 | invoke_signed( 45 | &instruction, 46 | &[self.account, self.mint, self.freeze_authority], 47 | signers, 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /programs/system/src/instructions/advance_nonce_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, ProgramResult, 5 | }; 6 | 7 | /// Consumes a stored nonce, replacing it with a successor. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE]` Nonce account 11 | /// 1. `[]` Recent blockhashes sysvar 12 | /// 2. `[SIGNER]` Nonce authority 13 | pub struct AdvanceNonceAccount<'a> { 14 | /// Nonce account. 15 | pub account: &'a AccountView, 16 | 17 | /// Recent blockhashes sysvar. 18 | pub recent_blockhashes_sysvar: &'a AccountView, 19 | 20 | /// Nonce authority. 21 | pub authority: &'a AccountView, 22 | } 23 | 24 | impl AdvanceNonceAccount<'_> { 25 | #[inline(always)] 26 | pub fn invoke(&self) -> ProgramResult { 27 | self.invoke_signed(&[]) 28 | } 29 | 30 | #[inline(always)] 31 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 32 | // Instruction accounts 33 | let instruction_accounts: [InstructionAccount; 3] = [ 34 | InstructionAccount::writable(self.account.address()), 35 | InstructionAccount::readonly(self.recent_blockhashes_sysvar.address()), 36 | InstructionAccount::readonly_signer(self.authority.address()), 37 | ]; 38 | 39 | // instruction 40 | let instruction = InstructionView { 41 | program_id: &crate::ID, 42 | accounts: &instruction_accounts, 43 | data: &[4, 0, 0, 0], 44 | }; 45 | 46 | invoke_signed( 47 | &instruction, 48 | &[self.account, self.recent_blockhashes_sysvar, self.authority], 49 | signers, 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /programs/token/README.md: -------------------------------------------------------------------------------- 1 |

2 | pinocchio-token 3 |

4 |

5 | pinocchio-token 6 |

7 |

8 | 9 | 10 |

11 | 12 | ## Overview 13 | 14 | This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Token instructions. 15 | 16 | Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. 17 | 18 | This is a `no_std` crate. 19 | 20 | > **Note:** The API defined in this crate is subject to change. 21 | 22 | ## Examples 23 | 24 | Initializing a mint account: 25 | ```rust 26 | // This example assumes that the instruction receives a writable `mint` 27 | // account; `authority` is a `Address`. 28 | InitializeMint { 29 | mint, 30 | rent_sysvar, 31 | decimals: 9, 32 | mint_authority: authority, 33 | freeze_authority: Some(authority), 34 | }.invoke()?; 35 | ``` 36 | 37 | Performing a transfer of tokens: 38 | ```rust 39 | // This example assumes that the instruction receives writable `from` and `to` 40 | // accounts, and a signer `authority` account. 41 | Transfer { 42 | from, 43 | to, 44 | authority, 45 | amount: 10, 46 | }.invoke()?; 47 | ``` 48 | 49 | ## License 50 | 51 | The code is licensed under the [Apache License Version 2.0](../LICENSE) 52 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 4 | use solana_program_error::ProgramResult; 5 | 6 | /// Initialize a new Token Account. 7 | /// 8 | /// ### Accounts: 9 | /// 0. `[WRITE]` The account to initialize. 10 | /// 1. `[]` The mint this account will be associated with. 11 | /// 2. `[]` The new account's owner/multi-signature. 12 | /// 3. `[]` Rent sysvar 13 | pub struct InitializeAccount<'a, 'b> { 14 | /// New Account. 15 | pub account: &'a AccountView, 16 | /// Mint Account. 17 | pub mint: &'a AccountView, 18 | /// Owner of the new Account. 19 | pub owner: &'a AccountView, 20 | /// Rent Sysvar Account 21 | pub rent_sysvar: &'a AccountView, 22 | /// Token Program 23 | pub token_program: &'b Address, 24 | } 25 | 26 | impl InitializeAccount<'_, '_> { 27 | #[inline(always)] 28 | pub fn invoke(&self) -> ProgramResult { 29 | // Instruction accounts 30 | let instruction_accounts: [InstructionAccount; 4] = [ 31 | InstructionAccount::writable(self.account.address()), 32 | InstructionAccount::readonly(self.mint.address()), 33 | InstructionAccount::readonly(self.owner.address()), 34 | InstructionAccount::readonly(self.rent_sysvar.address()), 35 | ]; 36 | 37 | let instruction = InstructionView { 38 | program_id: self.token_program, 39 | accounts: &instruction_accounts, 40 | data: &[1], 41 | }; 42 | 43 | invoke( 44 | &instruction, 45 | &[self.account, self.mint, self.owner, self.rent_sysvar], 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/thaw_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{ 4 | cpi::{invoke_signed, Signer}, 5 | InstructionAccount, InstructionView, 6 | }; 7 | use solana_program_error::ProgramResult; 8 | 9 | /// Thaw a Frozen account using the Mint's freeze authority 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE]` The account to thaw. 13 | /// 1. `[]` The token mint. 14 | /// 2. `[SIGNER]` The mint freeze authority. 15 | pub struct ThawAccount<'a, 'b> { 16 | /// Token Account to thaw. 17 | pub account: &'a AccountView, 18 | /// Mint Account. 19 | pub mint: &'a AccountView, 20 | /// Mint Freeze Authority Account 21 | pub freeze_authority: &'a AccountView, 22 | /// Token Program 23 | pub token_program: &'b Address, 24 | } 25 | 26 | impl ThawAccount<'_, '_> { 27 | #[inline(always)] 28 | pub fn invoke(&self) -> ProgramResult { 29 | self.invoke_signed(&[]) 30 | } 31 | 32 | #[inline(always)] 33 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 34 | // Instruction accounts 35 | let instruction_accounts: [InstructionAccount; 3] = [ 36 | InstructionAccount::writable(self.account.address()), 37 | InstructionAccount::readonly(self.mint.address()), 38 | InstructionAccount::readonly_signer(self.freeze_authority.address()), 39 | ]; 40 | 41 | let instruction = InstructionView { 42 | program_id: self.token_program, 43 | accounts: &instruction_accounts, 44 | data: &[11], 45 | }; 46 | 47 | invoke_signed( 48 | &instruction, 49 | &[self.account, self.mint, self.freeze_authority], 50 | signers, 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /programs/system/README.md: -------------------------------------------------------------------------------- 1 |

2 | pinocchio-system 3 |

4 |

5 | pinocchio-system 6 |

7 |

8 | 9 | 10 |

11 | 12 | ## Overview 13 | 14 | This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for System program instructions. 15 | 16 | Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. 17 | 18 | This is a `no_std` crate. 19 | 20 | > **Note:** The API defined in this crate is subject to change. 21 | 22 | ## Examples 23 | 24 | Creating a new account: 25 | ```rust 26 | // This example assumes that the instruction receives a writable signer `payer` 27 | // and `new_account` accounts. 28 | CreateAccount { 29 | from: payer, 30 | to: new_account, 31 | lamports: 1_000_000_000, // 1 SOL 32 | space: 200, // 200 bytes 33 | owner: &spl_token::ID, 34 | }.invoke()?; 35 | ``` 36 | 37 | Performing a transfer of lamports: 38 | ```rust 39 | // This example assumes that the instruction receives a writable signer `payer` 40 | // account and a writable `recipient_` account. 41 | Transfer { 42 | from: payer, 43 | to: recipient, 44 | lamports: 500_000_000, // 0.5 SOL 45 | }.invoke()?; 46 | ``` 47 | 48 | ## License 49 | 50 | The code is licensed under the [Apache License Version 2.0](../LICENSE) 51 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/close_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{ 4 | cpi::{invoke_signed, Signer}, 5 | InstructionAccount, InstructionView, 6 | }; 7 | use solana_program_error::ProgramResult; 8 | 9 | /// Close an account by transferring all its SOL to the destination account. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE]` The account to close. 13 | /// 1. `[WRITE]` The destination account. 14 | /// 2. `[SIGNER]` The account's owner. 15 | pub struct CloseAccount<'a, 'b> { 16 | /// Token Account. 17 | pub account: &'a AccountView, 18 | /// Destination Account 19 | pub destination: &'a AccountView, 20 | /// Owner Account 21 | pub authority: &'a AccountView, 22 | /// Token Program 23 | pub token_program: &'b Address, 24 | } 25 | 26 | impl CloseAccount<'_, '_> { 27 | #[inline(always)] 28 | pub fn invoke(&self) -> ProgramResult { 29 | self.invoke_signed(&[]) 30 | } 31 | 32 | #[inline(always)] 33 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 34 | // Instruction accounts 35 | let instruction_accounts: [InstructionAccount; 3] = [ 36 | InstructionAccount::writable(self.account.address()), 37 | InstructionAccount::writable(self.destination.address()), 38 | InstructionAccount::readonly_signer(self.authority.address()), 39 | ]; 40 | 41 | let instruction = InstructionView { 42 | program_id: self.token_program, 43 | accounts: &instruction_accounts, 44 | data: &[9], 45 | }; 46 | 47 | invoke_signed( 48 | &instruction, 49 | &[self.account, self.destination, self.authority], 50 | signers, 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/freeze_account.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_address::Address; 3 | use solana_instruction_view::{ 4 | cpi::{invoke_signed, Signer}, 5 | InstructionAccount, InstructionView, 6 | }; 7 | use solana_program_error::ProgramResult; 8 | 9 | /// Freeze an Initialized account using the Mint's freeze authority 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE]` The account to freeze. 13 | /// 1. `[]` The token mint. 14 | /// 2. `[SIGNER]` The mint freeze authority. 15 | pub struct FreezeAccount<'a, 'b> { 16 | /// Token Account to freeze. 17 | pub account: &'a AccountView, 18 | /// Mint Account. 19 | pub mint: &'a AccountView, 20 | /// Mint Freeze Authority Account 21 | pub freeze_authority: &'a AccountView, 22 | /// Token Program 23 | pub token_program: &'b Address, 24 | } 25 | 26 | impl FreezeAccount<'_, '_> { 27 | #[inline(always)] 28 | pub fn invoke(&self) -> ProgramResult { 29 | self.invoke_signed(&[]) 30 | } 31 | 32 | #[inline(always)] 33 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 34 | // Instruction accounts 35 | let instruction_accounts: [InstructionAccount; 3] = [ 36 | InstructionAccount::writable(self.account.address()), 37 | InstructionAccount::readonly(self.mint.address()), 38 | InstructionAccount::readonly_signer(self.freeze_authority.address()), 39 | ]; 40 | 41 | let instruction = InstructionView { 42 | program_id: self.token_program, 43 | accounts: &instruction_accounts, 44 | data: &[10], 45 | }; 46 | 47 | invoke_signed( 48 | &instruction, 49 | &[self.account, self.mint, self.freeze_authority], 50 | signers, 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/audit.mts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | 4 | const advisories = [ 5 | // === main repo === 6 | // 7 | // Crate: ed25519-dalek 8 | // Version: 1.0.1 9 | // Title: Double Public Key Signing Function Oracle Attack on `ed25519-dalek` 10 | // Date: 2022-06-11 11 | // ID: RUSTSEC-2022-0093 12 | // URL: https://rustsec.org/advisories/RUSTSEC-2022-0093 13 | // Solution: Upgrade to >=2 14 | 'RUSTSEC-2022-0093', 15 | 16 | // Crate: idna 17 | // Version: 0.1.5 18 | // Title: `idna` accepts Punycode labels that do not produce any non-ASCII when decoded 19 | // Date: 2024-12-09 20 | // ID: RUSTSEC-2024-0421 21 | // URL: https://rustsec.org/advisories/RUSTSEC-2024-0421 22 | // Solution: Upgrade to >=1.0.0 23 | // need to solve this depentant tree: 24 | // jsonrpc-core-client v18.0.0 -> jsonrpc-client-transports v18.0.0 -> url v1.7.2 -> idna v0.1.5 25 | 'RUSTSEC-2024-0421', 26 | 27 | // === programs/sbf === 28 | // 29 | // Crate: curve25519-dalek 30 | // Version: 3.2.1 31 | // Title: Timing variability in `curve25519-dalek`'s `Scalar29::sub`/`Scalar52::sub` 32 | // Date: 2024-06-18 33 | // ID: RUSTSEC-2024-0344 34 | // URL: https://rustsec.org/advisories/RUSTSEC-2024-0344 35 | // Solution: Upgrade to >=4.1.3 36 | 'RUSTSEC-2024-0344', 37 | 38 | // Crate: tonic 39 | // Version: 0.9.2 40 | // Title: Remotely exploitable Denial of Service in Tonic 41 | // Date: 2024-10-01 42 | // ID: RUSTSEC-2024-0376 43 | // URL: https://rustsec.org/advisories/RUSTSEC-2024-0376 44 | // Solution: Upgrade to >=0.12.3 45 | 'RUSTSEC-2024-0376', 46 | ]; 47 | const ignores: string[] = []; 48 | advisories.forEach((x) => { 49 | ignores.push('--ignore'); 50 | ignores.push(x); 51 | }); 52 | 53 | await $`cargo audit ${ignores}`; 54 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_account_3.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new Token Account. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE]` The account to initialize. 14 | /// 1. `[]` The mint this account will be associated with. 15 | pub struct InitializeAccount3<'a> { 16 | /// New Account. 17 | pub account: &'a AccountView, 18 | /// Mint Account. 19 | pub mint: &'a AccountView, 20 | /// Owner of the new Account. 21 | pub owner: &'a Address, 22 | } 23 | 24 | impl InitializeAccount3<'_> { 25 | #[inline(always)] 26 | pub fn invoke(&self) -> ProgramResult { 27 | // Instruction accounts 28 | let instruction_accounts: [InstructionAccount; 2] = [ 29 | InstructionAccount::writable(self.account.address()), 30 | InstructionAccount::readonly(self.mint.address()), 31 | ]; 32 | 33 | // instruction data 34 | // - [0]: instruction discriminator (1 byte, u8) 35 | // - [1..33]: owner (32 bytes, Address) 36 | let mut instruction_data = [UNINIT_BYTE; 33]; 37 | 38 | // Set discriminator as u8 at offset [0] 39 | write_bytes(&mut instruction_data, &[18]); 40 | // Set owner as [u8; 32] at offset [1..33] 41 | write_bytes(&mut instruction_data[1..], self.owner.as_array()); 42 | 43 | let instruction = InstructionView { 44 | program_id: &crate::ID, 45 | accounts: &instruction_accounts, 46 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, 47 | }; 48 | 49 | invoke(&instruction, &[self.account, self.mint]) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /programs/system/src/instructions/authorize_nonce_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Change the entity authorized to execute nonce instructions on the account. 8 | /// 9 | /// The [`Address`] parameter identifies the entity to authorize. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE]` Nonce account 13 | /// 1. `[SIGNER]` Nonce authority 14 | pub struct AuthorizeNonceAccount<'a, 'b> { 15 | /// Nonce account. 16 | pub account: &'a AccountView, 17 | 18 | /// Nonce authority. 19 | pub authority: &'a AccountView, 20 | 21 | /// New entity authorized to execute nonce instructions on the account. 22 | pub new_authority: &'b Address, 23 | } 24 | 25 | impl AuthorizeNonceAccount<'_, '_> { 26 | #[inline(always)] 27 | pub fn invoke(&self) -> ProgramResult { 28 | self.invoke_signed(&[]) 29 | } 30 | 31 | #[inline(always)] 32 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 33 | // Instruction accounts 34 | let instruction_accounts: [InstructionAccount; 2] = [ 35 | InstructionAccount::writable(self.account.address()), 36 | InstructionAccount::readonly_signer(self.authority.address()), 37 | ]; 38 | 39 | // instruction data 40 | // - [0..4 ]: instruction discriminator 41 | // - [4..12]: lamports 42 | let mut instruction_data = [0; 36]; 43 | instruction_data[0] = 7; 44 | instruction_data[4..36].copy_from_slice(self.new_authority.as_array()); 45 | 46 | let instruction = InstructionView { 47 | program_id: &crate::ID, 48 | accounts: &instruction_accounts, 49 | data: &instruction_data, 50 | }; 51 | 52 | invoke_signed(&instruction, &[self.account, self.authority], signers) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_account_3.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new Token Account. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE]` The account to initialize. 14 | /// 1. `[]` The mint this account will be associated with. 15 | pub struct InitializeAccount3<'a, 'b> { 16 | /// New Account. 17 | pub account: &'a AccountView, 18 | /// Mint Account. 19 | pub mint: &'a AccountView, 20 | /// Owner of the new Account. 21 | pub owner: &'a Address, 22 | /// Token Program 23 | pub token_program: &'b Address, 24 | } 25 | 26 | impl InitializeAccount3<'_, '_> { 27 | #[inline(always)] 28 | pub fn invoke(&self) -> ProgramResult { 29 | // Instruction accounts 30 | let instruction_accounts: [InstructionAccount; 2] = [ 31 | InstructionAccount::writable(self.account.address()), 32 | InstructionAccount::readonly(self.mint.address()), 33 | ]; 34 | 35 | // instruction data 36 | // - [0]: instruction discriminator (1 byte, u8) 37 | // - [1..33]: owner (32 bytes, Address) 38 | let mut instruction_data = [UNINIT_BYTE; 33]; 39 | 40 | // Set discriminator as u8 at offset [0] 41 | write_bytes(&mut instruction_data, &[18]); 42 | // Set owner as [u8; 32] at offset [1..33] 43 | write_bytes(&mut instruction_data[1..], self.owner.as_array()); 44 | 45 | let instruction = InstructionView { 46 | program_id: self.token_program, 47 | accounts: &instruction_accounts, 48 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, 49 | }; 50 | 51 | invoke(&instruction, &[self.account, self.mint]) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_account_2.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new Token Account. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE]` The account to initialize. 14 | /// 1. `[]` The mint this account will be associated with. 15 | /// 3. `[]` Rent sysvar 16 | pub struct InitializeAccount2<'a> { 17 | /// New Account. 18 | pub account: &'a AccountView, 19 | /// Mint Account. 20 | pub mint: &'a AccountView, 21 | /// Rent Sysvar Account 22 | pub rent_sysvar: &'a AccountView, 23 | /// Owner of the new Account. 24 | pub owner: &'a Address, 25 | } 26 | 27 | impl InitializeAccount2<'_> { 28 | #[inline(always)] 29 | pub fn invoke(&self) -> ProgramResult { 30 | // Instruction accounts 31 | let instruction_accounts: [InstructionAccount; 3] = [ 32 | InstructionAccount::writable(self.account.address()), 33 | InstructionAccount::readonly(self.mint.address()), 34 | InstructionAccount::readonly(self.rent_sysvar.address()), 35 | ]; 36 | 37 | // instruction data 38 | // - [0]: instruction discriminator (1 byte, u8) 39 | // - [1..33]: owner (32 bytes, Address) 40 | let mut instruction_data = [UNINIT_BYTE; 33]; 41 | 42 | // Set discriminator as u8 at offset [0] 43 | write_bytes(&mut instruction_data, &[16]); 44 | // Set owner as [u8; 32] at offset [1..33] 45 | write_bytes(&mut instruction_data[1..], self.owner.as_array()); 46 | 47 | let instruction = InstructionView { 48 | program_id: &crate::ID, 49 | accounts: &instruction_accounts, 50 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, 51 | }; 52 | 53 | invoke(&instruction, &[self.account, self.mint, self.rent_sysvar]) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | header = """ 10 | ## What's new 11 | """ 12 | # template for the changelog body 13 | # https://tera.netlify.app/docs 14 | body = """ 15 | {% for group, commits in commits | group_by(attribute="group") %}\ 16 | {% for commit in commits %} 17 | - {{ commit.message | upper_first | split(pat="\n") | first | trim }}\ 18 | {% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif %}\ 19 | {% endfor %}\ 20 | {% endfor %} 21 | """ 22 | # remove the leading and trailing whitespace from the template 23 | trim = true 24 | footer = """ 25 | """ 26 | postprocessors = [ ] 27 | [git] 28 | # parse the commits based on https://www.conventionalcommits.org 29 | conventional_commits = true 30 | # filter out the commits that are not conventional 31 | filter_unconventional = false 32 | # process each line of a commit as an individual commit 33 | split_commits = false 34 | # regex for preprocessing the commit messages 35 | commit_preprocessors = [] 36 | # regex for parsing and grouping commits 37 | commit_parsers = [ 38 | { message = "^build\\(deps\\)", skip = true }, 39 | { message = "^build\\(deps-dev\\)", skip = true }, 40 | { message = "^ci", skip = true }, 41 | { body = ".*", group = "Changes" }, 42 | ] 43 | # protect breaking changes from being skipped due to matching a skipping commit_parser 44 | protect_breaking_commits = false 45 | # filter out the commits that are not matched by commit parsers 46 | filter_commits = false 47 | # glob pattern for matching git tags 48 | tag_pattern = "v[0-9]*" 49 | # regex for skipping tags 50 | skip_tags = "" 51 | # regex for ignoring tags 52 | ignore_tags = "" 53 | # sort the tags topologically 54 | topo_order = false 55 | # sort the commits inside sections by oldest/newest order 56 | sort_commits = "newest" 57 | # limit the number of commits included in the changelog. 58 | # limit_commits = 42 -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_account_2.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new Token Account. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE]` The account to initialize. 14 | /// 1. `[]` The mint this account will be associated with. 15 | /// 3. `[]` Rent sysvar 16 | pub struct InitializeAccount2<'a, 'b> { 17 | /// New Account. 18 | pub account: &'a AccountView, 19 | /// Mint Account. 20 | pub mint: &'a AccountView, 21 | /// Rent Sysvar Account 22 | pub rent_sysvar: &'a AccountView, 23 | /// Owner of the new Account. 24 | pub owner: &'a Address, 25 | /// Token Program 26 | pub token_program: &'b Address, 27 | } 28 | 29 | impl InitializeAccount2<'_, '_> { 30 | #[inline(always)] 31 | pub fn invoke(&self) -> ProgramResult { 32 | // Instruction accounts 33 | let instruction_accounts: [InstructionAccount; 3] = [ 34 | InstructionAccount::writable(self.account.address()), 35 | InstructionAccount::readonly(self.mint.address()), 36 | InstructionAccount::readonly(self.rent_sysvar.address()), 37 | ]; 38 | 39 | // instruction data 40 | // - [0]: instruction discriminator (1 byte, u8) 41 | // - [1..33]: owner (32 bytes, Address) 42 | let mut instruction_data = [UNINIT_BYTE; 33]; 43 | 44 | // Set discriminator as u8 at offset [0] 45 | write_bytes(&mut instruction_data, &[16]); 46 | // Set owner as [u8; 32] at offset [1..33] 47 | write_bytes(&mut instruction_data[1..], self.owner.as_array()); 48 | 49 | let instruction = InstructionView { 50 | program_id: self.token_program, 51 | accounts: &instruction_accounts, 52 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) }, 53 | }; 54 | 55 | invoke(&instruction, &[self.account, self.mint, self.rent_sysvar]) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /programs/token/src/instructions/transfer.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Transfer tokens from one Token account to another. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` Sender account 16 | /// 1. `[WRITE]` Recipient account 17 | /// 2. `[SIGNER]` Authority account 18 | pub struct Transfer<'a> { 19 | /// Sender account. 20 | pub from: &'a AccountView, 21 | /// Recipient account. 22 | pub to: &'a AccountView, 23 | /// Authority account. 24 | pub authority: &'a AccountView, 25 | /// Amount of micro-tokens to transfer. 26 | pub amount: u64, 27 | } 28 | 29 | impl Transfer<'_> { 30 | #[inline(always)] 31 | pub fn invoke(&self) -> ProgramResult { 32 | self.invoke_signed(&[]) 33 | } 34 | 35 | #[inline(always)] 36 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 37 | // Instruction accounts 38 | let instruction_accounts: [InstructionAccount; 3] = [ 39 | InstructionAccount::writable(self.from.address()), 40 | InstructionAccount::writable(self.to.address()), 41 | InstructionAccount::readonly_signer(self.authority.address()), 42 | ]; 43 | 44 | // Instruction data layout: 45 | // - [0]: instruction discriminator (1 byte, u8) 46 | // - [1..9]: amount (8 bytes, u64) 47 | let mut instruction_data = [UNINIT_BYTE; 9]; 48 | 49 | // Set discriminator as u8 at offset [0] 50 | write_bytes(&mut instruction_data, &[3]); 51 | // Set amount as u64 at offset [1..9] 52 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 53 | 54 | let instruction = InstructionView { 55 | program_id: &crate::ID, 56 | accounts: &instruction_accounts, 57 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 58 | }; 59 | 60 | invoke_signed(&instruction, &[self.from, self.to, self.authority], signers) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /programs/token-2022/README.md: -------------------------------------------------------------------------------- 1 |

2 | pinocchio-token-2022 3 |

4 |

5 | pinocchio-token-2022 6 |

7 |

8 | 9 | 10 |

11 | 12 | ## Overview 13 | 14 | This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Token-2022 instructions. 15 | 16 | Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. 17 | 18 | Instruction that are common to both SPL Token and SPL Token-2022 programs expect the program address, so they can be used to invoke either token program. 19 | 20 | This is a `no_std` crate. 21 | 22 | > **Note:** The API defined in this crate is subject to change. 23 | 24 | ## Examples 25 | 26 | Initializing a mint account: 27 | 28 | ```rust 29 | // This example assumes that the instruction receives a writable `mint` 30 | // account; `authority` is an `Address`. 31 | // The SPL Token program is being invoked. 32 | InitializeMint { 33 | mint, 34 | rent_sysvar, 35 | decimals: 9, 36 | mint_authority: authority, 37 | freeze_authority: Some(authority), 38 | token_program: Address::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") 39 | }.invoke()?; 40 | ``` 41 | 42 | Performing a transfer of tokens: 43 | 44 | ```rust 45 | // This example assumes that the instruction receives writable `from` and `to` 46 | // accounts, and a signer `authority` account. 47 | // The SPL Token-2022 is being invoked. 48 | Transfer { 49 | from, 50 | to, 51 | authority, 52 | amount: 10, 53 | token_program: Address::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") 54 | }.invoke()?; 55 | ``` 56 | 57 | ## License 58 | 59 | The code is licensed under the [Apache License Version 2.0](../LICENSE) 60 | -------------------------------------------------------------------------------- /programs/token/src/instructions/approve.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Approves a delegate. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The token account. 16 | /// 1. `[]` The delegate. 17 | /// 2. `[SIGNER]` The source account owner. 18 | pub struct Approve<'a> { 19 | /// Source Account. 20 | pub source: &'a AccountView, 21 | /// Delegate Account 22 | pub delegate: &'a AccountView, 23 | /// Source Owner Account 24 | pub authority: &'a AccountView, 25 | /// Amount 26 | pub amount: u64, 27 | } 28 | 29 | impl Approve<'_> { 30 | #[inline(always)] 31 | pub fn invoke(&self) -> ProgramResult { 32 | self.invoke_signed(&[]) 33 | } 34 | 35 | #[inline(always)] 36 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 37 | // Instruction accounts 38 | let instruction_accounts: [InstructionAccount; 3] = [ 39 | InstructionAccount::writable(self.source.address()), 40 | InstructionAccount::readonly(self.delegate.address()), 41 | InstructionAccount::readonly_signer(self.authority.address()), 42 | ]; 43 | 44 | // Instruction data 45 | // - [0]: instruction discriminator (1 byte, u8) 46 | // - [1..9]: amount (8 bytes, u64) 47 | let mut instruction_data = [UNINIT_BYTE; 9]; 48 | 49 | // Set discriminator as u8 at offset [0] 50 | write_bytes(&mut instruction_data, &[4]); 51 | // Set amount as u64 at offset [1..9] 52 | write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); 53 | 54 | let instruction = InstructionView { 55 | program_id: &crate::ID, 56 | accounts: &instruction_accounts, 57 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 58 | }; 59 | 60 | invoke_signed( 61 | &instruction, 62 | &[self.source, self.delegate, self.authority], 63 | signers, 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /programs/token/src/instructions/mint_to.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Mints new tokens to an account. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The mint. 16 | /// 1. `[WRITE]` The account to mint tokens to. 17 | /// 2. `[SIGNER]` The mint's minting authority. 18 | pub struct MintTo<'a> { 19 | /// Mint Account. 20 | pub mint: &'a AccountView, 21 | /// Token Account. 22 | pub account: &'a AccountView, 23 | /// Mint Authority 24 | pub mint_authority: &'a AccountView, 25 | /// Amount 26 | pub amount: u64, 27 | } 28 | 29 | impl MintTo<'_> { 30 | #[inline(always)] 31 | pub fn invoke(&self) -> ProgramResult { 32 | self.invoke_signed(&[]) 33 | } 34 | 35 | #[inline(always)] 36 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 37 | // Instruction accounts 38 | let instruction_accounts: [InstructionAccount; 3] = [ 39 | InstructionAccount::writable(self.mint.address()), 40 | InstructionAccount::writable(self.account.address()), 41 | InstructionAccount::readonly_signer(self.mint_authority.address()), 42 | ]; 43 | 44 | // Instruction data layout: 45 | // - [0]: instruction discriminator (1 byte, u8) 46 | // - [1..9]: amount (8 bytes, u64) 47 | let mut instruction_data = [UNINIT_BYTE; 9]; 48 | 49 | // Set discriminator as u8 at offset [0] 50 | write_bytes(&mut instruction_data, &[7]); 51 | // Set amount as u64 at offset [1..9] 52 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 53 | 54 | let instruction = InstructionView { 55 | program_id: &crate::ID, 56 | accounts: &instruction_accounts, 57 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 58 | }; 59 | 60 | invoke_signed( 61 | &instruction, 62 | &[self.mint, self.account, self.mint_authority], 63 | signers, 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /programs/token/src/instructions/burn.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Burns tokens by removing them from an account. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The account to burn from. 16 | /// 1. `[WRITE]` The token mint. 17 | /// 2. `[SIGNER]` The account's owner/delegate. 18 | pub struct Burn<'a> { 19 | /// Source of the Burn Account 20 | pub account: &'a AccountView, 21 | /// Mint Account 22 | pub mint: &'a AccountView, 23 | /// Owner of the Token Account 24 | pub authority: &'a AccountView, 25 | /// Amount 26 | pub amount: u64, 27 | } 28 | 29 | impl Burn<'_> { 30 | #[inline(always)] 31 | pub fn invoke(&self) -> ProgramResult { 32 | self.invoke_signed(&[]) 33 | } 34 | 35 | #[inline(always)] 36 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 37 | // Instruction accounts 38 | let instruction_accounts: [InstructionAccount; 3] = [ 39 | InstructionAccount::writable(self.account.address()), 40 | InstructionAccount::writable(self.mint.address()), 41 | InstructionAccount::readonly_signer(self.authority.address()), 42 | ]; 43 | 44 | // Instruction data 45 | // - [0]: instruction discriminator (1 byte, u8) 46 | // - [1..9]: amount (8 bytes, u64) 47 | let mut instruction_data = [UNINIT_BYTE; 9]; 48 | 49 | // Set discriminator as u8 at offset [0] 50 | write_bytes(&mut instruction_data, &[8]); 51 | // Set amount as u64 at offset [1..9] 52 | write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); 53 | 54 | let instruction = InstructionView { 55 | program_id: &crate::ID, 56 | accounts: &instruction_accounts, 57 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 58 | }; 59 | 60 | invoke_signed( 61 | &instruction, 62 | &[self.account, self.mint, self.authority], 63 | signers, 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | env: 9 | CACHE: true 10 | 11 | jobs: 12 | sanity: 13 | name: Process Workspace 14 | runs-on: ubuntu-latest 15 | outputs: 16 | members: ${{ steps.filter.outputs.members }} 17 | steps: 18 | - name: Git checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Environment 22 | uses: ./.github/actions/setup 23 | with: 24 | cargo-cache-key: cargo-audit 25 | toolchain: lint 26 | components: audit, miri 27 | 28 | - name: cargo-audit 29 | run: pnpm cargo-audit 30 | 31 | - name: cargo-miri 32 | run: pnpm miri 33 | 34 | - name: Filter members 35 | id: filter 36 | run: pnpm tsx ./scripts/setup/members.mts 37 | 38 | spellcheck: 39 | name: Spellcheck 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Git Checkout 43 | uses: actions/checkout@v4 44 | 45 | - name: Setup Environment 46 | uses: ./.github/actions/setup 47 | with: 48 | cargo-cache-key: cargo-spellcheck 49 | components: spellcheck 50 | 51 | - name: cargo-spellcheck 52 | run: pnpm spellcheck 53 | 54 | process: 55 | name: Check 56 | needs: sanity 57 | runs-on: ubuntu-latest 58 | strategy: 59 | matrix: 60 | member: ${{ fromJson(needs.sanity.outputs.members) }} 61 | steps: 62 | - name: Git Checkout 63 | uses: actions/checkout@v4 64 | 65 | - name: Setup Environment 66 | uses: ./.github/actions/setup 67 | with: 68 | cargo-cache-key: cargo-${{ matrix.member }} 69 | toolchain: build, format, lint, test 70 | components: hack 71 | solana: true 72 | 73 | - name: fmt 74 | run: pnpm format ${{ matrix.member }} 75 | 76 | - name: clippy 77 | run: pnpm clippy ${{ matrix.member }} 78 | 79 | - name: cargo-doc 80 | run: pnpm doc ${{ matrix.member }} 81 | 82 | - name: cargo-hack 83 | run: pnpm hack ${{ matrix.member }} 84 | 85 | - name: build-sbf 86 | run: pnpm build-sbf ${{ matrix.member }} 87 | 88 | - name: test 89 | run: pnpm test ${{ matrix.member }} 90 | -------------------------------------------------------------------------------- /programs/system/src/instructions/initialize_nonce_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::invoke, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value. 8 | /// 9 | /// The [`Address`] parameter specifies the entity authorized to execute nonce 10 | /// instruction on the account 11 | /// 12 | /// No signatures are required to execute this instruction, enabling derived 13 | /// nonce account addresses. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` Nonce account 17 | /// 1. `[]` Recent blockhashes sysvar 18 | /// 2. `[]` Rent sysvar 19 | pub struct InitializeNonceAccount<'a, 'b> { 20 | /// Nonce account. 21 | pub account: &'a AccountView, 22 | 23 | /// Recent blockhashes sysvar. 24 | pub recent_blockhashes_sysvar: &'a AccountView, 25 | 26 | /// Rent sysvar. 27 | pub rent_sysvar: &'a AccountView, 28 | 29 | /// Indicates the entity authorized to execute nonce 30 | /// instruction on the account 31 | pub authority: &'b Address, 32 | } 33 | 34 | impl InitializeNonceAccount<'_, '_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | // Instruction accounts 38 | let instruction_accounts: [InstructionAccount; 3] = [ 39 | InstructionAccount::writable(self.account.address()), 40 | InstructionAccount::readonly(self.recent_blockhashes_sysvar.address()), 41 | InstructionAccount::readonly(self.rent_sysvar.address()), 42 | ]; 43 | 44 | // instruction data 45 | // - [0..4 ]: instruction discriminator 46 | // - [4..36]: authority address 47 | let mut instruction_data = [0; 36]; 48 | instruction_data[0] = 6; 49 | instruction_data[4..36].copy_from_slice(self.authority.as_array()); 50 | 51 | let instruction = InstructionView { 52 | program_id: &crate::ID, 53 | accounts: &instruction_accounts, 54 | data: &instruction_data, 55 | }; 56 | 57 | invoke( 58 | &instruction, 59 | &[ 60 | self.account, 61 | self.recent_blockhashes_sysvar, 62 | self.rent_sysvar, 63 | ], 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /programs/memo/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed_with_bounds, Signer, MAX_STATIC_CPI_ACCOUNTS}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::{ProgramError, ProgramResult}; 9 | 10 | /// Memo instruction. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `..+N` `[SIGNER]` N signing accounts 14 | pub struct Memo<'a, 'b, 'c> { 15 | /// Signing accounts 16 | pub signers: &'b [&'a AccountView], 17 | /// Memo 18 | pub memo: &'c str, 19 | } 20 | 21 | impl Memo<'_, '_, '_> { 22 | #[inline(always)] 23 | pub fn invoke(&self) -> ProgramResult { 24 | self.invoke_signed(&[]) 25 | } 26 | 27 | #[inline(always)] 28 | pub fn invoke_signed(&self, signers_seeds: &[Signer]) -> ProgramResult { 29 | const UNINIT_INSTRUCTION_ACCOUNT: MaybeUninit = 30 | MaybeUninit::::uninit(); 31 | 32 | // We don't know num_accounts at compile time, so we use 33 | // `MAX_STATIC_CPI_ACCOUNTS`. 34 | let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; MAX_STATIC_CPI_ACCOUNTS]; 35 | 36 | let num_accounts = self.signers.len(); 37 | if num_accounts > MAX_STATIC_CPI_ACCOUNTS { 38 | return Err(ProgramError::InvalidArgument); 39 | } 40 | 41 | for i in 0..num_accounts { 42 | unsafe { 43 | // SAFETY: `num_accounts` is less than MAX_STATIC_CPI_ACCOUNTS. 44 | instruction_accounts.get_unchecked_mut(i).write( 45 | InstructionAccount::readonly_signer(self.signers.get_unchecked(i).address()), 46 | ); 47 | } 48 | } 49 | 50 | // SAFETY: len(instruction_accounts) <= MAX_CPI_ACCOUNTS 51 | let instruction = InstructionView { 52 | program_id: &crate::ID, 53 | accounts: unsafe { 54 | core::slice::from_raw_parts(instruction_accounts.as_ptr() as _, num_accounts) 55 | }, 56 | data: self.memo.as_bytes(), 57 | }; 58 | 59 | invoke_signed_with_bounds::( 60 | &instruction, 61 | self.signers, 62 | signers_seeds, 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/transfer.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Transfer Tokens from one Token Account to another. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` Sender account 17 | /// 1. `[WRITE]` Recipient account 18 | /// 2. `[SIGNER]` Authority account 19 | pub struct Transfer<'a, 'b> { 20 | /// Sender account. 21 | pub from: &'a AccountView, 22 | /// Recipient account. 23 | pub to: &'a AccountView, 24 | /// Authority account. 25 | pub authority: &'a AccountView, 26 | /// Amount of micro-tokens to transfer. 27 | pub amount: u64, 28 | /// Token Program 29 | pub token_program: &'b Address, 30 | } 31 | 32 | impl Transfer<'_, '_> { 33 | #[inline(always)] 34 | pub fn invoke(&self) -> ProgramResult { 35 | self.invoke_signed(&[]) 36 | } 37 | 38 | #[inline(always)] 39 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 40 | // Instruction accounts 41 | let instruction_accounts: [InstructionAccount; 3] = [ 42 | InstructionAccount::writable(self.from.address()), 43 | InstructionAccount::writable(self.to.address()), 44 | InstructionAccount::readonly_signer(self.authority.address()), 45 | ]; 46 | 47 | // Instruction data layout: 48 | // - [0]: instruction discriminator (1 byte, u8) 49 | // - [1..9]: amount (8 bytes, u64) 50 | let mut instruction_data = [UNINIT_BYTE; 9]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[3]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 56 | 57 | let instruction = InstructionView { 58 | program_id: self.token_program, 59 | accounts: &instruction_accounts, 60 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 61 | }; 62 | 63 | invoke_signed(&instruction, &[self.from, self.to, self.authority], signers) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/approve.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Approves a delegate. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The token account. 17 | /// 1. `[]` The delegate. 18 | /// 2. `[SIGNER]` The source account owner. 19 | pub struct Approve<'a, 'b> { 20 | /// Source Account. 21 | pub source: &'a AccountView, 22 | /// Delegate Account 23 | pub delegate: &'a AccountView, 24 | /// Source Owner Account 25 | pub authority: &'a AccountView, 26 | /// Amount 27 | pub amount: u64, 28 | /// Token Program 29 | pub token_program: &'b Address, 30 | } 31 | 32 | impl Approve<'_, '_> { 33 | #[inline(always)] 34 | pub fn invoke(&self) -> ProgramResult { 35 | self.invoke_signed(&[]) 36 | } 37 | 38 | #[inline(always)] 39 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 40 | // Instruction accounts 41 | let instruction_accounts: [InstructionAccount; 3] = [ 42 | InstructionAccount::writable(self.source.address()), 43 | InstructionAccount::readonly(self.delegate.address()), 44 | InstructionAccount::readonly_signer(self.authority.address()), 45 | ]; 46 | 47 | // Instruction data 48 | // - [0]: instruction discriminator (1 byte, u8) 49 | // - [1..9]: amount (8 bytes, u64) 50 | let mut instruction_data = [UNINIT_BYTE; 9]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[4]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); 56 | 57 | let instruction = InstructionView { 58 | program_id: self.token_program, 59 | accounts: &instruction_accounts, 60 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 61 | }; 62 | 63 | invoke_signed( 64 | &instruction, 65 | &[self.source, self.delegate, self.authority], 66 | signers, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/mint_to.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Mints new tokens to an account. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The mint. 17 | /// 1. `[WRITE]` The account to mint tokens to. 18 | /// 2. `[SIGNER]` The mint's minting authority. 19 | pub struct MintTo<'a, 'b> { 20 | /// Mint Account. 21 | pub mint: &'a AccountView, 22 | /// Token Account. 23 | pub account: &'a AccountView, 24 | /// Mint Authority 25 | pub mint_authority: &'a AccountView, 26 | /// Amount 27 | pub amount: u64, 28 | /// Token Program 29 | pub token_program: &'b Address, 30 | } 31 | 32 | impl MintTo<'_, '_> { 33 | #[inline(always)] 34 | pub fn invoke(&self) -> ProgramResult { 35 | self.invoke_signed(&[]) 36 | } 37 | 38 | #[inline(always)] 39 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 40 | // Instruction accounts 41 | let instruction_accounts: [InstructionAccount; 3] = [ 42 | InstructionAccount::writable(self.mint.address()), 43 | InstructionAccount::writable(self.account.address()), 44 | InstructionAccount::readonly_signer(self.mint_authority.address()), 45 | ]; 46 | 47 | // Instruction data layout: 48 | // - [0]: instruction discriminator (1 byte, u8) 49 | // - [1..9]: amount (8 bytes, u64) 50 | let mut instruction_data = [UNINIT_BYTE; 9]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[7]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 56 | 57 | let instruction = InstructionView { 58 | program_id: self.token_program, 59 | accounts: &instruction_accounts, 60 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 61 | }; 62 | 63 | invoke_signed( 64 | &instruction, 65 | &[self.mint, self.account, self.mint_authority], 66 | signers, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/burn.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Burns tokens by removing them from an account. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The account to burn from. 17 | /// 1. `[WRITE]` The token mint. 18 | /// 2. `[SIGNER]` The account's owner/delegate. 19 | pub struct Burn<'a, 'b> { 20 | /// Source of the Burn Account 21 | pub account: &'a AccountView, 22 | /// Mint Account 23 | pub mint: &'a AccountView, 24 | /// Owner of the Token Account 25 | pub authority: &'a AccountView, 26 | /// Amount 27 | pub amount: u64, 28 | /// Token Program 29 | pub token_program: &'b Address, 30 | } 31 | 32 | impl Burn<'_, '_> { 33 | #[inline(always)] 34 | pub fn invoke(&self) -> ProgramResult { 35 | self.invoke_signed(&[]) 36 | } 37 | 38 | #[inline(always)] 39 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 40 | // Instruction accounts 41 | let instruction_accounts: [InstructionAccount; 3] = [ 42 | InstructionAccount::writable(self.account.address()), 43 | InstructionAccount::writable(self.mint.address()), 44 | InstructionAccount::readonly_signer(self.authority.address()), 45 | ]; 46 | 47 | // Instruction data 48 | // - [0]: instruction discriminator (1 byte, u8) 49 | // - [1..9]: amount (8 bytes, u64) 50 | let mut instruction_data = [UNINIT_BYTE; 9]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[8]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes()); 56 | 57 | let instruction = InstructionView { 58 | program_id: self.token_program, 59 | accounts: &instruction_accounts, 60 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }, 61 | }; 62 | 63 | invoke_signed( 64 | &instruction, 65 | &[self.account, self.mint, self.authority], 66 | signers, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /programs/token/src/instructions/mint_to_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Mints new tokens to an account. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The mint. 16 | /// 1. `[WRITE]` The account to mint tokens to. 17 | /// 2. `[SIGNER]` The mint's minting authority. 18 | pub struct MintToChecked<'a> { 19 | /// Mint Account. 20 | pub mint: &'a AccountView, 21 | /// Token Account. 22 | pub account: &'a AccountView, 23 | /// Mint Authority 24 | pub mint_authority: &'a AccountView, 25 | /// Amount 26 | pub amount: u64, 27 | /// Decimals 28 | pub decimals: u8, 29 | } 30 | 31 | impl MintToChecked<'_> { 32 | #[inline(always)] 33 | pub fn invoke(&self) -> ProgramResult { 34 | self.invoke_signed(&[]) 35 | } 36 | 37 | #[inline(always)] 38 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 39 | // Instruction accounts 40 | let instruction_accounts: [InstructionAccount; 3] = [ 41 | InstructionAccount::writable(self.mint.address()), 42 | InstructionAccount::writable(self.account.address()), 43 | InstructionAccount::readonly_signer(self.mint_authority.address()), 44 | ]; 45 | 46 | // Instruction data layout: 47 | // - [0]: instruction discriminator (1 byte, u8) 48 | // - [1..9]: amount (8 bytes, u64) 49 | // - [9]: decimals (1 byte, u8) 50 | let mut instruction_data = [UNINIT_BYTE; 10]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[14]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 56 | // Set decimals as u8 at offset [9] 57 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 58 | 59 | let instruction = InstructionView { 60 | program_id: &crate::ID, 61 | accounts: &instruction_accounts, 62 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 63 | }; 64 | 65 | invoke_signed( 66 | &instruction, 67 | &[self.mint, self.account, self.mint_authority], 68 | signers, 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /programs/token/src/instructions/burn_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Burns tokens by removing them from an account. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The account to burn from. 16 | /// 1. `[WRITE]` The token mint. 17 | /// 2. `[SIGNER]` The account's owner/delegate. 18 | pub struct BurnChecked<'a> { 19 | /// Source of the Burn Account 20 | pub account: &'a AccountView, 21 | /// Mint Account 22 | pub mint: &'a AccountView, 23 | /// Owner of the Token Account 24 | pub authority: &'a AccountView, 25 | /// Amount 26 | pub amount: u64, 27 | /// Decimals 28 | pub decimals: u8, 29 | } 30 | 31 | impl BurnChecked<'_> { 32 | #[inline(always)] 33 | pub fn invoke(&self) -> ProgramResult { 34 | self.invoke_signed(&[]) 35 | } 36 | 37 | #[inline(always)] 38 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 39 | // Instruction accounts 40 | let instruction_accounts: [InstructionAccount; 3] = [ 41 | InstructionAccount::writable(self.account.address()), 42 | InstructionAccount::writable(self.mint.address()), 43 | InstructionAccount::readonly_signer(self.authority.address()), 44 | ]; 45 | 46 | // Instruction data 47 | // - [0]: instruction discriminator (1 byte, u8) 48 | // - [1..9]: amount (8 bytes, u64) 49 | // - [9]: decimals (1 byte, u8) 50 | let mut instruction_data = [UNINIT_BYTE; 10]; 51 | 52 | // Set discriminator as u8 at offset [0] 53 | write_bytes(&mut instruction_data, &[15]); 54 | // Set amount as u64 at offset [1..9] 55 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 56 | // Set decimals as u8 at offset [9] 57 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 58 | 59 | let instruction = InstructionView { 60 | program_id: &crate::ID, 61 | accounts: &instruction_accounts, 62 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 63 | }; 64 | 65 | invoke_signed( 66 | &instruction, 67 | &[self.account, self.mint, self.authority], 68 | signers, 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /programs/associated-token-account/README.md: -------------------------------------------------------------------------------- 1 |

2 | pinocchio-associated-token-account 3 |

4 |

5 | pinocchio-associated-token-account 6 |

7 |

8 | 9 | 10 |

11 | 12 | ## Overview 13 | 14 | This crate contains [`pinocchio`](https://crates.io/crates/pinocchio) helpers to perform cross-program invocations (CPIs) for SPL Associated Token Account program instructions. 15 | 16 | Each instruction defines a `struct` with the accounts and parameters required. Once all values are set, you can call directly `invoke` or `invoke_signed` to perform the CPI. 17 | 18 | This is a `no_std` crate. 19 | 20 | > **Note:** The API defined in this crate is subject to change. 21 | 22 | ## Getting Started 23 | 24 | From your project folder: 25 | 26 | ```bash 27 | cargo add pinocchio-associated-token-account 28 | ``` 29 | 30 | This will add the `pinocchio-associated-token-account` dependency to your `Cargo.toml` file. 31 | 32 | ## Examples 33 | 34 | Creating an associated token account: 35 | ```rust 36 | // Those examples assume that each instruction receives writable and signer `funding_account` account, 37 | // writable `account` account, and `wallet`, `mint`, `system_program`, `token_program` accounts. 38 | Create { 39 | funding_account, 40 | account, 41 | wallet, 42 | mint, 43 | system_program, 44 | token_program, 45 | }.invoke()?; 46 | 47 | CreateIdempotent { 48 | funding_account, 49 | account, 50 | wallet, 51 | mint, 52 | system_program, 53 | token_program, 54 | }.invoke()?; 55 | ``` 56 | 57 | Recovering Nested 58 | ```rust 59 | // This example assumes that instruction receives writable and signer `wallet` account, 60 | // writable `account` and `destination_account`, and `mint`, `owner_account`, `owner_mint`, 61 | // `token_program` accounts. 62 | RecoverNested { 63 | account, 64 | mint, 65 | destination_account, 66 | owner_account, 67 | owner_mint, 68 | wallet, 69 | token_program, 70 | }.invoke()?; 71 | ``` 72 | 73 | ## License 74 | 75 | The code is licensed under the [Apache License Version 2.0](../LICENSE) 76 | -------------------------------------------------------------------------------- /programs/system/src/instructions/assign_with_seed.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Assign account to a program based on a seed. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE]` Assigned account 11 | /// 1. `[SIGNER]` Base account 12 | pub struct AssignWithSeed<'a, 'b, 'c> { 13 | /// Allocated account. 14 | pub account: &'a AccountView, 15 | 16 | /// Base account. 17 | /// 18 | /// The account matching the base `Address` below must be provided as 19 | /// a signer, but may be the same as the funding account and provided 20 | /// as account 0. 21 | pub base: &'a AccountView, 22 | 23 | /// String of ASCII chars, no longer than [`MAX_SEED_LEN`](https://docs.rs/solana-address/latest/solana_address/constant.MAX_SEED_LEN.html). 24 | pub seed: &'b str, 25 | 26 | /// Address of program that will own the new account. 27 | pub owner: &'c Address, 28 | } 29 | 30 | impl AssignWithSeed<'_, '_, '_> { 31 | #[inline(always)] 32 | pub fn invoke(&self) -> ProgramResult { 33 | self.invoke_signed(&[]) 34 | } 35 | 36 | #[inline(always)] 37 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 38 | // Instruction accounts 39 | let instruction_accounts: [InstructionAccount; 2] = [ 40 | InstructionAccount::writable(self.account.address()), 41 | InstructionAccount::readonly_signer(self.base.address()), 42 | ]; 43 | 44 | // instruction data 45 | // - [0..4 ]: instruction discriminator 46 | // - [4..36 ]: base address 47 | // - [36..44]: seed length 48 | // - [44.. ]: seed (max 32) 49 | // - [.. +32]: owner address 50 | let mut instruction_data = [0; 104]; 51 | instruction_data[0] = 10; 52 | instruction_data[4..36].copy_from_slice(self.base.address().as_array()); 53 | instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); 54 | 55 | let offset = 44 + self.seed.len(); 56 | instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); 57 | instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); 58 | 59 | let instruction = InstructionView { 60 | program_id: &crate::ID, 61 | accounts: &instruction_accounts, 62 | data: &instruction_data[..offset + 32], 63 | }; 64 | 65 | invoke_signed(&instruction, &[self.account, self.base], signers) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/mint_to_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Mints new tokens to an account. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The mint. 17 | /// 1. `[WRITE]` The account to mint tokens to. 18 | /// 2. `[SIGNER]` The mint's minting authority. 19 | pub struct MintToChecked<'a, 'b> { 20 | /// Mint Account. 21 | pub mint: &'a AccountView, 22 | /// Token Account. 23 | pub account: &'a AccountView, 24 | /// Mint Authority 25 | pub mint_authority: &'a AccountView, 26 | /// Amount 27 | pub amount: u64, 28 | /// Decimals 29 | pub decimals: u8, 30 | /// Token Program 31 | pub token_program: &'b Address, 32 | } 33 | 34 | impl MintToChecked<'_, '_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 3] = [ 44 | InstructionAccount::writable(self.mint.address()), 45 | InstructionAccount::writable(self.account.address()), 46 | InstructionAccount::readonly_signer(self.mint_authority.address()), 47 | ]; 48 | 49 | // Instruction data layout: 50 | // - [0]: instruction discriminator (1 byte, u8) 51 | // - [1..9]: amount (8 bytes, u64) 52 | // - [9]: decimals (1 byte, u8) 53 | let mut instruction_data = [UNINIT_BYTE; 10]; 54 | 55 | // Set discriminator as u8 at offset [0] 56 | write_bytes(&mut instruction_data, &[14]); 57 | // Set amount as u64 at offset [1..9] 58 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 59 | // Set decimals as u8 at offset [9] 60 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 61 | 62 | let instruction = InstructionView { 63 | program_id: self.token_program, 64 | accounts: &instruction_accounts, 65 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 66 | }; 67 | 68 | invoke_signed( 69 | &instruction, 70 | &[self.mint, self.account, self.mint_authority], 71 | signers, 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/burn_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Burns tokens by removing them from an account. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The account to burn from. 17 | /// 1. `[WRITE]` The token mint. 18 | /// 2. `[SIGNER]` The account's owner/delegate. 19 | pub struct BurnChecked<'a, 'b> { 20 | /// Source of the Burn Account 21 | pub account: &'a AccountView, 22 | /// Mint Account 23 | pub mint: &'a AccountView, 24 | /// Owner of the Token Account 25 | pub authority: &'a AccountView, 26 | /// Amount 27 | pub amount: u64, 28 | /// Decimals 29 | pub decimals: u8, 30 | /// Token Program 31 | pub token_program: &'b Address, 32 | } 33 | 34 | impl BurnChecked<'_, '_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 3] = [ 44 | InstructionAccount::writable(self.account.address()), 45 | InstructionAccount::writable(self.mint.address()), 46 | InstructionAccount::readonly_signer(self.authority.address()), 47 | ]; 48 | 49 | // Instruction data 50 | // - [0]: instruction discriminator (1 byte, u8) 51 | // - [1..9]: amount (8 bytes, u64) 52 | // - [9]: decimals (1 byte, u8) 53 | let mut instruction_data = [UNINIT_BYTE; 10]; 54 | 55 | // Set discriminator as u8 at offset [0] 56 | write_bytes(&mut instruction_data, &[15]); 57 | // Set amount as u64 at offset [1..9] 58 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 59 | // Set decimals as u8 at offset [9] 60 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 61 | 62 | let instruction = InstructionView { 63 | program_id: self.token_program, 64 | accounts: &instruction_accounts, 65 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 66 | }; 67 | 68 | invoke_signed( 69 | &instruction, 70 | &[self.account, self.mint, self.authority], 71 | signers, 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /programs/token/src/instructions/approve_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Approves a delegate. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The source account. 16 | /// 1. `[]` The token mint. 17 | /// 2. `[]` The delegate. 18 | /// 3. `[SIGNER]` The source account owner. 19 | pub struct ApproveChecked<'a> { 20 | /// Source Account. 21 | pub source: &'a AccountView, 22 | /// Mint Account. 23 | pub mint: &'a AccountView, 24 | /// Delegate Account. 25 | pub delegate: &'a AccountView, 26 | /// Source Owner Account. 27 | pub authority: &'a AccountView, 28 | /// Amount. 29 | pub amount: u64, 30 | /// Decimals. 31 | pub decimals: u8, 32 | } 33 | 34 | impl ApproveChecked<'_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 4] = [ 44 | InstructionAccount::writable(self.source.address()), 45 | InstructionAccount::readonly(self.mint.address()), 46 | InstructionAccount::readonly(self.delegate.address()), 47 | InstructionAccount::readonly_signer(self.authority.address()), 48 | ]; 49 | 50 | // Instruction data 51 | // - [0] : instruction discriminator (1 byte, u8) 52 | // - [1..9]: amount (8 bytes, u64) 53 | // - [9] : decimals (1 byte, u8) 54 | let mut instruction_data = [UNINIT_BYTE; 10]; 55 | 56 | // Set discriminator as u8 at offset [0] 57 | write_bytes(&mut instruction_data, &[13]); 58 | // Set amount as u64 at offset [1..9] 59 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 60 | // Set decimals as u8 at offset [9] 61 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 62 | 63 | let instruction = InstructionView { 64 | program_id: &crate::ID, 65 | accounts: &instruction_accounts, 66 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 67 | }; 68 | 69 | invoke_signed( 70 | &instruction, 71 | &[self.source, self.mint, self.delegate, self.authority], 72 | signers, 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_mint_2.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new mint. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITABLE]` Mint account 14 | pub struct InitializeMint2<'a> { 15 | /// Mint Account. 16 | pub mint: &'a AccountView, 17 | /// Decimals. 18 | pub decimals: u8, 19 | /// Mint Authority. 20 | pub mint_authority: &'a Address, 21 | /// Freeze Authority. 22 | pub freeze_authority: Option<&'a Address>, 23 | } 24 | 25 | impl InitializeMint2<'_> { 26 | #[inline(always)] 27 | pub fn invoke(&self) -> ProgramResult { 28 | // Instruction accounts 29 | let instruction_accounts: [InstructionAccount; 1] = 30 | [InstructionAccount::writable(self.mint.address())]; 31 | 32 | // Instruction data layout: 33 | // - [0]: instruction discriminator (1 byte, u8) 34 | // - [1]: decimals (1 byte, u8) 35 | // - [2..34]: mint_authority (32 bytes, Address) 36 | // - [34]: freeze_authority presence flag (1 byte, u8) 37 | // - [35..67]: freeze_authority (optional, 32 bytes, Address) 38 | let mut instruction_data = [UNINIT_BYTE; 67]; 39 | let mut length = instruction_data.len(); 40 | 41 | // Set discriminator as u8 at offset [0] 42 | write_bytes(&mut instruction_data, &[20]); 43 | // Set decimals as u8 at offset [1] 44 | write_bytes(&mut instruction_data[1..2], &[self.decimals]); 45 | // Set mint_authority as Address at offset [2..34] 46 | write_bytes(&mut instruction_data[2..34], self.mint_authority.as_array()); 47 | 48 | if let Some(freeze_auth) = self.freeze_authority { 49 | // Set Option = `true` & freeze_authority at offset [34..67] 50 | write_bytes(&mut instruction_data[34..35], &[1]); 51 | write_bytes(&mut instruction_data[35..], freeze_auth.as_array()); 52 | } else { 53 | // Set Option = `false` 54 | write_bytes(&mut instruction_data[34..35], &[0]); 55 | // Adjust length if no freeze authority 56 | length = 35; 57 | } 58 | 59 | let instruction = InstructionView { 60 | program_id: &crate::ID, 61 | accounts: &instruction_accounts, 62 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 63 | }; 64 | 65 | invoke(&instruction, &[self.mint]) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /programs/token/src/instructions/transfer_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{ 5 | cpi::{invoke_signed, Signer}, 6 | InstructionAccount, InstructionView, 7 | }; 8 | use solana_program_error::ProgramResult; 9 | 10 | use crate::{write_bytes, UNINIT_BYTE}; 11 | 12 | /// Transfer Tokens from one Token Account to another. 13 | /// 14 | /// ### Accounts: 15 | /// 0. `[WRITE]` The source account. 16 | /// 1. `[]` The token mint. 17 | /// 2. `[WRITE]` The destination account. 18 | /// 3. `[SIGNER]` The source account's owner/delegate. 19 | pub struct TransferChecked<'a> { 20 | /// Sender account. 21 | pub from: &'a AccountView, 22 | /// Mint Account 23 | pub mint: &'a AccountView, 24 | /// Recipient account. 25 | pub to: &'a AccountView, 26 | /// Authority account. 27 | pub authority: &'a AccountView, 28 | /// Amount of micro-tokens to transfer. 29 | pub amount: u64, 30 | /// Decimal for the Token 31 | pub decimals: u8, 32 | } 33 | 34 | impl TransferChecked<'_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 4] = [ 44 | InstructionAccount::writable(self.from.address()), 45 | InstructionAccount::readonly(self.mint.address()), 46 | InstructionAccount::writable(self.to.address()), 47 | InstructionAccount::readonly_signer(self.authority.address()), 48 | ]; 49 | 50 | // Instruction data layout: 51 | // - [0]: instruction discriminator (1 byte, u8) 52 | // - [1..9]: amount (8 bytes, u64) 53 | // - [9]: decimals (1 byte, u8) 54 | let mut instruction_data = [UNINIT_BYTE; 10]; 55 | 56 | // Set discriminator as u8 at offset [0] 57 | write_bytes(&mut instruction_data, &[12]); 58 | // Set amount as u64 at offset [1..9] 59 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 60 | // Set decimals as u8 at offset [9] 61 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 62 | 63 | let instruction = InstructionView { 64 | program_id: &crate::ID, 65 | accounts: &instruction_accounts, 66 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 67 | }; 68 | 69 | invoke_signed( 70 | &instruction, 71 | &[self.from, self.mint, self.to, self.authority], 72 | signers, 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_mint_2.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new mint. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITABLE]` Mint account 14 | pub struct InitializeMint2<'a, 'b> { 15 | /// Mint Account. 16 | pub mint: &'a AccountView, 17 | /// Decimals. 18 | pub decimals: u8, 19 | /// Mint Authority. 20 | pub mint_authority: &'a Address, 21 | /// Freeze Authority. 22 | pub freeze_authority: Option<&'a Address>, 23 | /// Token Program 24 | pub token_program: &'b Address, 25 | } 26 | 27 | impl InitializeMint2<'_, '_> { 28 | #[inline(always)] 29 | pub fn invoke(&self) -> ProgramResult { 30 | // Instruction accounts 31 | let instruction_accounts: [InstructionAccount; 1] = 32 | [InstructionAccount::writable(self.mint.address())]; 33 | 34 | // Instruction data layout: 35 | // - [0]: instruction discriminator (1 byte, u8) 36 | // - [1]: decimals (1 byte, u8) 37 | // - [2..34]: mint_authority (32 bytes, Address) 38 | // - [34]: freeze_authority presence flag (1 byte, u8) 39 | // - [35..67]: freeze_authority (optional, 32 bytes, Address) 40 | let mut instruction_data = [UNINIT_BYTE; 67]; 41 | let mut length = instruction_data.len(); 42 | 43 | // Set discriminator as u8 at offset [0] 44 | write_bytes(&mut instruction_data, &[20]); 45 | // Set decimals as u8 at offset [1] 46 | write_bytes(&mut instruction_data[1..2], &[self.decimals]); 47 | // Set mint_authority at offset [2..34] 48 | write_bytes(&mut instruction_data[2..34], self.mint_authority.as_array()); 49 | 50 | if let Some(freeze_auth) = self.freeze_authority { 51 | // Set Option = `true` & freeze_authority at offset [34..67] 52 | write_bytes(&mut instruction_data[34..35], &[1]); 53 | write_bytes(&mut instruction_data[35..], freeze_auth.as_array()); 54 | } else { 55 | // Set Option = `false` 56 | write_bytes(&mut instruction_data[34..35], &[0]); 57 | // Adjust length if no freeze authority 58 | length = 35; 59 | } 60 | 61 | let instruction = InstructionView { 62 | program_id: self.token_program, 63 | accounts: &instruction_accounts, 64 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 65 | }; 66 | 67 | invoke(&instruction, &[self.mint]) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/approve_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Approves a delegate. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The source account. 17 | /// 1. `[]` The token mint. 18 | /// 2. `[]` The delegate. 19 | /// 3. `[SIGNER]` The source account owner. 20 | pub struct ApproveChecked<'a, 'b> { 21 | /// Source Account. 22 | pub source: &'a AccountView, 23 | /// Mint Account. 24 | pub mint: &'a AccountView, 25 | /// Delegate Account. 26 | pub delegate: &'a AccountView, 27 | /// Source Owner Account. 28 | pub authority: &'a AccountView, 29 | /// Amount. 30 | pub amount: u64, 31 | /// Decimals. 32 | pub decimals: u8, 33 | /// Token Program 34 | pub token_program: &'b Address, 35 | } 36 | 37 | impl ApproveChecked<'_, '_> { 38 | #[inline(always)] 39 | pub fn invoke(&self) -> ProgramResult { 40 | self.invoke_signed(&[]) 41 | } 42 | 43 | #[inline(always)] 44 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 45 | // Instruction accounts 46 | let instruction_accounts: [InstructionAccount; 4] = [ 47 | InstructionAccount::writable(self.source.address()), 48 | InstructionAccount::readonly(self.mint.address()), 49 | InstructionAccount::readonly(self.delegate.address()), 50 | InstructionAccount::readonly_signer(self.authority.address()), 51 | ]; 52 | 53 | // Instruction data 54 | // - [0] : instruction discriminator (1 byte, u8) 55 | // - [1..9]: amount (8 bytes, u64) 56 | // - [9] : decimals (1 byte, u8) 57 | let mut instruction_data = [UNINIT_BYTE; 10]; 58 | 59 | // Set discriminator as u8 at offset [0] 60 | write_bytes(&mut instruction_data, &[13]); 61 | // Set amount as u64 at offset [1..9] 62 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 63 | // Set decimals as u8 at offset [9] 64 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 65 | 66 | let instruction = InstructionView { 67 | program_id: self.token_program, 68 | accounts: &instruction_accounts, 69 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 70 | }; 71 | 72 | invoke_signed( 73 | &instruction, 74 | &[self.source, self.mint, self.delegate, self.authority], 75 | signers, 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /programs/system/src/instructions/create_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | error::ProgramError, 4 | instruction::{InstructionAccount, InstructionView}, 5 | sysvars::rent::Rent, 6 | AccountView, Address, ProgramResult, 7 | }; 8 | 9 | /// Create a new account. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE, SIGNER]` Funding account 13 | /// 1. `[WRITE, SIGNER]` New account 14 | pub struct CreateAccount<'a> { 15 | /// Funding account. 16 | pub from: &'a AccountView, 17 | 18 | /// New account. 19 | pub to: &'a AccountView, 20 | 21 | /// Number of lamports to transfer to the new account. 22 | pub lamports: u64, 23 | 24 | /// Number of bytes of memory to allocate. 25 | pub space: u64, 26 | 27 | /// Address of program that will own the new account. 28 | pub owner: &'a Address, 29 | } 30 | 31 | impl<'a> CreateAccount<'a> { 32 | #[inline(always)] 33 | pub fn with_minimal_balance( 34 | from: &'a AccountView, 35 | to: &'a AccountView, 36 | rent_sysvar: &'a AccountView, 37 | space: u64, 38 | owner: &'a Address, 39 | ) -> Result { 40 | let rent = Rent::from_account_view(rent_sysvar)?; 41 | let lamports = rent.try_minimum_balance(space as usize)?; 42 | 43 | Ok(Self { 44 | from, 45 | to, 46 | lamports, 47 | space, 48 | owner, 49 | }) 50 | } 51 | 52 | #[inline(always)] 53 | pub fn invoke(&self) -> ProgramResult { 54 | self.invoke_signed(&[]) 55 | } 56 | 57 | #[inline(always)] 58 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 59 | // Instruction accounts 60 | let instruction_accounts: [InstructionAccount; 2] = [ 61 | InstructionAccount::writable_signer(self.from.address()), 62 | InstructionAccount::writable_signer(self.to.address()), 63 | ]; 64 | 65 | // instruction data 66 | // - [0..4 ]: instruction discriminator 67 | // - [4..12 ]: lamports 68 | // - [12..20]: account space 69 | // - [20..52]: owner address 70 | let mut instruction_data = [0; 52]; 71 | // create account instruction has a '0' discriminator 72 | instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); 73 | instruction_data[12..20].copy_from_slice(&self.space.to_le_bytes()); 74 | instruction_data[20..52].copy_from_slice(self.owner.as_ref()); 75 | 76 | let instruction = InstructionView { 77 | program_id: &crate::ID, 78 | accounts: &instruction_accounts, 79 | data: &instruction_data, 80 | }; 81 | 82 | invoke_signed(&instruction, &[self.from, self.to], signers) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /programs/system/src/instructions/withdraw_nonce_account.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, ProgramResult, 5 | }; 6 | 7 | /// Withdraw funds from a nonce account. 8 | /// 9 | /// The `u64` parameter is the lamports to withdraw, which must leave the 10 | /// account balance above the rent exempt reserve or at zero. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE]` Nonce account 14 | /// 1. `[WRITE]` Recipient account 15 | /// 2. `[]` Recent blockhashes sysvar 16 | /// 3. `[]` Rent sysvar 17 | /// 4. `[SIGNER]` Nonce authority 18 | pub struct WithdrawNonceAccount<'a> { 19 | /// Nonce account. 20 | pub account: &'a AccountView, 21 | 22 | /// Recipient account. 23 | pub recipient: &'a AccountView, 24 | 25 | /// Recent blockhashes sysvar. 26 | pub recent_blockhashes_sysvar: &'a AccountView, 27 | 28 | /// Rent sysvar. 29 | pub rent_sysvar: &'a AccountView, 30 | 31 | /// Nonce authority. 32 | pub authority: &'a AccountView, 33 | 34 | /// Lamports to withdraw. 35 | /// 36 | /// The account balance must be left above the rent exempt reserve 37 | /// or at zero. 38 | pub lamports: u64, 39 | } 40 | 41 | impl WithdrawNonceAccount<'_> { 42 | #[inline(always)] 43 | pub fn invoke(&self) -> ProgramResult { 44 | self.invoke_signed(&[]) 45 | } 46 | 47 | #[inline(always)] 48 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 49 | // Instruction accounts 50 | let instruction_accounts: [InstructionAccount; 5] = [ 51 | InstructionAccount::writable(self.account.address()), 52 | InstructionAccount::writable(self.recipient.address()), 53 | InstructionAccount::readonly(self.recent_blockhashes_sysvar.address()), 54 | InstructionAccount::readonly(self.rent_sysvar.address()), 55 | InstructionAccount::readonly_signer(self.authority.address()), 56 | ]; 57 | 58 | // instruction data 59 | // - [0..4 ]: instruction discriminator 60 | // - [4..12]: lamports 61 | let mut instruction_data = [0; 12]; 62 | instruction_data[0] = 5; 63 | instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); 64 | 65 | let instruction = InstructionView { 66 | program_id: &crate::ID, 67 | accounts: &instruction_accounts, 68 | data: &instruction_data, 69 | }; 70 | 71 | invoke_signed( 72 | &instruction, 73 | &[ 74 | self.account, 75 | self.recipient, 76 | self.recent_blockhashes_sysvar, 77 | self.rent_sysvar, 78 | self.authority, 79 | ], 80 | signers, 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /programs/system/src/instructions/transfer_with_seed.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Transfer lamports from a derived address. 8 | /// 9 | /// ### Accounts: 10 | /// 0. `[WRITE]` Funding account 11 | /// 1. `[SIGNER]` Base for funding account 12 | /// 2. `[WRITE]` Recipient account 13 | pub struct TransferWithSeed<'a, 'b, 'c> { 14 | /// Funding account. 15 | pub from: &'a AccountView, 16 | 17 | /// Base account. 18 | /// 19 | /// The account matching the base [`Address`] below must be provided as 20 | /// a signer, but may be the same as the funding account and provided 21 | /// as account 0. 22 | pub base: &'a AccountView, 23 | 24 | /// Recipient account. 25 | pub to: &'a AccountView, 26 | 27 | /// Amount of lamports to transfer. 28 | pub lamports: u64, 29 | 30 | /// String of ASCII chars, no longer than [`MAX_SEED_LEN`](https://docs.rs/solana-address/latest/solana_address/constant.MAX_SEED_LEN.html). 31 | pub seed: &'b str, 32 | 33 | /// Address of program that will own the new account. 34 | pub owner: &'c Address, 35 | } 36 | 37 | impl TransferWithSeed<'_, '_, '_> { 38 | #[inline(always)] 39 | pub fn invoke(&self) -> ProgramResult { 40 | self.invoke_signed(&[]) 41 | } 42 | 43 | #[inline(always)] 44 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 45 | // Instruction accounts 46 | let instruction_accounts: [InstructionAccount; 3] = [ 47 | InstructionAccount::writable(self.from.address()), 48 | InstructionAccount::readonly_signer(self.base.address()), 49 | InstructionAccount::writable(self.to.address()), 50 | ]; 51 | 52 | // instruction data 53 | // - [0..4 ]: instruction discriminator 54 | // - [4..12 ]: lamports amount 55 | // - [12..20]: seed length 56 | // - [20.. ]: seed (max 32) 57 | // - [.. +32]: owner address 58 | let mut instruction_data = [0; 80]; 59 | instruction_data[0] = 11; 60 | instruction_data[4..12].copy_from_slice(&self.lamports.to_le_bytes()); 61 | instruction_data[12..20].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); 62 | 63 | let offset = 20 + self.seed.len(); 64 | instruction_data[20..offset].copy_from_slice(self.seed.as_bytes()); 65 | instruction_data[offset..offset + 32].copy_from_slice(self.owner.as_ref()); 66 | 67 | let instruction = InstructionView { 68 | program_id: &crate::ID, 69 | accounts: &instruction_accounts, 70 | data: &instruction_data[..offset + 32], 71 | }; 72 | 73 | invoke_signed(&instruction, &[self.from, self.base, self.to], signers) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/transfer_checked.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | /// Transfer Tokens from one Token Account to another. 14 | /// 15 | /// ### Accounts: 16 | /// 0. `[WRITE]` The source account. 17 | /// 1. `[]` The token mint. 18 | /// 2. `[WRITE]` The destination account. 19 | /// 3. `[SIGNER]` The source account's owner/delegate. 20 | pub struct TransferChecked<'a, 'b> { 21 | /// Sender account. 22 | pub from: &'a AccountView, 23 | /// Mint Account 24 | pub mint: &'a AccountView, 25 | /// Recipient account. 26 | pub to: &'a AccountView, 27 | /// Authority account. 28 | pub authority: &'a AccountView, 29 | /// Amount of micro-tokens to transfer. 30 | pub amount: u64, 31 | /// Decimal for the Token 32 | pub decimals: u8, 33 | /// Token Program 34 | pub token_program: &'b Address, 35 | } 36 | 37 | impl TransferChecked<'_, '_> { 38 | #[inline(always)] 39 | pub fn invoke(&self) -> ProgramResult { 40 | self.invoke_signed(&[]) 41 | } 42 | 43 | #[inline(always)] 44 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 45 | // Instruction accounts 46 | let instruction_accounts: [InstructionAccount; 4] = [ 47 | InstructionAccount::writable(self.from.address()), 48 | InstructionAccount::readonly(self.mint.address()), 49 | InstructionAccount::writable(self.to.address()), 50 | InstructionAccount::readonly_signer(self.authority.address()), 51 | ]; 52 | 53 | // Instruction data layout: 54 | // - [0]: instruction discriminator (1 byte, u8) 55 | // - [1..9]: amount (8 bytes, u64) 56 | // - [9]: decimals (1 byte, u8) 57 | let mut instruction_data = [UNINIT_BYTE; 10]; 58 | 59 | // Set discriminator as u8 at offset [0] 60 | write_bytes(&mut instruction_data, &[12]); 61 | // Set amount as u64 at offset [1..9] 62 | write_bytes(&mut instruction_data[1..9], &self.amount.to_le_bytes()); 63 | // Set decimals as u8 at offset [9] 64 | write_bytes(&mut instruction_data[9..], &[self.decimals]); 65 | 66 | let instruction = InstructionView { 67 | program_id: self.token_program, 68 | accounts: &instruction_accounts, 69 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 10) }, 70 | }; 71 | 72 | invoke_signed( 73 | &instruction, 74 | &[self.from, self.mint, self.to, self.authority], 75 | signers, 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /programs/system/src/instructions/allocate_with_seed.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | instruction::{InstructionAccount, InstructionView}, 4 | AccountView, Address, ProgramResult, 5 | }; 6 | 7 | /// Allocate space for and assign an account at an address derived 8 | /// from a base address and a seed. 9 | /// 10 | /// ### Accounts: 11 | /// 0. `[WRITE]` Allocated account 12 | /// 1. `[SIGNER]` Base account 13 | pub struct AllocateWithSeed<'a, 'b, 'c> { 14 | /// Allocated account. 15 | pub account: &'a AccountView, 16 | 17 | /// Base account. 18 | /// 19 | /// The account matching the base address below must be provided as 20 | /// a signer, but may be the same as the funding account and provided 21 | /// as account 0. 22 | pub base: &'a AccountView, 23 | 24 | /// String of ASCII chars, no longer than [`MAX_SEED_LEN`](https://docs.rs/solana-address/latest/solana_address/constant.MAX_SEED_LEN.html). 25 | pub seed: &'b str, 26 | 27 | /// Number of bytes of memory to allocate. 28 | pub space: u64, 29 | 30 | /// Address of program that will own the new account. 31 | pub owner: &'c Address, 32 | } 33 | 34 | impl AllocateWithSeed<'_, '_, '_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 2] = [ 44 | InstructionAccount::writable(self.account.address()), 45 | InstructionAccount::readonly_signer(self.base.address()), 46 | ]; 47 | 48 | // instruction data 49 | // - [0..4 ]: instruction discriminator 50 | // - [4..36 ]: base address 51 | // - [36..44]: seed length 52 | // - [44.. ]: seed (max 32) 53 | // - [.. +8]: account space 54 | // - [.. +32]: owner address 55 | let mut instruction_data = [0; 112]; 56 | instruction_data[0] = 9; 57 | instruction_data[4..36].copy_from_slice(self.base.address().as_array()); 58 | instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); 59 | 60 | let offset = 44 + self.seed.len(); 61 | instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); 62 | instruction_data[offset..offset + 8].copy_from_slice(&self.space.to_le_bytes()); 63 | instruction_data[offset + 8..offset + 40].copy_from_slice(self.owner.as_ref()); 64 | 65 | let instruction = InstructionView { 66 | program_id: &crate::ID, 67 | accounts: &instruction_accounts, 68 | data: &instruction_data[..offset + 40], 69 | }; 70 | 71 | invoke_signed(&instruction, &[self.account, self.base], signers) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /programs/system/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use crate::instructions::{Assign, CreateAccount, Transfer}; 4 | use pinocchio::{ 5 | address::declare_id, 6 | cpi::Signer, 7 | sysvars::{rent::Rent, Sysvar}, 8 | AccountView, Address, ProgramResult, 9 | }; 10 | 11 | pub mod instructions; 12 | 13 | declare_id!("11111111111111111111111111111111"); 14 | 15 | /// Create an account with a minimum balance to be rent-exempt. 16 | #[inline(always)] 17 | pub fn create_account_with_minimum_balance( 18 | account: &AccountView, 19 | space: usize, 20 | owner: &Address, 21 | payer: &AccountView, 22 | rent_sysvar: Option<&AccountView>, 23 | ) -> ProgramResult { 24 | create_account_with_minimum_balance_signed(account, space, owner, payer, rent_sysvar, &[]) 25 | } 26 | 27 | /// Create an account with a minimum balance to be rent-exempt. 28 | /// 29 | /// When creating a PDA `account`, the PDA signer seeds must be provided 30 | /// via the `signers`. 31 | /// 32 | /// The account will be funded by the `payer` if its current lamports 33 | /// are insufficient for rent-exemption. The payer can be a PDA signer 34 | /// owned by the system program and its signer seeds can be provided 35 | /// via the `signers`. 36 | #[inline(always)] 37 | pub fn create_account_with_minimum_balance_signed( 38 | account: &AccountView, 39 | space: usize, 40 | owner: &Address, 41 | payer: &AccountView, 42 | rent_sysvar: Option<&AccountView>, 43 | signers: &[Signer], 44 | ) -> ProgramResult { 45 | let lamports = if let Some(rent_sysvar) = rent_sysvar { 46 | let rent = Rent::from_account_view(rent_sysvar)?; 47 | rent.try_minimum_balance(space)? 48 | } else { 49 | Rent::get()?.try_minimum_balance(space)? 50 | }; 51 | 52 | if account.lamports() == 0 { 53 | // Create the account if it does not exist. 54 | CreateAccount { 55 | from: payer, 56 | to: account, 57 | lamports, 58 | space: space as u64, 59 | owner, 60 | } 61 | .invoke_signed(signers) 62 | } else { 63 | let required_lamports = lamports.saturating_sub(account.lamports()); 64 | 65 | // Transfer lamports from `payer` to `account` if needed. 66 | if required_lamports > 0 { 67 | Transfer { 68 | from: payer, 69 | to: account, 70 | lamports: required_lamports, 71 | } 72 | .invoke_signed(signers)?; 73 | } 74 | 75 | // Assign the account to the specified owner. 76 | Assign { account, owner }.invoke_signed(signers)?; 77 | 78 | // Allocate the required space for the account. 79 | // 80 | // SAFETY: There are no active borrows of the `account`. 81 | // This was checked by the `Assign` CPI above. 82 | unsafe { account.resize_unchecked(space) } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_mint.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new mint. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITABLE]` Mint account 14 | /// 1. `[]` Rent sysvar 15 | pub struct InitializeMint<'a> { 16 | /// Mint Account. 17 | pub mint: &'a AccountView, 18 | /// Rent sysvar Account. 19 | pub rent_sysvar: &'a AccountView, 20 | /// Decimals. 21 | pub decimals: u8, 22 | /// Mint Authority. 23 | pub mint_authority: &'a Address, 24 | /// Freeze Authority. 25 | pub freeze_authority: Option<&'a Address>, 26 | } 27 | 28 | impl InitializeMint<'_> { 29 | #[inline(always)] 30 | pub fn invoke(&self) -> ProgramResult { 31 | // Instruction accounts 32 | let instruction_accounts: [InstructionAccount; 2] = [ 33 | InstructionAccount::writable(self.mint.address()), 34 | InstructionAccount::readonly(self.rent_sysvar.address()), 35 | ]; 36 | 37 | // Instruction data layout: 38 | // - [0]: instruction discriminator (1 byte, u8) 39 | // - [1]: decimals (1 byte, u8) 40 | // - [2..34]: mint_authority (32 bytes, Address) 41 | // - [34]: freeze_authority presence flag (1 byte, u8) 42 | // - [35..67]: freeze_authority (optional, 32 bytes, Address) 43 | let mut instruction_data = [UNINIT_BYTE; 67]; 44 | let mut length = instruction_data.len(); 45 | 46 | // Set discriminator as u8 at offset [0] 47 | write_bytes(&mut instruction_data, &[0]); 48 | // Set decimals as u8 at offset [1] 49 | write_bytes(&mut instruction_data[1..2], &[self.decimals]); 50 | // Set mint_authority as Address at offset [2..34] 51 | write_bytes(&mut instruction_data[2..34], self.mint_authority.as_array()); 52 | 53 | if let Some(freeze_auth) = self.freeze_authority { 54 | // Set Option = `true` & freeze_authority at offset [34..67] 55 | write_bytes(&mut instruction_data[34..35], &[1]); 56 | write_bytes(&mut instruction_data[35..], freeze_auth.as_array()); 57 | } else { 58 | // Set Option = `false` 59 | write_bytes(&mut instruction_data[34..35], &[0]); 60 | // Adjust length if no freeze authority 61 | length = 35; 62 | } 63 | 64 | let instruction = InstructionView { 65 | program_id: &crate::ID, 66 | accounts: &instruction_accounts, 67 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 68 | }; 69 | 70 | invoke(&instruction, &[self.mint, self.rent_sysvar]) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /programs/associated-token-account/src/instructions/create.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Creates an associated token account for the given wallet address and token mint. 9 | /// Returns an error if the account exists. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE, SIGNER]` Funding account (must be a system account) 13 | /// 1. `[WRITE]` Associated token account address to be created 14 | /// 2. `[]` Wallet address for the new associated token account 15 | /// 3. `[]` The token mint for the new associated token account 16 | /// 4. `[]` System program 17 | /// 5. `[]` SPL Token program 18 | pub struct Create<'a> { 19 | /// Funding account (must be a system account) 20 | pub funding_account: &'a AccountView, 21 | /// Associated token account address to be created 22 | pub account: &'a AccountView, 23 | /// Wallet address for the new associated token account 24 | pub wallet: &'a AccountView, 25 | /// The token mint for the new associated token account 26 | pub mint: &'a AccountView, 27 | /// System program 28 | pub system_program: &'a AccountView, 29 | /// SPL Token program 30 | pub token_program: &'a AccountView, 31 | } 32 | 33 | impl Create<'_> { 34 | #[inline(always)] 35 | pub fn invoke(&self) -> ProgramResult { 36 | self.invoke_signed(&[]) 37 | } 38 | 39 | #[inline(always)] 40 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 41 | // Instruction accounts 42 | let instruction_accounts: [InstructionAccount; 6] = [ 43 | InstructionAccount::writable_signer(self.funding_account.address()), 44 | InstructionAccount::writable(self.account.address()), 45 | InstructionAccount::readonly(self.wallet.address()), 46 | InstructionAccount::readonly(self.mint.address()), 47 | InstructionAccount::readonly(self.system_program.address()), 48 | InstructionAccount::readonly(self.token_program.address()), 49 | ]; 50 | 51 | // Instruction data: 52 | // - [0]: Instruction discriminator (1 byte, u8) (0 for Create) 53 | 54 | let instruction_data = [0u8]; 55 | 56 | let instruction = InstructionView { 57 | program_id: &crate::ID, 58 | accounts: &instruction_accounts, 59 | data: &instruction_data, 60 | }; 61 | 62 | invoke_signed( 63 | &instruction, 64 | &[ 65 | self.funding_account, 66 | self.account, 67 | self.wallet, 68 | self.mint, 69 | self.system_program, 70 | self.token_program, 71 | ], 72 | signers, 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_mint.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke, InstructionAccount, InstructionView}; 6 | use solana_program_error::ProgramResult; 7 | 8 | use crate::{write_bytes, UNINIT_BYTE}; 9 | 10 | /// Initialize a new mint. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITABLE]` Mint account 14 | /// 1. `[]` Rent sysvar 15 | pub struct InitializeMint<'a, 'b> { 16 | /// Mint Account. 17 | pub mint: &'a AccountView, 18 | /// Rent sysvar Account. 19 | pub rent_sysvar: &'a AccountView, 20 | /// Decimals. 21 | pub decimals: u8, 22 | /// Mint Authority. 23 | pub mint_authority: &'a Address, 24 | /// Freeze Authority. 25 | pub freeze_authority: Option<&'a Address>, 26 | /// Token Program 27 | pub token_program: &'b Address, 28 | } 29 | 30 | impl InitializeMint<'_, '_> { 31 | #[inline(always)] 32 | pub fn invoke(&self) -> ProgramResult { 33 | // Instruction accounts 34 | let instruction_accounts: [InstructionAccount; 2] = [ 35 | InstructionAccount::writable(self.mint.address()), 36 | InstructionAccount::readonly(self.rent_sysvar.address()), 37 | ]; 38 | 39 | // Instruction data layout: 40 | // - [0]: instruction discriminator (1 byte, u8) 41 | // - [1]: decimals (1 byte, u8) 42 | // - [2..34]: mint_authority (32 bytes, Address) 43 | // - [34]: freeze_authority presence flag (1 byte, u8) 44 | // - [35..67]: freeze_authority (optional, 32 bytes, Address) 45 | let mut instruction_data = [UNINIT_BYTE; 67]; 46 | let mut length = instruction_data.len(); 47 | 48 | // Set discriminator as u8 at offset [0] 49 | write_bytes(&mut instruction_data, &[0]); 50 | // Set decimals as u8 at offset [1] 51 | write_bytes(&mut instruction_data[1..2], &[self.decimals]); 52 | // Set mint_authority at offset [2..34] 53 | write_bytes(&mut instruction_data[2..34], self.mint_authority.as_array()); 54 | 55 | if let Some(freeze_auth) = self.freeze_authority { 56 | // Set Option = `true` & freeze_authority at offset [34..67] 57 | write_bytes(&mut instruction_data[34..35], &[1]); 58 | write_bytes(&mut instruction_data[35..], freeze_auth.as_array()); 59 | } else { 60 | // Set Option = `false` 61 | write_bytes(&mut instruction_data[34..35], &[0]); 62 | // Adjust length if no freeze authority 63 | length = 35; 64 | } 65 | 66 | let instruction = InstructionView { 67 | program_id: self.token_program, 68 | accounts: &instruction_accounts, 69 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 70 | }; 71 | 72 | invoke(&instruction, &[self.mint, self.rent_sysvar]) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /programs/associated-token-account/src/instructions/create_idempotent.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Creates an associated token account for the given wallet address and 9 | /// token mint, if it doesn't already exist. Returns an error if the 10 | /// account exists, but with a different owner. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[WRITE, SIGNER]` Funding account (must be a system account) 14 | /// 1. `[WRITE]` Associated token account address to be created 15 | /// 2. `[]` Wallet address for the new associated token account 16 | /// 3. `[]` The token mint for the new associated token account 17 | /// 4. `[]` System program 18 | /// 5. `[]` SPL Token program 19 | pub struct CreateIdempotent<'a> { 20 | /// Funding account (must be a system account) 21 | pub funding_account: &'a AccountView, 22 | /// Associated token account address to be created 23 | pub account: &'a AccountView, 24 | /// Wallet address for the new associated token account 25 | pub wallet: &'a AccountView, 26 | /// The token mint for the new associated token account 27 | pub mint: &'a AccountView, 28 | /// System program 29 | pub system_program: &'a AccountView, 30 | /// SPL Token program 31 | pub token_program: &'a AccountView, 32 | } 33 | 34 | impl CreateIdempotent<'_> { 35 | #[inline(always)] 36 | pub fn invoke(&self) -> ProgramResult { 37 | self.invoke_signed(&[]) 38 | } 39 | 40 | #[inline(always)] 41 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 42 | // Instruction accounts 43 | let instruction_accounts: [InstructionAccount; 6] = [ 44 | InstructionAccount::writable_signer(self.funding_account.address()), 45 | InstructionAccount::writable(self.account.address()), 46 | InstructionAccount::readonly(self.wallet.address()), 47 | InstructionAccount::readonly(self.mint.address()), 48 | InstructionAccount::readonly(self.system_program.address()), 49 | InstructionAccount::readonly(self.token_program.address()), 50 | ]; 51 | 52 | // Instruction data: 53 | // - [0]: Instruction discriminator (1 byte, u8) (1 for CreateIdempotent) 54 | 55 | let instruction_data = [1u8]; 56 | 57 | let instruction = InstructionView { 58 | program_id: &crate::ID, 59 | accounts: &instruction_accounts, 60 | data: &instruction_data, 61 | }; 62 | 63 | invoke_signed( 64 | &instruction, 65 | &[ 66 | self.funding_account, 67 | self.account, 68 | self.wallet, 69 | self.mint, 70 | self.system_program, 71 | self.token_program, 72 | ], 73 | signers, 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /scripts/setup/shared.mts: -------------------------------------------------------------------------------- 1 | import 'zx/globals'; 2 | import { JsonMap, parse as parseToml } from '@iarna/toml'; 3 | 4 | process.env.FORCE_COLOR = '3'; 5 | process.env.CARGO_TERM_COLOR = 'always'; 6 | 7 | export const workingDirectory = (await $`pwd`.quiet()).toString().trim(); 8 | 9 | export function getCargo(folder?: string): JsonMap { 10 | return parseToml( 11 | fs.readFileSync( 12 | path.resolve( 13 | workingDirectory, 14 | path.join(folder ? folder : '.', 'Cargo.toml') 15 | ), 16 | 'utf8' 17 | ) 18 | ); 19 | } 20 | 21 | export function getCargoMetadata(folder?: string) { 22 | const cargo = getCargo(folder); 23 | return folder ? cargo?.package?.['metadata'] : cargo?.workspace?.['metadata']; 24 | } 25 | 26 | export function getSolanaVersion(): string { 27 | return getCargoMetadata()?.cli?.solana; 28 | } 29 | 30 | export function getToolchain(operation): string { 31 | return getCargoMetadata()?.toolchains?.[operation]; 32 | } 33 | 34 | export function getToolchainArgument(operation): string { 35 | const channel = getToolchain(operation); 36 | return channel ? `+${channel}` : ''; 37 | } 38 | 39 | export function cliArguments(): string[] { 40 | return process.argv.slice(2); 41 | } 42 | 43 | export function popArgument(args: string[], arg: string) { 44 | const index = args.indexOf(arg); 45 | if (index >= 0) { 46 | args.splice(index, 1); 47 | } 48 | return index >= 0; 49 | } 50 | 51 | export function partitionArguments( 52 | args: string[], 53 | delimiter: string, 54 | defaultArgs?: string[] 55 | ): [string[], string[]] { 56 | const index = args.indexOf(delimiter); 57 | const [providedCargoArgs, providedCommandArgs] = 58 | index >= 0 ? [args.slice(0, index), args.slice(index + 1)] : [args, []]; 59 | 60 | if (defaultArgs) { 61 | const [defaultCargoArgs, defaultCommandArgs] = partitionArguments( 62 | defaultArgs, 63 | delimiter 64 | ); 65 | return [ 66 | [...defaultCargoArgs, ...providedCargoArgs], 67 | [...defaultCommandArgs, ...providedCommandArgs], 68 | ]; 69 | } 70 | return [providedCargoArgs, providedCommandArgs]; 71 | } 72 | 73 | export async function getInstalledSolanaVersion(): Promise { 74 | try { 75 | const { stdout } = await $`solana --version`.quiet(); 76 | return stdout.match(/(\d+\.\d+\.\d+)/)?.[1]; 77 | } catch (error) { 78 | return ''; 79 | } 80 | } 81 | 82 | export function parseCliArguments(): { 83 | command: string; 84 | libraryPath: string; 85 | args: string[]; 86 | } { 87 | const command = process.argv[2]; 88 | const args = process.argv.slice(3); 89 | 90 | // Extract the relative crate directory from the command-line arguments. This 91 | // is the only required argument. 92 | const relativePath = args.shift(); 93 | 94 | if (!relativePath) { 95 | throw new Error('Missing relative manifest path'); 96 | } 97 | 98 | return { 99 | command, 100 | libraryPath: path.join(workingDirectory, relativePath), 101 | args, 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /programs/token/src/instructions/set_authority.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | #[repr(u8)] 14 | #[derive(Clone, Copy)] 15 | pub enum AuthorityType { 16 | MintTokens = 0, 17 | FreezeAccount = 1, 18 | AccountOwner = 2, 19 | CloseAccount = 3, 20 | } 21 | 22 | /// Sets a new authority of a mint or account. 23 | /// 24 | /// ### Accounts: 25 | /// 0. `[WRITE]` The mint or account to change the authority of. 26 | /// 1. `[SIGNER]` The current authority of the mint or account. 27 | pub struct SetAuthority<'a> { 28 | /// Account (Mint or Token) 29 | pub account: &'a AccountView, 30 | /// Authority of the Account. 31 | pub authority: &'a AccountView, 32 | /// The type of authority to update. 33 | pub authority_type: AuthorityType, 34 | /// The new authority 35 | pub new_authority: Option<&'a Address>, 36 | } 37 | 38 | impl SetAuthority<'_> { 39 | #[inline(always)] 40 | pub fn invoke(&self) -> ProgramResult { 41 | self.invoke_signed(&[]) 42 | } 43 | 44 | #[inline(always)] 45 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 46 | // Instruction accounts 47 | let instruction_accounts: [InstructionAccount; 2] = [ 48 | InstructionAccount::writable(self.account.address()), 49 | InstructionAccount::readonly_signer(self.authority.address()), 50 | ]; 51 | 52 | // instruction data 53 | // - [0]: instruction discriminator (1 byte, u8) 54 | // - [1]: authority_type (1 byte, u8) 55 | // - [2]: new_authority presence flag (1 byte, AuthorityType) 56 | // - [3..35] new_authority (optional, 32 bytes, Address) 57 | let mut instruction_data = [UNINIT_BYTE; 35]; 58 | let mut length = instruction_data.len(); 59 | 60 | // Set discriminator as u8 at offset [0] 61 | write_bytes(&mut instruction_data, &[6]); 62 | // Set authority_type as u8 at offset [1] 63 | write_bytes(&mut instruction_data[1..2], &[self.authority_type as u8]); 64 | 65 | if let Some(new_authority) = self.new_authority { 66 | // Set new_authority as [u8; 32] at offset [2..35] 67 | write_bytes(&mut instruction_data[2..3], &[1]); 68 | write_bytes(&mut instruction_data[3..], new_authority.as_array()); 69 | } else { 70 | write_bytes(&mut instruction_data[2..3], &[0]); 71 | // Adjust length if no new authority 72 | length = 3; 73 | } 74 | 75 | let instruction = InstructionView { 76 | program_id: &crate::ID, 77 | accounts: &instruction_accounts, 78 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 79 | }; 80 | 81 | invoke_signed(&instruction, &[self.account, self.authority], signers) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/set_authority.rs: -------------------------------------------------------------------------------- 1 | use core::slice::from_raw_parts; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{ 6 | cpi::{invoke_signed, Signer}, 7 | InstructionAccount, InstructionView, 8 | }; 9 | use solana_program_error::ProgramResult; 10 | 11 | use crate::{write_bytes, UNINIT_BYTE}; 12 | 13 | #[repr(u8)] 14 | #[derive(Clone, Copy)] 15 | pub enum AuthorityType { 16 | MintTokens = 0, 17 | FreezeAccount = 1, 18 | AccountOwner = 2, 19 | CloseAccount = 3, 20 | } 21 | 22 | /// Sets a new authority of a mint or account. 23 | /// 24 | /// ### Accounts: 25 | /// 0. `[WRITE]` The mint or account to change the authority of. 26 | /// 1. `[SIGNER]` The current authority of the mint or account. 27 | pub struct SetAuthority<'a, 'b> { 28 | /// Account (Mint or Token) 29 | pub account: &'a AccountView, 30 | /// Authority of the Account. 31 | pub authority: &'a AccountView, 32 | /// The type of authority to update. 33 | pub authority_type: AuthorityType, 34 | /// The new authority 35 | pub new_authority: Option<&'a Address>, 36 | /// Token Program 37 | pub token_program: &'b Address, 38 | } 39 | 40 | impl SetAuthority<'_, '_> { 41 | #[inline(always)] 42 | pub fn invoke(&self) -> ProgramResult { 43 | self.invoke_signed(&[]) 44 | } 45 | 46 | #[inline(always)] 47 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 48 | // Instruction accounts 49 | let instruction_accounts: [InstructionAccount; 2] = [ 50 | InstructionAccount::writable(self.account.address()), 51 | InstructionAccount::readonly_signer(self.authority.address()), 52 | ]; 53 | 54 | // instruction data 55 | // - [0]: instruction discriminator (1 byte, u8) 56 | // - [1]: authority_type (1 byte, u8) 57 | // - [2]: new_authority presence flag (1 byte, AuthorityType) 58 | // - [3..35] new_authority (optional, 32 bytes, Address) 59 | let mut instruction_data = [UNINIT_BYTE; 35]; 60 | let mut length = instruction_data.len(); 61 | 62 | // Set discriminator as u8 at offset [0] 63 | write_bytes(&mut instruction_data, &[6]); 64 | // Set authority_type as u8 at offset [1] 65 | write_bytes(&mut instruction_data[1..2], &[self.authority_type as u8]); 66 | 67 | if let Some(new_authority) = self.new_authority { 68 | // Set new_authority as [u8; 32] at offset [2..35] 69 | write_bytes(&mut instruction_data[2..3], &[1]); 70 | write_bytes(&mut instruction_data[3..], new_authority.as_array()); 71 | } else { 72 | write_bytes(&mut instruction_data[2..3], &[0]); 73 | // Adjust length if no new authority 74 | length = 3; 75 | } 76 | 77 | let instruction = InstructionView { 78 | program_id: self.token_program, 79 | accounts: &instruction_accounts, 80 | data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, length) }, 81 | }; 82 | 83 | invoke_signed(&instruction, &[self.account, self.authority], signers) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_multisig_2.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice}; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{cpi::invoke_with_bounds, InstructionAccount, InstructionView}; 5 | use solana_program_error::{ProgramError, ProgramResult}; 6 | 7 | use crate::instructions::MAX_MULTISIG_SIGNERS; 8 | 9 | /// Initialize a new Multisig. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[writable]` The multisig account to initialize. 13 | /// 1. ..`1+N`. `[]` The N signer accounts, where N is between 1 and 11. 14 | pub struct InitializeMultisig2<'a, 'b> 15 | where 16 | 'a: 'b, 17 | { 18 | /// Multisig Account. 19 | pub multisig: &'a AccountView, 20 | /// Signer Accounts 21 | pub signers: &'b [&'a AccountView], 22 | /// The number of signers (M) required to validate this multisignature 23 | /// account. 24 | pub m: u8, 25 | } 26 | 27 | impl InitializeMultisig2<'_, '_> { 28 | #[inline(always)] 29 | pub fn invoke(&self) -> ProgramResult { 30 | let &Self { 31 | multisig, 32 | signers, 33 | m, 34 | } = self; 35 | 36 | if signers.len() > MAX_MULTISIG_SIGNERS { 37 | return Err(ProgramError::InvalidArgument); 38 | } 39 | 40 | let num_accounts = 1 + signers.len(); 41 | 42 | // Instruction accounts 43 | const UNINIT_INSTRUCTION_ACCOUNT: MaybeUninit = 44 | MaybeUninit::::uninit(); 45 | let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; 1 + MAX_MULTISIG_SIGNERS]; 46 | 47 | unsafe { 48 | // SAFETY: 49 | // - `instruction_accounts` is sized to 1 + MAX_MULTISIG_SIGNERS 50 | // - Index 0 is always present 51 | instruction_accounts 52 | .get_unchecked_mut(0) 53 | .write(InstructionAccount::writable(multisig.address())); 54 | } 55 | 56 | for (instruction_account, signer) in 57 | instruction_accounts[1..].iter_mut().zip(signers.iter()) 58 | { 59 | instruction_account.write(InstructionAccount::readonly(signer.address())); 60 | } 61 | 62 | // Instruction data layout: 63 | // - [0]: instruction discriminator (1 byte, u8) 64 | // - [1]: m (1 byte, u8) 65 | let data = &[19, m]; 66 | 67 | let instruction = InstructionView { 68 | program_id: &crate::ID, 69 | accounts: unsafe { 70 | slice::from_raw_parts(instruction_accounts.as_ptr() as _, num_accounts) 71 | }, 72 | data, 73 | }; 74 | 75 | // Account view array 76 | const UNINIT_VIEW: MaybeUninit<&AccountView> = MaybeUninit::uninit(); 77 | let mut acc_views = [UNINIT_VIEW; 1 + MAX_MULTISIG_SIGNERS]; 78 | 79 | unsafe { 80 | // SAFETY: 81 | // - `account_views` is sized to 1 + MAX_MULTISIG_SIGNERS 82 | // - Index 0 is always present 83 | acc_views.get_unchecked_mut(0).write(multisig); 84 | } 85 | 86 | // Fill signer accounts 87 | for (account_view, signer) in acc_views[1..].iter_mut().zip(signers.iter()) { 88 | account_view.write(signer); 89 | } 90 | 91 | invoke_with_bounds::<{ 1 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe { 92 | slice::from_raw_parts(acc_views.as_ptr() as _, num_accounts) 93 | }) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /sdk/src/sysvars/fees.rs: -------------------------------------------------------------------------------- 1 | //! Calculation of transaction fees. 2 | 3 | use super::{clock::DEFAULT_MS_PER_SLOT, Sysvar}; 4 | use crate::impl_sysvar_get; 5 | 6 | /// Fee calculator for processing transactions 7 | #[cfg_attr(feature = "copy", derive(Copy))] 8 | #[derive(Clone, Debug)] 9 | pub struct FeeCalculator { 10 | /// The current cost of a signature in lamports. 11 | /// This amount may increase/decrease over time based on cluster processing 12 | /// load. 13 | pub lamports_per_signature: u64, 14 | } 15 | 16 | impl FeeCalculator { 17 | /// Create a new instance of the `FeeCalculator`. 18 | pub fn new(lamports_per_signature: u64) -> Self { 19 | Self { 20 | lamports_per_signature, 21 | } 22 | } 23 | } 24 | 25 | /// Governs the fee rate for the cluster 26 | #[cfg_attr(feature = "copy", derive(Copy))] 27 | #[derive(Clone, Debug)] 28 | pub struct FeeRateGovernor { 29 | /// The current cost of a signature 30 | pub lamports_per_signature: u64, 31 | /// The target cost of a signature 32 | pub target_lamports_per_signature: u64, 33 | /// The target number of signatures per slot 34 | pub target_signatures_per_slot: u64, 35 | /// Minimum lamports per signature 36 | pub min_lamports_per_signature: u64, 37 | /// Maximum lamports per signature 38 | pub max_lamports_per_signature: u64, 39 | /// Percentage of fees to burn (0-100) 40 | pub burn_percent: u8, 41 | } 42 | 43 | /// Default lamports per signature. 44 | pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000; 45 | 46 | /// Default signatures per slot. 47 | pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT; 48 | 49 | /// Default percentage of fees to burn. 50 | pub const DEFAULT_BURN_PERCENT: u8 = 50; 51 | 52 | impl Default for FeeRateGovernor { 53 | fn default() -> Self { 54 | Self { 55 | lamports_per_signature: 0, 56 | target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE, // Example default value 57 | target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT, // Assuming 400ms per slot 58 | min_lamports_per_signature: 0, 59 | max_lamports_per_signature: 0, 60 | burn_percent: DEFAULT_BURN_PERCENT, 61 | } 62 | } 63 | } 64 | 65 | impl FeeRateGovernor { 66 | /// Create a new `FeeCalculator` based on current cluster signature throughput 67 | pub fn create_fee_calculator(&self) -> FeeCalculator { 68 | FeeCalculator::new(self.lamports_per_signature) 69 | } 70 | 71 | /// Calculate unburned fee from a fee total, returns (unburned, burned) 72 | pub fn burn(&self, fees: u64) -> (u64, u64) { 73 | let burned = fees * u64::from(self.burn_percent) / 100; 74 | (fees - burned, burned) 75 | } 76 | } 77 | 78 | /// Fees sysvar 79 | #[cfg_attr(feature = "copy", derive(Copy))] 80 | #[derive(Clone, Debug)] 81 | pub struct Fees { 82 | /// Fee calculator for processing transactions 83 | pub fee_calculator: FeeCalculator, 84 | /// Fee rate governor 85 | pub fee_rate_governor: FeeRateGovernor, 86 | } 87 | 88 | impl Fees { 89 | /// Create a new instance of the Fees sysvar 90 | pub fn new(fee_calculator: FeeCalculator, fee_rate_governor: FeeRateGovernor) -> Self { 91 | Self { 92 | fee_calculator, 93 | fee_rate_governor, 94 | } 95 | } 96 | } 97 | 98 | impl Sysvar for Fees { 99 | impl_sysvar_get!(sol_get_fees_sysvar); 100 | } 101 | -------------------------------------------------------------------------------- /sdk/src/sysvars/slot_hashes/test_edge.rs: -------------------------------------------------------------------------------- 1 | use super::test_utils::{build_slot_hashes_bytes as raw_slot_hashes, make_account_view}; 2 | use crate::{error::ProgramError, sysvars::slot_hashes::*}; 3 | 4 | #[test] 5 | fn test_wrong_key_from_account_view() { 6 | let bytes = raw_slot_hashes(0, &[]); 7 | let (view, _backing) = unsafe { 8 | make_account_view( 9 | Address::new_from_array([1u8; 32]), 10 | &bytes, 11 | crate::entrypoint::NON_DUP_MARKER, 12 | ) 13 | }; 14 | assert!(matches!( 15 | SlotHashes::from_account_view(&view), 16 | Err(ProgramError::InvalidArgument) 17 | )); 18 | } 19 | 20 | #[test] 21 | fn test_wrong_size_buffer_rejected() { 22 | // Buffer that declares 1 entry but is 1 byte too small to hold it. 23 | let num_entries: u64 = 1; 24 | let required_size = NUM_ENTRIES_SIZE + (num_entries as usize) * ENTRY_SIZE; 25 | let mut small_buffer = alloc::vec![0u8; required_size - 1]; 26 | small_buffer[0..NUM_ENTRIES_SIZE].copy_from_slice(&num_entries.to_le_bytes()); 27 | 28 | assert!(matches!( 29 | SlotHashes::new(small_buffer.as_slice()), 30 | Err(ProgramError::AccountDataTooSmall) 31 | )); 32 | 33 | // Buffer too small to even contain the length header. 34 | let too_small_for_header = [0u8; NUM_ENTRIES_SIZE - 1]; 35 | assert!(matches!( 36 | SlotHashes::new(too_small_for_header.as_slice()), 37 | Err(ProgramError::AccountDataTooSmall) 38 | )); 39 | } 40 | 41 | #[test] 42 | fn test_truncated_payload_with_max_size_buffer_is_valid() { 43 | let entry = (123u64, [7u8; HASH_BYTES]); 44 | let bytes = raw_slot_hashes(2, &[entry]); // says 2 but provides 1, rest is zeros 45 | 46 | // With MAX_SIZE buffers, this is now valid - the second entry is just zeros 47 | let slot_hashes = SlotHashes::new(bytes.as_slice()).expect("Should be valid"); 48 | assert_eq!(slot_hashes.len(), 2); 49 | 50 | // First entry should match what we provided 51 | let first_entry = slot_hashes.get_entry(0).unwrap(); 52 | assert_eq!(first_entry.slot(), 123); 53 | assert_eq!(first_entry.hash, [7u8; HASH_BYTES]); 54 | 55 | // Second entry should be all zeros (default padding) 56 | let second_entry = slot_hashes.get_entry(1).unwrap(); 57 | assert_eq!(second_entry.slot(), 0); 58 | assert_eq!(second_entry.hash, [0u8; HASH_BYTES]); 59 | } 60 | 61 | #[test] 62 | fn test_duplicate_slots_binary_search_safe() { 63 | let entries = &[ 64 | (200, [0u8; HASH_BYTES]), 65 | (200, [1u8; HASH_BYTES]), 66 | (199, [2u8; HASH_BYTES]), 67 | ]; 68 | let bytes = raw_slot_hashes(entries.len() as u64, entries); 69 | let sh = unsafe { SlotHashes::new_unchecked(&bytes[..]) }; 70 | let dup_pos = sh.position(200).expect("slot 200 must exist"); 71 | assert!( 72 | dup_pos <= 1, 73 | "binary_search should return one of the duplicate indices (0 or 1)" 74 | ); 75 | assert_eq!(sh.get_hash(199), Some(&entries[2].1)); 76 | } 77 | 78 | #[test] 79 | fn test_zero_len_minimal_slice_iterates_empty() { 80 | let zero_data = raw_slot_hashes(0, &[]); 81 | let sh = unsafe { SlotHashes::new_unchecked(&zero_data[..]) }; 82 | assert_eq!(sh.len(), 0); 83 | assert!(sh.into_iter().next().is_none()); 84 | } 85 | 86 | #[test] 87 | fn test_borrow_state_failure_from_account_view() { 88 | let bytes = raw_slot_hashes(0, &[]); 89 | let (view, _backing) = unsafe { make_account_view(SLOTHASHES_ID, &bytes, 0) }; 90 | assert!(matches!( 91 | SlotHashes::from_account_view(&view), 92 | Err(ProgramError::AccountBorrowFailed) 93 | )); 94 | } 95 | -------------------------------------------------------------------------------- /programs/associated-token-account/src/instructions/recover_nested.rs: -------------------------------------------------------------------------------- 1 | use solana_account_view::AccountView; 2 | use solana_instruction_view::{ 3 | cpi::{invoke_signed, Signer}, 4 | InstructionAccount, InstructionView, 5 | }; 6 | use solana_program_error::ProgramResult; 7 | 8 | /// Transfers from and closes a nested associated token account: an 9 | /// associated token account owned by an associated token account. 10 | /// 11 | /// The tokens are moved from the nested associated token account to the 12 | /// wallet's associated token account, and the nested account lamports are 13 | /// moved to the wallet. 14 | /// 15 | /// Note: Nested token accounts are an anti-pattern, and almost always 16 | /// created unintentionally, so this instruction should only be used to 17 | /// recover from errors 18 | /// 19 | /// ### Accounts: 20 | /// 0. `[WRITE]` Nested associated token account, must be owned by `3` 21 | /// 1. `[]` Token mint for the nested associated token account 22 | /// 2. `[WRITE]` Wallet's associated token account 23 | /// 3. `[]` Owner associated token account address, must be owned by `5` 24 | /// 4. `[]` Token mint for the owner associated token account 25 | /// 5. `[WRITE, SIGNER]` Wallet address for the owner associated token account 26 | /// 6. `[]` SPL Token program 27 | pub struct RecoverNested<'a> { 28 | /// Nested associated token account, must be owned by `owner_associated_token_account` 29 | pub account: &'a AccountView, 30 | /// Token mint for the nested associated token account 31 | pub mint: &'a AccountView, 32 | /// Wallet's associated token account 33 | pub destination_account: &'a AccountView, 34 | /// Owner associated token account address, must be owned by `wallet_account` 35 | pub owner_account: &'a AccountView, 36 | /// Token mint for the owner associated token account 37 | pub owner_mint: &'a AccountView, 38 | /// Wallet address for the owner associated token account 39 | pub wallet: &'a AccountView, 40 | /// SPL Token program 41 | pub token_program: &'a AccountView, 42 | } 43 | 44 | impl RecoverNested<'_> { 45 | #[inline(always)] 46 | pub fn invoke(&self) -> ProgramResult { 47 | self.invoke_signed(&[]) 48 | } 49 | 50 | #[inline(always)] 51 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 52 | // Instruction accounts 53 | let instruction_accounts: [InstructionAccount; 7] = [ 54 | InstructionAccount::writable(self.account.address()), 55 | InstructionAccount::readonly(self.mint.address()), 56 | InstructionAccount::writable(self.destination_account.address()), 57 | InstructionAccount::readonly(self.owner_account.address()), 58 | InstructionAccount::readonly(self.owner_mint.address()), 59 | InstructionAccount::writable_signer(self.wallet.address()), 60 | InstructionAccount::readonly(self.token_program.address()), 61 | ]; 62 | 63 | // Instruction data: 64 | // - [0]: Instruction discriminator (1 byte, u8) (2 for RecoverNested) 65 | 66 | let instruction_data = [2u8]; 67 | 68 | let instruction = InstructionView { 69 | program_id: &crate::ID, 70 | accounts: &instruction_accounts, 71 | data: &instruction_data, 72 | }; 73 | 74 | invoke_signed( 75 | &instruction, 76 | &[ 77 | self.account, 78 | self.mint, 79 | self.destination_account, 80 | self.owner_account, 81 | self.owner_mint, 82 | self.wallet, 83 | self.token_program, 84 | ], 85 | signers, 86 | ) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_multisig_2.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice}; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke_with_bounds, InstructionAccount, InstructionView}; 6 | use solana_program_error::{ProgramError, ProgramResult}; 7 | 8 | use crate::instructions::MAX_MULTISIG_SIGNERS; 9 | 10 | /// Initialize a new Multisig. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[writable]` The multisig account to initialize. 14 | /// 1. `..+N` `[]` The `N` signer accounts, where `N` is `1 <= 15 | /// N <= 11`. 16 | pub struct InitializeMultisig2<'a, 'b, 'c> 17 | where 18 | 'a: 'b, 19 | { 20 | /// Multisig Account. 21 | pub multisig: &'a AccountView, 22 | /// Signer Accounts 23 | pub signers: &'b [&'a AccountView], 24 | /// The number of signers (M) required to validate this multisignature 25 | /// account. 26 | pub m: u8, 27 | /// Token Program. 28 | pub token_program: &'c Address, 29 | } 30 | 31 | impl InitializeMultisig2<'_, '_, '_> { 32 | #[inline(always)] 33 | pub fn invoke(&self) -> ProgramResult { 34 | let &Self { 35 | multisig, 36 | signers, 37 | m, 38 | token_program, 39 | } = self; 40 | 41 | if signers.len() > MAX_MULTISIG_SIGNERS { 42 | return Err(ProgramError::InvalidArgument); 43 | } 44 | 45 | let num_accounts = 1 + signers.len(); 46 | 47 | // Instruction accounts 48 | const UNINIT_INSTRUCTION_ACCOUNT: MaybeUninit = 49 | MaybeUninit::::uninit(); 50 | let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; 1 + MAX_MULTISIG_SIGNERS]; 51 | 52 | unsafe { 53 | // SAFETY: 54 | // - `instruction_accounts` is sized to 1 + MAX_MULTISIG_SIGNERS 55 | // - Index 0 is always present 56 | instruction_accounts 57 | .get_unchecked_mut(0) 58 | .write(InstructionAccount::writable(multisig.address())); 59 | } 60 | 61 | for (instruction_account, signer) in 62 | instruction_accounts[1..].iter_mut().zip(signers.iter()) 63 | { 64 | instruction_account.write(InstructionAccount::readonly(signer.address())); 65 | } 66 | 67 | // Instruction data layout: 68 | // - [0]: instruction discriminator (1 byte, u8) 69 | // - [1]: m (1 byte, u8) 70 | let data = &[19, m]; 71 | 72 | let instruction = InstructionView { 73 | program_id: token_program, 74 | accounts: unsafe { 75 | slice::from_raw_parts(instruction_accounts.as_ptr() as _, num_accounts) 76 | }, 77 | data, 78 | }; 79 | 80 | // Account view array 81 | const UNINIT_VIEW: MaybeUninit<&AccountView> = MaybeUninit::uninit(); 82 | let mut acc_views = [UNINIT_VIEW; 1 + MAX_MULTISIG_SIGNERS]; 83 | 84 | unsafe { 85 | // SAFETY: 86 | // - `account_views` is sized to 1 + MAX_MULTISIG_SIGNERS 87 | // - Index 0 is always present 88 | acc_views.get_unchecked_mut(0).write(multisig); 89 | } 90 | 91 | // Fill signer accounts 92 | for (account_view, signer) in acc_views[1..].iter_mut().zip(signers.iter()) { 93 | account_view.write(signer); 94 | } 95 | 96 | invoke_with_bounds::<{ 1 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe { 97 | slice::from_raw_parts(acc_views.as_ptr() as _, num_accounts) 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "five8" 7 | version = "1.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" 10 | dependencies = [ 11 | "five8_core", 12 | ] 13 | 14 | [[package]] 15 | name = "five8_const" 16 | version = "1.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" 19 | dependencies = [ 20 | "five8_core", 21 | ] 22 | 23 | [[package]] 24 | name = "five8_core" 25 | version = "0.1.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" 28 | 29 | [[package]] 30 | name = "pinocchio" 31 | version = "0.9.2" 32 | dependencies = [ 33 | "pinocchio", 34 | "solana-account-view", 35 | "solana-address", 36 | "solana-define-syscall", 37 | "solana-instruction-view", 38 | "solana-program-error", 39 | ] 40 | 41 | [[package]] 42 | name = "pinocchio-associated-token-account" 43 | version = "0.2.0" 44 | dependencies = [ 45 | "solana-account-view", 46 | "solana-address", 47 | "solana-instruction-view", 48 | "solana-program-error", 49 | ] 50 | 51 | [[package]] 52 | name = "pinocchio-memo" 53 | version = "0.2.0" 54 | dependencies = [ 55 | "solana-account-view", 56 | "solana-address", 57 | "solana-instruction-view", 58 | "solana-program-error", 59 | ] 60 | 61 | [[package]] 62 | name = "pinocchio-system" 63 | version = "0.4.0" 64 | dependencies = [ 65 | "pinocchio", 66 | "solana-address", 67 | ] 68 | 69 | [[package]] 70 | name = "pinocchio-token" 71 | version = "0.4.0" 72 | dependencies = [ 73 | "solana-account-view", 74 | "solana-address", 75 | "solana-instruction-view", 76 | "solana-program-error", 77 | ] 78 | 79 | [[package]] 80 | name = "pinocchio-token-2022" 81 | version = "0.1.0" 82 | dependencies = [ 83 | "solana-account-view", 84 | "solana-address", 85 | "solana-instruction-view", 86 | "solana-program-error", 87 | ] 88 | 89 | [[package]] 90 | name = "solana-account-view" 91 | version = "1.0.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "f37ca34c37f92ee341b73d5ce7c8ef5bb38e9a87955b4bd343c63fa18b149215" 94 | dependencies = [ 95 | "solana-address", 96 | "solana-program-error", 97 | ] 98 | 99 | [[package]] 100 | name = "solana-address" 101 | version = "2.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "e37320fd2945c5d654b2c6210624a52d66c3f1f73b653ed211ab91a703b35bdd" 104 | dependencies = [ 105 | "five8", 106 | "five8_const", 107 | "solana-define-syscall", 108 | "solana-program-error", 109 | ] 110 | 111 | [[package]] 112 | name = "solana-define-syscall" 113 | version = "4.0.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" 116 | 117 | [[package]] 118 | name = "solana-instruction-view" 119 | version = "1.0.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "60147e4d0a4620013df40bf30a86dd299203ff12fcb8b593cd51014fce0875d8" 122 | dependencies = [ 123 | "solana-account-view", 124 | "solana-address", 125 | "solana-define-syscall", 126 | "solana-program-error", 127 | ] 128 | 129 | [[package]] 130 | name = "solana-program-error" 131 | version = "3.0.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" 134 | -------------------------------------------------------------------------------- /programs/token/src/instructions/initialize_multisig.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice}; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_instruction_view::{cpi::invoke_with_bounds, InstructionAccount, InstructionView}; 5 | use solana_program_error::{ProgramError, ProgramResult}; 6 | 7 | /// Maximum number of multisignature signers. 8 | pub const MAX_MULTISIG_SIGNERS: usize = 11; 9 | 10 | /// Initialize a new Multisig. 11 | /// 12 | /// ### Accounts: 13 | /// 0. `[writable]` The multisig account to initialize. 14 | /// 1. `[]` Rent sysvar 15 | /// 2. ..`2+N`. `[]` The N signer accounts, where N is between 1 and 11. 16 | pub struct InitializeMultisig<'a, 'b> 17 | where 18 | 'a: 'b, 19 | { 20 | /// Multisig Account. 21 | pub multisig: &'a AccountView, 22 | /// Rent sysvar Account. 23 | pub rent_sysvar: &'a AccountView, 24 | /// Signer Accounts 25 | pub signers: &'b [&'a AccountView], 26 | /// The number of signers (M) required to validate this multisignature 27 | /// account. 28 | pub m: u8, 29 | } 30 | 31 | impl InitializeMultisig<'_, '_> { 32 | #[inline(always)] 33 | pub fn invoke(&self) -> ProgramResult { 34 | let &Self { 35 | multisig, 36 | rent_sysvar, 37 | signers, 38 | m, 39 | } = self; 40 | 41 | if signers.len() > MAX_MULTISIG_SIGNERS { 42 | return Err(ProgramError::InvalidArgument); 43 | } 44 | 45 | let num_accounts = 2 + signers.len(); 46 | 47 | // Instruction accounts 48 | const UNINIT_INSTRUCTION_ACCOUNT: MaybeUninit = 49 | MaybeUninit::::uninit(); 50 | let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; 2 + MAX_MULTISIG_SIGNERS]; 51 | 52 | unsafe { 53 | // SAFETY: 54 | // - `instruction_accounts` is sized to 2 + MAX_MULTISIG_SIGNERS 55 | // - Index 0 and 1 are always present 56 | instruction_accounts 57 | .get_unchecked_mut(0) 58 | .write(InstructionAccount::writable(multisig.address())); 59 | instruction_accounts 60 | .get_unchecked_mut(1) 61 | .write(InstructionAccount::readonly(rent_sysvar.address())); 62 | } 63 | 64 | for (instruction_account, signer) in 65 | instruction_accounts[2..].iter_mut().zip(signers.iter()) 66 | { 67 | instruction_account.write(InstructionAccount::readonly(signer.address())); 68 | } 69 | 70 | // Instruction data layout: 71 | // - [0]: instruction discriminator (1 byte, u8) 72 | // - [1]: m (1 byte, u8) 73 | let data = &[2, m]; 74 | 75 | let instruction = InstructionView { 76 | program_id: &crate::ID, 77 | accounts: unsafe { 78 | slice::from_raw_parts(instruction_accounts.as_ptr() as _, num_accounts) 79 | }, 80 | data, 81 | }; 82 | 83 | // Account view array 84 | const UNINIT_VIEW: MaybeUninit<&AccountView> = MaybeUninit::uninit(); 85 | let mut acc_views = [UNINIT_VIEW; 2 + MAX_MULTISIG_SIGNERS]; 86 | 87 | unsafe { 88 | // SAFETY: 89 | // - `account_views` is sized to 2 + MAX_MULTISIG_SIGNERS 90 | // - Index 0 and 1 are always present 91 | acc_views.get_unchecked_mut(0).write(multisig); 92 | acc_views.get_unchecked_mut(1).write(rent_sysvar); 93 | } 94 | 95 | // Fill signer accounts 96 | for (account_view, signer) in acc_views[2..].iter_mut().zip(signers.iter()) { 97 | account_view.write(signer); 98 | } 99 | 100 | invoke_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe { 101 | slice::from_raw_parts(acc_views.as_ptr() as _, num_accounts) 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /programs/token/src/state/multisig.rs: -------------------------------------------------------------------------------- 1 | use core::mem::size_of; 2 | 3 | use solana_account_view::{AccountView, Ref}; 4 | use solana_address::Address; 5 | use solana_program_error::ProgramError; 6 | 7 | use crate::{instructions::MAX_MULTISIG_SIGNERS, ID}; 8 | 9 | /// Multisignature data. 10 | #[repr(C)] 11 | pub struct Multisig { 12 | /// Number of signers required 13 | m: u8, 14 | /// Number of valid signers 15 | n: u8, 16 | /// Is `true` if this structure has been initialized 17 | is_initialized: u8, 18 | /// Signer public keys 19 | signers: [Address; MAX_MULTISIG_SIGNERS], 20 | } 21 | 22 | impl Multisig { 23 | /// The length of the `Multisig` account data. 24 | pub const LEN: usize = size_of::(); 25 | 26 | /// Return a `Multisig` from the given account view. 27 | /// 28 | /// This method performs owner and length validation on `AccountView`, safe borrowing 29 | /// the account data. 30 | #[inline] 31 | pub fn from_account_view(account_view: &AccountView) -> Result, ProgramError> { 32 | if account_view.data_len() != Self::LEN { 33 | return Err(ProgramError::InvalidAccountData); 34 | } 35 | if !account_view.owned_by(&ID) { 36 | return Err(ProgramError::InvalidAccountOwner); 37 | } 38 | Ok(Ref::map(account_view.try_borrow()?, |data| unsafe { 39 | Self::from_bytes_unchecked(data) 40 | })) 41 | } 42 | 43 | /// Return a `Multisig` from the given account view. 44 | /// 45 | /// This method performs owner and length validation on `AccountView`, but does not 46 | /// perform the borrow check. 47 | /// 48 | /// # Safety 49 | /// 50 | /// The caller must ensure that it is safe to borrow the account data (e.g., there are 51 | /// no mutable borrows of the account data). 52 | #[inline] 53 | pub unsafe fn from_account_view_unchecked( 54 | account_view: &AccountView, 55 | ) -> Result<&Self, ProgramError> { 56 | if account_view.data_len() != Self::LEN { 57 | return Err(ProgramError::InvalidAccountData); 58 | } 59 | if account_view.owner() != &ID { 60 | return Err(ProgramError::InvalidAccountOwner); 61 | } 62 | Ok(Self::from_bytes_unchecked(account_view.borrow_unchecked())) 63 | } 64 | 65 | /// Return a `Multisig` from the given bytes. 66 | /// 67 | /// # Safety 68 | /// 69 | /// The caller must ensure that `bytes` contains a valid representation of `Multisig`, and 70 | /// it has the correct length to be interpreted as an instance of `Multisig`. 71 | #[inline(always)] 72 | pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { 73 | &*(bytes.as_ptr() as *const Multisig) 74 | } 75 | 76 | /// Number of signers required to validate the `Multisig` signature. 77 | #[inline(always)] 78 | pub const fn required_signers(&self) -> u8 { 79 | self.m 80 | } 81 | 82 | /// Number of signer addresses present on the `Multisig`. 83 | #[inline(always)] 84 | pub const fn signers_len(&self) -> usize { 85 | self.n as usize 86 | } 87 | 88 | /// Return the signer addresses of the `Multisig`. 89 | #[inline(always)] 90 | pub fn signers(&self) -> &[Address] { 91 | // SAFETY: `self.signers` is an array of `Address` with a fixed size of 92 | // `MAX_MULTISIG_SIGNERS`; `self.signers_len` is always `<= MAX_MULTISIG_SIGNERS` 93 | // and indicates how many of these signers are valid. 94 | unsafe { self.signers.get_unchecked(..self.signers_len()) } 95 | } 96 | 97 | /// Check whether the multisig is initialized or not. 98 | // 99 | // It will return a boolean value indicating whether [`self.is_initialized`] 100 | // is different than `0` or not. 101 | #[inline(always)] 102 | pub fn is_initialized(&self) -> bool { 103 | self.is_initialized != 0 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup environment 2 | 3 | inputs: 4 | cargo-cache-key: 5 | description: The key to cache cargo dependencies. Skips cargo caching if not provided. 6 | required: false 7 | toolchain: 8 | description: Rust toolchain to install. Comma-separated string of [`build`, `format`, `lint`, `test`]. 9 | required: false 10 | components: 11 | description: Cargo components to install. Comma-separated string of [`audit`, `hack``, `release`, `semver-checks]. 12 | required: false 13 | solana: 14 | description: Install Solana if `true`. Defaults to `false`. 15 | required: false 16 | 17 | runs: 18 | using: 'composite' 19 | steps: 20 | - name: Setup pnpm 21 | uses: pnpm/action-setup@v3 22 | 23 | - name: Setup Node.js 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | cache: 'pnpm' 28 | 29 | - name: Install Dependencies 30 | run: pnpm install --frozen-lockfile 31 | shell: bash 32 | 33 | - name: Set Environment Variables 34 | shell: bash 35 | run: pnpm tsx ./scripts/setup/ci.mts 36 | 37 | - name: Install Rust 'build' Toolchain 38 | if: ${{ contains(inputs.toolchain, 'build') }} 39 | uses: dtolnay/rust-toolchain@master 40 | with: 41 | toolchain: ${{ env.TOOLCHAIN_BUILD }} 42 | 43 | - name: Install Rust 'format' Toolchain 44 | if: ${{ contains(inputs.toolchain, 'format') }} 45 | uses: dtolnay/rust-toolchain@master 46 | with: 47 | toolchain: ${{ env.TOOLCHAIN_FORMAT }} 48 | components: rustfmt 49 | 50 | - name: Install Rust 'lint' Toolchain 51 | if: ${{ contains(inputs.toolchain, 'lint') }} 52 | uses: dtolnay/rust-toolchain@master 53 | with: 54 | toolchain: ${{ env.TOOLCHAIN_LINT }} 55 | components: clippy 56 | 57 | - name: Install Rust 'test' Toolchain 58 | if: ${{ contains(inputs.toolchain, 'test') }} 59 | uses: dtolnay/rust-toolchain@master 60 | with: 61 | toolchain: ${{ env.TOOLCHAIN_TEST }} 62 | 63 | - name: Install Solana 64 | if: ${{ inputs.solana == 'true' }} 65 | uses: solana-program/actions/install-solana@v1 66 | with: 67 | version: ${{ env.SOLANA_VERSION }} 68 | cache: true 69 | 70 | - name: Install 'cargo-audit' 71 | if: ${{ contains(inputs.components, 'audit') }} 72 | uses: taiki-e/install-action@v2 73 | with: 74 | tool: cargo-audit 75 | 76 | - name: Install 'cargo-hack' 77 | if: ${{ contains(inputs.components, 'hack') }} 78 | uses: taiki-e/install-action@v2 79 | with: 80 | tool: cargo-hack 81 | 82 | - name: Install 'cargo-release' 83 | if: ${{ contains(inputs.components, 'release') }} 84 | uses: taiki-e/install-action@v2 85 | with: 86 | tool: cargo-release 87 | 88 | - name: Install 'cargo-semver-checks' 89 | if: ${{ contains(inputs.components, 'semver-checks') }} 90 | uses: taiki-e/install-action@v2 91 | with: 92 | tool: cargo-semver-checks@0.41.0 93 | 94 | - name: Install 'cargo-miri' 95 | if: ${{ contains(inputs.toolchain, 'lint') }} 96 | uses: dtolnay/rust-toolchain@master 97 | with: 98 | toolchain: ${{ env.TOOLCHAIN_LINT }} 99 | components: miri 100 | 101 | - name: Install 'cargo-spellcheck' 102 | if: ${{ contains(inputs.components, 'spellcheck') }} 103 | uses: taiki-e/install-action@v2 104 | with: 105 | tool: cargo-spellcheck 106 | 107 | - name: Cache Cargo Dependencies 108 | if: ${{ inputs.cargo-cache-key }} 109 | uses: actions/cache@v4 110 | with: 111 | path: | 112 | ~/.cargo/bin/ 113 | ~/.cargo/registry/index/ 114 | ~/.cargo/registry/cache/ 115 | ~/.cargo/git/db/ 116 | target/ 117 | key: ${{ runner.os }}-${{ inputs.cargo-cache-key }}-${{ hashFiles('**/Cargo.lock') }} 118 | -------------------------------------------------------------------------------- /programs/token-2022/src/state/multisig.rs: -------------------------------------------------------------------------------- 1 | use core::mem::size_of; 2 | 3 | use solana_account_view::{AccountView, Ref}; 4 | use solana_address::Address; 5 | use solana_program_error::ProgramError; 6 | 7 | use crate::{instructions::MAX_MULTISIG_SIGNERS, ID}; 8 | 9 | /// Multisignature data. 10 | #[repr(C)] 11 | pub struct Multisig { 12 | /// Number of signers required 13 | m: u8, 14 | /// Number of valid signers 15 | n: u8, 16 | /// Is `true` if this structure has been initialized 17 | is_initialized: u8, 18 | /// Signer public keys 19 | signers: [Address; MAX_MULTISIG_SIGNERS], 20 | } 21 | 22 | impl Multisig { 23 | /// The length of the `Multisig` account data. 24 | pub const LEN: usize = size_of::(); 25 | 26 | /// Return a `Multisig` from the given account view. 27 | /// 28 | /// This method performs owner and length validation on `AccountView`, safe borrowing 29 | /// the account data. 30 | #[inline] 31 | pub fn from_account_view(account_view: &AccountView) -> Result, ProgramError> { 32 | if account_view.data_len() != Self::LEN { 33 | return Err(ProgramError::InvalidAccountData); 34 | } 35 | if !account_view.owned_by(&ID) { 36 | return Err(ProgramError::InvalidAccountOwner); 37 | } 38 | Ok(Ref::map(account_view.try_borrow()?, |data| unsafe { 39 | Self::from_bytes_unchecked(data) 40 | })) 41 | } 42 | 43 | /// Return a `Multisig` from the given account view. 44 | /// 45 | /// This method performs owner and length validation on `AccountView`, but does not 46 | /// perform the borrow check. 47 | /// 48 | /// # Safety 49 | /// 50 | /// The caller must ensure that it is safe to borrow the account data (e.g., there are 51 | /// no mutable borrows of the account data). 52 | #[inline] 53 | pub unsafe fn from_account_view_unchecked( 54 | account_view: &AccountView, 55 | ) -> Result<&Self, ProgramError> { 56 | if account_view.data_len() != Self::LEN { 57 | return Err(ProgramError::InvalidAccountData); 58 | } 59 | if account_view.owner() != &ID { 60 | return Err(ProgramError::InvalidAccountOwner); 61 | } 62 | Ok(Self::from_bytes_unchecked(account_view.borrow_unchecked())) 63 | } 64 | 65 | /// Return a `Multisig` from the given bytes. 66 | /// 67 | /// # Safety 68 | /// 69 | /// The caller must ensure that `bytes` contains a valid representation of `Multisig`, and 70 | /// it has the correct length to be interpreted as an instance of `Multisig`. 71 | #[inline(always)] 72 | pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { 73 | &*(bytes.as_ptr() as *const Multisig) 74 | } 75 | 76 | /// Number of signers required to validate the `Multisig` signature. 77 | #[inline(always)] 78 | pub const fn required_signers(&self) -> u8 { 79 | self.m 80 | } 81 | 82 | /// Number of signer addresses present on the `Multisig`. 83 | #[inline(always)] 84 | pub const fn signers_len(&self) -> usize { 85 | self.n as usize 86 | } 87 | 88 | /// Return the signer addresses of the `Multisig`. 89 | #[inline(always)] 90 | pub fn signers(&self) -> &[Address] { 91 | // SAFETY: `self.signers` is an array of `Address` with a fixed size of 92 | // `MAX_MULTISIG_SIGNERS`; `self.signers_len` is always `<= MAX_MULTISIG_SIGNERS` 93 | // and indicates how many of these signers are valid. 94 | unsafe { self.signers.get_unchecked(..self.signers_len()) } 95 | } 96 | 97 | /// Check whether the multisig is initialized or not. 98 | // 99 | // It will return a boolean value indicating whether [`self.is_initialized`] 100 | // is different than `0` or not. 101 | #[inline(always)] 102 | pub fn is_initialized(&self) -> bool { 103 | self.is_initialized != 0 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /programs/token-2022/src/instructions/initialize_multisig.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::MaybeUninit, slice}; 2 | 3 | use solana_account_view::AccountView; 4 | use solana_address::Address; 5 | use solana_instruction_view::{cpi::invoke_with_bounds, InstructionAccount, InstructionView}; 6 | use solana_program_error::{ProgramError, ProgramResult}; 7 | 8 | /// Maximum number of multisignature signers. 9 | pub const MAX_MULTISIG_SIGNERS: usize = 11; 10 | 11 | /// Initialize a new Multisig. 12 | /// 13 | /// ### Accounts: 14 | /// 0. `[writable]` The multisig account to initialize. 15 | /// 1. `[]` Rent sysvar 16 | /// 2. `..+N` `[]` The `N` signer accounts, where `N` is `1 <= 17 | /// N <= 11`. 18 | pub struct InitializeMultisig<'a, 'b, 'c> 19 | where 20 | 'a: 'b, 21 | { 22 | /// Multisig Account. 23 | pub multisig: &'a AccountView, 24 | /// Rent sysvar Account. 25 | pub rent_sysvar: &'a AccountView, 26 | /// Signer Accounts 27 | pub signers: &'b [&'a AccountView], 28 | /// The number of signers (M) required to validate this multisignature 29 | /// account. 30 | pub m: u8, 31 | /// Token Program. 32 | pub token_program: &'c Address, 33 | } 34 | 35 | impl InitializeMultisig<'_, '_, '_> { 36 | #[inline(always)] 37 | pub fn invoke(&self) -> ProgramResult { 38 | let &Self { 39 | multisig, 40 | rent_sysvar, 41 | signers, 42 | m, 43 | token_program, 44 | } = self; 45 | 46 | if signers.len() > MAX_MULTISIG_SIGNERS { 47 | return Err(ProgramError::InvalidArgument); 48 | } 49 | 50 | let num_accounts = 2 + signers.len(); 51 | 52 | // Instruction accounts 53 | const UNINIT_INSTRUCTION_ACCOUNT: MaybeUninit = 54 | MaybeUninit::::uninit(); 55 | let mut instruction_accounts = [UNINIT_INSTRUCTION_ACCOUNT; 2 + MAX_MULTISIG_SIGNERS]; 56 | 57 | unsafe { 58 | // SAFETY: 59 | // - `instruction_accounts` is sized to 2 + MAX_MULTISIG_SIGNERS 60 | // - Index 0 and 1 are always present 61 | instruction_accounts 62 | .get_unchecked_mut(0) 63 | .write(InstructionAccount::writable(multisig.address())); 64 | instruction_accounts 65 | .get_unchecked_mut(1) 66 | .write(InstructionAccount::readonly(rent_sysvar.address())); 67 | } 68 | 69 | for (instruction_account, signer) in 70 | instruction_accounts[2..].iter_mut().zip(signers.iter()) 71 | { 72 | instruction_account.write(InstructionAccount::readonly(signer.address())); 73 | } 74 | 75 | // Instruction data layout: 76 | // - [0]: instruction discriminator (1 byte, u8) 77 | // - [1]: m (1 byte, u8) 78 | let data = &[2, m]; 79 | 80 | let instruction = InstructionView { 81 | program_id: token_program, 82 | accounts: unsafe { 83 | slice::from_raw_parts(instruction_accounts.as_ptr() as _, num_accounts) 84 | }, 85 | data, 86 | }; 87 | 88 | // Account view array 89 | const UNINIT_VIEW: MaybeUninit<&AccountView> = MaybeUninit::uninit(); 90 | let mut acc_views = [UNINIT_VIEW; 2 + MAX_MULTISIG_SIGNERS]; 91 | 92 | unsafe { 93 | // SAFETY: 94 | // - `account_views` is sized to 2 + MAX_MULTISIG_SIGNERS 95 | // - Index 0 and 1 are always present 96 | acc_views.get_unchecked_mut(0).write(multisig); 97 | acc_views.get_unchecked_mut(1).write(rent_sysvar); 98 | } 99 | 100 | // Fill signer accounts 101 | for (account_view, signer) in acc_views[2..].iter_mut().zip(signers.iter()) { 102 | account_view.write(signer); 103 | } 104 | 105 | invoke_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe { 106 | slice::from_raw_parts(acc_views.as_ptr() as _, num_accounts) 107 | }) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /programs/system/src/instructions/create_account_with_seed.rs: -------------------------------------------------------------------------------- 1 | use pinocchio::{ 2 | cpi::{invoke_signed, Signer}, 3 | error::ProgramError, 4 | instruction::{InstructionAccount, InstructionView}, 5 | sysvars::rent::Rent, 6 | AccountView, Address, ProgramResult, 7 | }; 8 | 9 | /// Create a new account at an address derived from a base address and a seed. 10 | /// 11 | /// ### Accounts: 12 | /// 0. `[WRITE, SIGNER]` Funding account 13 | /// 1. `[WRITE]` Created account 14 | /// 2. `[SIGNER]` (optional) Base account; the account matching the base address below must be 15 | /// provided as a signer, but may be the same as the funding account 16 | pub struct CreateAccountWithSeed<'a, 'b, 'c> { 17 | /// Funding account. 18 | pub from: &'a AccountView, 19 | 20 | /// New account. 21 | pub to: &'a AccountView, 22 | 23 | /// Base account. 24 | /// 25 | /// The account matching the base [`Address`] below must be provided as 26 | /// a signer, but may be the same as the funding account and provided 27 | /// as account 0. 28 | pub base: Option<&'a AccountView>, 29 | 30 | /// String of ASCII chars, no longer than [`MAX_SEED_LEN`](https://docs.rs/solana-address/latest/solana_address/constant.MAX_SEED_LEN.html). 31 | pub seed: &'b str, 32 | 33 | /// Number of lamports to transfer to the new account. 34 | pub lamports: u64, 35 | 36 | /// Number of bytes of memory to allocate. 37 | pub space: u64, 38 | 39 | /// Address of program that will own the new account. 40 | pub owner: &'c Address, 41 | } 42 | 43 | impl<'a, 'b, 'c> CreateAccountWithSeed<'a, 'b, 'c> { 44 | #[inline(always)] 45 | pub fn with_minimal_balance( 46 | from: &'a AccountView, 47 | to: &'a AccountView, 48 | base: Option<&'a AccountView>, 49 | seed: &'b str, 50 | rent_sysvar: &'a AccountView, 51 | space: u64, 52 | owner: &'c Address, 53 | ) -> Result { 54 | let rent = Rent::from_account_view(rent_sysvar)?; 55 | let lamports = rent.try_minimum_balance(space as usize)?; 56 | 57 | Ok(Self { 58 | from, 59 | to, 60 | base, 61 | seed, 62 | lamports, 63 | space, 64 | owner, 65 | }) 66 | } 67 | 68 | #[inline(always)] 69 | pub fn invoke(&self) -> ProgramResult { 70 | self.invoke_signed(&[]) 71 | } 72 | 73 | #[inline(always)] 74 | pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult { 75 | // Instruction accounts 76 | let instruction_accounts: [InstructionAccount; 3] = [ 77 | InstructionAccount::writable_signer(self.from.address()), 78 | InstructionAccount::writable(self.to.address()), 79 | InstructionAccount::readonly_signer(self.base.unwrap_or(self.from).address()), 80 | ]; 81 | 82 | // instruction data 83 | // - [0..4 ]: instruction discriminator 84 | // - [4..36 ]: base address 85 | // - [36..44]: seed length 86 | // - [44.. ]: seed (max 32) 87 | // - [.. +8]: lamports 88 | // - [.. +8]: account space 89 | // - [.. +32]: owner address 90 | let mut instruction_data = [0; 120]; 91 | instruction_data[0] = 3; 92 | instruction_data[4..36] 93 | .copy_from_slice(self.base.unwrap_or(self.from).address().as_array()); 94 | instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); 95 | 96 | let offset = 44 + self.seed.len(); 97 | instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); 98 | instruction_data[offset..offset + 8].copy_from_slice(&self.lamports.to_le_bytes()); 99 | instruction_data[offset + 8..offset + 16].copy_from_slice(&self.space.to_le_bytes()); 100 | instruction_data[offset + 16..offset + 48].copy_from_slice(self.owner.as_ref()); 101 | 102 | let instruction = InstructionView { 103 | program_id: &crate::ID, 104 | accounts: &instruction_accounts, 105 | data: &instruction_data[..offset + 48], 106 | }; 107 | 108 | invoke_signed( 109 | &instruction, 110 | &[self.from, self.to, self.base.unwrap_or(self.from)], 111 | signers, 112 | ) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /sdk/src/sysvars/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides access to cluster system accounts. 2 | 3 | #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] 4 | use core::hint::black_box; 5 | 6 | #[cfg(any(target_os = "solana", target_arch = "bpf"))] 7 | use crate::syscalls::sol_get_sysvar; 8 | use crate::{error::ProgramError, Address}; 9 | 10 | pub mod clock; 11 | pub mod fees; 12 | pub mod instructions; 13 | pub mod rent; 14 | pub mod slot_hashes; 15 | 16 | /// Return value indicating that the `offset + length` is greater than the length of 17 | /// the sysvar data. 18 | // 19 | // Defined in the bpf loader as [`OFFSET_LENGTH_EXCEEDS_SYSVAR`](https://github.com/anza-xyz/agave/blob/master/programs/bpf_loader/src/syscalls/sysvar.rs#L172). 20 | #[cfg(any(target_os = "solana", target_arch = "bpf"))] 21 | const OFFSET_LENGTH_EXCEEDS_SYSVAR: u64 = 1; 22 | 23 | /// Return value indicating that the sysvar was not found. 24 | // 25 | // Defined in the bpf loader as [`SYSVAR_NOT_FOUND`](https://github.com/anza-xyz/agave/blob/master/programs/bpf_loader/src/syscalls/sysvar.rs#L171). 26 | #[cfg(any(target_os = "solana", target_arch = "bpf"))] 27 | const SYSVAR_NOT_FOUND: u64 = 2; 28 | 29 | /// A type that holds sysvar data. 30 | pub trait Sysvar: Sized { 31 | /// Load the sysvar directly from the runtime. 32 | /// 33 | /// This is the preferred way to load a sysvar. Calling this method does not 34 | /// incur any deserialization overhead, and does not require the sysvar 35 | /// account to be passed to the program. 36 | /// 37 | /// Not all sysvars support this method. If not, it returns 38 | /// [`ProgramError::UnsupportedSysvar`]. 39 | fn get() -> Result { 40 | Err(ProgramError::UnsupportedSysvar) 41 | } 42 | } 43 | 44 | /// Implements the [`Sysvar::get`] method for both SBF and host targets. 45 | #[macro_export] 46 | macro_rules! impl_sysvar_get { 47 | ($syscall_name:ident) => { 48 | fn get() -> Result { 49 | let mut var = core::mem::MaybeUninit::::uninit(); 50 | let var_addr = var.as_mut_ptr() as *mut _ as *mut u8; 51 | 52 | #[cfg(any(target_os = "solana", target_arch = "bpf"))] 53 | let result = unsafe { $crate::syscalls::$syscall_name(var_addr) }; 54 | 55 | #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] 56 | let result = core::hint::black_box(var_addr as *const _ as u64); 57 | 58 | match result { 59 | $crate::SUCCESS => { 60 | // SAFETY: The syscall initialized the memory. 61 | Ok(unsafe { var.assume_init() }) 62 | } 63 | // Unexpected errors are folded into `UnsupportedSysvar`. 64 | _ => Err($crate::error::ProgramError::UnsupportedSysvar), 65 | } 66 | } 67 | }; 68 | } 69 | 70 | /// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar` 71 | /// syscall. 72 | /// 73 | /// # Safety 74 | /// 75 | /// The caller must ensure that the `dst` pointer is valid and has enough space 76 | /// to hold the requested `len` bytes of data. 77 | #[inline] 78 | pub unsafe fn get_sysvar_unchecked( 79 | dst: *mut u8, 80 | sysvar_id: &Address, 81 | offset: usize, 82 | len: usize, 83 | ) -> Result<(), ProgramError> { 84 | #[cfg(any(target_os = "solana", target_arch = "bpf"))] 85 | { 86 | let result = unsafe { 87 | sol_get_sysvar( 88 | sysvar_id as *const _ as *const u8, 89 | dst, 90 | offset as u64, 91 | len as u64, 92 | ) 93 | }; 94 | 95 | match result { 96 | crate::SUCCESS => Ok(()), 97 | OFFSET_LENGTH_EXCEEDS_SYSVAR => Err(ProgramError::InvalidArgument), 98 | SYSVAR_NOT_FOUND => Err(ProgramError::UnsupportedSysvar), 99 | // Unexpected errors are folded into `UnsupportedSysvar`. 100 | _ => Err(ProgramError::UnsupportedSysvar), 101 | } 102 | } 103 | 104 | #[cfg(not(any(target_os = "solana", target_arch = "bpf")))] 105 | { 106 | black_box((dst, sysvar_id, offset, len)); 107 | Ok(()) 108 | } 109 | } 110 | 111 | /// Handler for retrieving a slice of sysvar data from the `sol_get_sysvar` 112 | /// syscall. 113 | #[inline(always)] 114 | pub fn get_sysvar(dst: &mut [u8], sysvar_id: &Address, offset: usize) -> Result<(), ProgramError> { 115 | // SAFETY: Use the length of the slice as the length parameter. 116 | unsafe { get_sysvar_unchecked(dst.as_mut_ptr(), sysvar_id, offset, dst.len()) } 117 | } 118 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | crate: 7 | description: Crate 8 | required: true 9 | default: sdk 10 | type: choice 11 | options: 12 | - programs/associated-token-account 13 | - programs/memo 14 | - programs/system 15 | - programs/token 16 | - programs/token-2022 17 | - sdk 18 | level: 19 | description: Level 20 | required: true 21 | default: patch 22 | type: choice 23 | options: 24 | - patch 25 | - minor 26 | - major 27 | - rc 28 | - beta 29 | - alpha 30 | - release 31 | - version 32 | version: 33 | description: Version (used with level "version") 34 | required: false 35 | type: string 36 | dry_run: 37 | description: Dry run 38 | required: true 39 | default: true 40 | type: boolean 41 | create_release: 42 | description: Create a GitHub release 43 | required: true 44 | type: boolean 45 | default: true 46 | 47 | env: 48 | CACHE: true 49 | 50 | jobs: 51 | semver_check: 52 | name: Check Semver 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Git checkout 56 | uses: actions/checkout@v4 57 | 58 | - name: Setup Environment 59 | uses: ./.github/actions/setup 60 | with: 61 | cargo-cache-key: cargo-semver-checks 62 | toolchain: test 63 | components: release, semver-checks 64 | 65 | - name: Set Version 66 | run: | 67 | if [ "${{ inputs.level }}" == "version" ]; then 68 | LEVEL=${{ inputs.version }} 69 | else 70 | LEVEL=${{ inputs.level }} 71 | fi 72 | 73 | git config --global user.email "github-actions@github.com" 74 | git config --global user.name "github-actions" 75 | 76 | cargo release $LEVEL --manifest-path ${{ inputs.crate }}/Cargo.toml --no-tag --no-publish --no-push --no-confirm --execute 77 | 78 | - name: Check semver 79 | run: | 80 | pnpm semver ${{ inputs.crate }} 81 | 82 | 83 | publish_release: 84 | name: Publish 85 | runs-on: ubuntu-latest 86 | needs: semver_check 87 | permissions: 88 | contents: write 89 | steps: 90 | - name: Ensure CARGO_REGISTRY_TOKEN variable is set 91 | env: 92 | token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 93 | if: ${{ env.token == '' }} 94 | run: | 95 | echo "The CARGO_REGISTRY_TOKEN secret variable is not set" 96 | echo "Go to \"Settings\" -> \"Secrets and variables\" -> \"Actions\" -> \"New repository secret\"." 97 | exit 1 98 | 99 | - name: Git checkout 100 | uses: actions/checkout@v4 101 | with: 102 | token: ${{ secrets.ANZA_TEAM_PAT }} 103 | fetch-depth: 0 # get the whole history for git-cliff 104 | 105 | - name: Setup Environment 106 | uses: ./.github/actions/setup 107 | with: 108 | cargo-cache-key: cargo-publish 109 | toolchain: test 110 | components: release 111 | solana: true 112 | 113 | - name: Build 114 | run: pnpm build-sbf ${{ inputs.crate }} 115 | 116 | - name: Test 117 | run: pnpm test ${{ inputs.crate }} 118 | 119 | - name: Set Git Author 120 | run: | 121 | git config --global user.email "github-actions@github.com" 122 | git config --global user.name "github-actions" 123 | 124 | - name: Publish Crate 125 | id: publish 126 | env: 127 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 128 | run: | 129 | if [ "${{ inputs.level }}" == "version" ]; then 130 | LEVEL=${{ inputs.version }} 131 | else 132 | LEVEL=${{ inputs.level }} 133 | fi 134 | 135 | if [ "${{ inputs.dry_run }}" == "true" ]; then 136 | OPTIONS="--dry-run" 137 | else 138 | OPTIONS="" 139 | fi 140 | 141 | pnpm tsx ./scripts/publish.mts ${{ inputs.crate }} $LEVEL $OPTIONS 142 | 143 | - name: Generate a changelog 144 | if: github.event.inputs.dry_run != 'true' && github.event.inputs.create_release == 'true' 145 | uses: orhun/git-cliff-action@v4 146 | with: 147 | config: ".github/cliff.toml" 148 | args: ${{ steps.publish.outputs.old_git_tag }}..HEAD --include-path "${{ inputs.crate }}/**" --github-repo ${{ github.repository }} 149 | env: 150 | OUTPUT: CHANGELOG.md 151 | GITHUB_REPO: ${{ github.repository }} 152 | 153 | - name: Create GitHub release 154 | if: github.event.inputs.dry_run != 'true' && github.event.inputs.create_release == 'true' 155 | uses: ncipollo/release-action@v1 156 | with: 157 | tag: ${{ steps.publish.outputs.new_git_tag }} 158 | bodyFile: CHANGELOG.md 159 | --------------------------------------------------------------------------------