Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 187 additions & 86 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ resolver = "2"
members = ["program", "clients/rust"]

[workspace.metadata.cli]
solana = "2.3.4"
solana = "3.1.14"

[workspace.metadata.toolchains]
format = "nightly-2025-02-16"
lint = "nightly-2025-02-16"
format = "nightly-2026-01-22"
lint = "nightly-2026-01-22"

[workspace.metadata.spellcheck]
config = "scripts/spellcheck.toml"
5 changes: 5 additions & 0 deletions clients/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Disable warnings for generated code since these need to be fixed
// in the codama generator.
#![allow(dead_code)]
#![allow(clippy::io_other_error)]

mod generated;
mod hooked;

Expand Down
2 changes: 1 addition & 1 deletion codama.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default {
{
formatCode: true,
crateFolder: 'clients/rust',
toolchain: '+nightly-2025-02-16',
toolchain: '+nightly-2026-01-22',
anchorTraits: false,
linkOverrides: {
definedTypes: {
Expand Down
6 changes: 3 additions & 3 deletions program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ name = "spl_program_metadata"
crate-type = ["cdylib", "lib"]

[dependencies]
pinocchio = "0.8"
pinocchio-pubkey = "0.2"
pinocchio-system = "0.2"
pinocchio = { version = "0.11.1", features = ["account-resize"] }
pinocchio-system = "0.6.1"
solana-program-log = { version = "1.0", default-features = false }
solana-security-txt = "1.1.1"

[dev-dependencies]
Expand Down
28 changes: 15 additions & 13 deletions program/src/entrypoint.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use pinocchio::{
account_info::AccountInfo, default_panic_handler, no_allocator, program_entrypoint,
program_error::ProgramError, pubkey::Pubkey, ProgramResult,
default_panic_handler, error::ProgramError, no_allocator, program_entrypoint, AccountView,
Address, ProgramResult,
};
#[cfg(feature = "logging")]
use solana_program_log::log;

use crate::{
instruction::ProgramMetadataInstruction,
Expand All @@ -19,8 +21,8 @@ default_panic_handler!();
no_allocator!();

fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_program_id: &Address,
accounts: &mut [AccountView],
instruction_data: &[u8],
) -> ProgramResult {
let [instruction, data @ ..] = instruction_data else {
Expand All @@ -31,63 +33,63 @@ fn process_instruction(
// 0 - Write
ProgramMetadataInstruction::Write => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Write");
log("Instruction: Write");

write(accounts, data)
}
// 1 - Initialize
ProgramMetadataInstruction::Initialize => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Initialize");
log("Instruction: Initialize");

initialize(accounts, data)
}
// 2 - SetAuthority
ProgramMetadataInstruction::SetAuthority => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: SetAuthority");
log("Instruction: SetAuthority");

set_authority(accounts, data)
}
// 3 - SetData
ProgramMetadataInstruction::SetData => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: SetData");
log("Instruction: SetData");

set_data(accounts, data)
}
// 4 - SetImmutable
ProgramMetadataInstruction::SetImmutable => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: SetImmutable");
log("Instruction: SetImmutable");

set_immutable(accounts)
}
// 5 - Trim
ProgramMetadataInstruction::Trim => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Trim");
log("Instruction: Trim");

trim(accounts)
}
// 6 - Close
ProgramMetadataInstruction::Close => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Close");
log("Instruction: Close");

close(accounts)
}
// 7 - Allocate
ProgramMetadataInstruction::Allocate => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Allocate");
log("Instruction: Allocate");

allocate(accounts, data)
}
// 8 - Extend
ProgramMetadataInstruction::Extend => {
#[cfg(feature = "logging")]
pinocchio::msg!("Instruction: Extend");
log("Instruction: Extend");

extend(accounts, data)
}
Expand Down
2 changes: 1 addition & 1 deletion program/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use pinocchio::program_error::ProgramError;
use pinocchio::error::ProgramError;

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ProgramMetadataError {
Expand Down
2 changes: 1 addition & 1 deletion program/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use pinocchio::program_error::ProgramError;
use pinocchio::error::ProgramError;

/// Instructions supported by the program metadata program.
#[derive(Clone, Copy, Debug)]
Expand Down
2 changes: 1 addition & 1 deletion program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod instruction;
pub mod processor;
pub mod state;

pinocchio_pubkey::declare_id!("ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S");
pinocchio::address::declare_id!("ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S");

solana_security_txt::security_txt! {
// Required fields
Expand Down
54 changes: 29 additions & 25 deletions program/src/processor/allocate.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use pinocchio::{
account_info::AccountInfo,
instruction::{Seed, Signer},
program_error::ProgramError,
pubkey::find_program_address,
cpi::{Seed, Signer},
error::ProgramError,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
AccountView, ProgramResult,
};
use pinocchio_system::instructions::{Allocate, Assign};

use crate::{
error::ProgramMetadataError,
processor::derive_program_address,
state::{buffer::Buffer, AccountDiscriminator, SEED_LEN},
ID,
};
Expand All @@ -18,7 +17,7 @@ use super::is_program_authority;

/// Processor for the [`Allocate`](`crate::instruction::ProgramMetadataInstruction::Allocate`)
/// instruction.
pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
pub fn allocate(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramResult {
// Access accounts.

let [buffer, authority, program, program_data, _system_program, _remaining @ ..] = accounts
Expand All @@ -43,7 +42,7 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
// a signer (match the authority)
// - must be rent exempt (pre-funded account)

let (is_pda, bump, canonical) = if buffer.key() == authority.key() {
let (is_pda, bump, canonical) = if buffer.address() == authority.address() {
// A keypair buffer does not require a `seed` value.
if !instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
Expand All @@ -60,17 +59,22 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
return Err(ProgramMetadataError::NotExecutableAccount.into());
}

let canonical = is_program_authority(program, program_data, authority.key())?;
let canonical = is_program_authority(program, program_data, authority.address())?;

let seeds: &[&[u8]] = if canonical {
&[program.key(), instruction_data]
let (derived_metadata, bump) = if canonical {
derive_program_address(&[program.address().as_array(), instruction_data], &ID)
} else {
&[program.key(), authority.key(), instruction_data]
derive_program_address(
&[
program.address().as_array(),
authority.address().as_array(),
instruction_data,
],
&ID,
)
};

let (derived_metadata, bump) = find_program_address(seeds, &ID);

if buffer.key() != &derived_metadata {
if buffer.address() != &derived_metadata {
return Err(ProgramError::InvalidSeeds);
}

Expand All @@ -90,7 +94,7 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
buffer,
space,
&[
Seed::from(program.key()),
Seed::from(program.address().as_array()),
Seed::from(instruction_data),
Seed::from(&[bump]),
],
Expand All @@ -100,8 +104,8 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
buffer,
space,
&[
Seed::from(program.key()),
Seed::from(authority.key()),
Seed::from(program.address().as_array()),
Seed::from(authority.address().as_array()),
Seed::from(instruction_data),
Seed::from(&[bump]),
],
Expand All @@ -115,7 +119,7 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
// buffer account to the program (the runtime only allows assigning
// zeroed accounts, so there is no need to check the contents of the
// account).
if !buffer.is_owned_by(&crate::ID) {
if !buffer.owned_by(&crate::ID) {
Assign {
account: buffer,
owner: &crate::ID,
Expand All @@ -125,7 +129,7 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
// Checks whether the buffer account is already initialized or not.
//
// SAFETY: scoped borrow of the `buffer` account data.
let data = unsafe { buffer.borrow_data_unchecked() };
let data = unsafe { buffer.borrow_unchecked() };

if data[0] != AccountDiscriminator::Empty as u8 {
return Err(ProgramError::AccountAlreadyInitialized);
Expand All @@ -135,7 +139,8 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
_ => return Err(ProgramError::InvalidAccountData),
}

let minimum_balance = Rent::get()?.minimum_balance(buffer.data_len());
// `buffer` length is within the permitted limit.
let minimum_balance = Rent::get()?.minimum_balance_unchecked(buffer.data_len());

if buffer.lamports() < minimum_balance {
return Err(ProgramError::AccountNotRentExempt);
Expand All @@ -145,13 +150,12 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes

// SAFETY: single mutable borrow of the `buffer` account data. The legth of the buffer account
// data has been checked to be at least `Buffer::LEN` and uninitialized.
let buffer_header =
unsafe { Buffer::from_bytes_mut_unchecked(buffer.borrow_mut_data_unchecked()) };
let buffer_header = unsafe { Buffer::from_bytes_mut_unchecked(buffer.borrow_unchecked_mut()) };
buffer_header.discriminator = AccountDiscriminator::Buffer as u8;
buffer_header.authority = (*authority.key()).into();
buffer_header.authority = (*authority.address()).into();

if is_pda {
buffer_header.program = (*program.key()).into();
buffer_header.program = (*program.address()).into();
buffer_header.canonical = canonical as u8;
buffer_header
.seed
Expand All @@ -165,7 +169,7 @@ pub fn allocate(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramRes
///
/// When the `account` is a PDA, the `seeds` are used to create the signer.
#[inline(always)]
fn allocate_and_assign(account: &AccountInfo, space: u64, seeds: &[Seed]) -> ProgramResult {
fn allocate_and_assign(account: &AccountView, space: u64, seeds: &[Seed]) -> ProgramResult {
let signer: &[Signer] = if seeds.is_empty() {
&[]
} else {
Expand Down
27 changes: 13 additions & 14 deletions program/src/processor/close.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult};
use pinocchio::{account::AccountView, error::ProgramError, ProgramResult};

use crate::state::{buffer::Buffer, AccountDiscriminator};

use super::{validate_authority, validate_metadata};

/// Processor for the [`Close`](`crate::instruction::ProgramMetadataInstruction::Close`)
/// instruction.
pub fn close(accounts: &[AccountInfo]) -> ProgramResult {
pub fn close(accounts: &mut [AccountView]) -> ProgramResult {
// Access accounts.

let [account, authority, program, program_data, destination] = accounts else {
Expand All @@ -29,15 +29,15 @@ pub fn close(accounts: &[AccountInfo]) -> ProgramResult {
// - must have data
// - authority must match (if not a keypair buffer)

let account_data = if account.data_is_empty() {
let account_data = if account.is_data_empty() {
return Err(ProgramError::UninitializedAccount);
} else {
unsafe { account.borrow_data_unchecked() }
unsafe { account.borrow_unchecked() }
};

// We only need to validate the authority if it is not a keypair buffer,
// since we already validated that the authority is a signer.
if account.key() != authority.key() {
if account.address() != authority.address() {
match AccountDiscriminator::try_from(account_data[0])? {
AccountDiscriminator::Buffer => {
let buffer = unsafe { Buffer::from_bytes_unchecked(account_data) };
Expand All @@ -53,16 +53,15 @@ pub fn close(accounts: &[AccountInfo]) -> ProgramResult {

// Move the lamports to the destination account and close the account.

// SAFETY: There are no active borrows to accounts' lamports.
unsafe {
let account_lamports = account.borrow_mut_lamports_unchecked();
let destination_lamports = destination.borrow_mut_lamports_unchecked();
let account_lamports = account.lamports();
let destination_lamports = destination.lamports();

*destination_lamports = destination_lamports
.checked_add(*account_lamports)
.ok_or(ProgramError::ArithmeticOverflow)?;
*account_lamports = 0;
}
destination.set_lamports(
destination_lamports
.checked_add(account_lamports)
.ok_or(ProgramError::ArithmeticOverflow)?,
);
account.set_lamports(0);

account.close()
}
Loading
Loading