From cb2bbb0c9739e6e4400264fe80a4d8294d5f13f4 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 3 Jun 2026 17:49:12 +0100 Subject: [PATCH 1/3] Assert non-zero lamports --- program/src/processor/allocate.rs | 8 +++----- program/src/processor/initialize.rs | 8 +++----- program/tests/initialize.rs | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/program/src/processor/allocate.rs b/program/src/processor/allocate.rs index f99006f..f02e10a 100644 --- a/program/src/processor/allocate.rs +++ b/program/src/processor/allocate.rs @@ -1,7 +1,6 @@ use pinocchio::{ cpi::{Seed, Signer}, error::ProgramError, - sysvars::{rent::Rent, Sysvar}, AccountView, ProgramResult, }; use pinocchio_system::instructions::{Allocate, Assign}; @@ -139,10 +138,9 @@ pub fn allocate(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra _ => return Err(ProgramError::InvalidAccountData), } - // `buffer` length is within the permitted limit. - let minimum_balance = Rent::get()?.minimum_balance_unchecked(buffer.data_len()); - - if buffer.lamports() < minimum_balance { + // The buffer account must have non-zero lamports. The runtime will then + // ensure that the account is rent exempt. + if buffer.lamports() == 0 { return Err(ProgramError::AccountNotRentExempt); } diff --git a/program/src/processor/initialize.rs b/program/src/processor/initialize.rs index 89a7f24..0090177 100644 --- a/program/src/processor/initialize.rs +++ b/program/src/processor/initialize.rs @@ -4,7 +4,6 @@ use pinocchio::{ cpi::{Seed, Signer}, error::ProgramError, instruction::seeds, - sysvars::{rent::Rent, Sysvar}, AccountView, Address, ProgramResult, }; use pinocchio_system::instructions::{Allocate, Assign}; @@ -150,10 +149,9 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog } .invoke_signed(signer)?; - // `space` is guranteed to be within the permitted limits. - let minimum_balance = Rent::get()?.minimum_balance_unchecked(space); - - if metadata.lamports() < minimum_balance { + // The metadata account must have non-zero lamports. The runtime will + // then ensure that the account is rent exempt. + if metadata.lamports() == 0 { return Err(ProgramError::AccountNotRentExempt); } diff --git a/program/tests/initialize.rs b/program/tests/initialize.rs index 44088a5..6b737cd 100644 --- a/program/tests/initialize.rs +++ b/program/tests/initialize.rs @@ -378,7 +378,7 @@ fn fail_initialize_with_wrong_metadata_pda() { } #[test] -fn fail_initialize_without_rent_exemption() { +fn fail_initialize_unfunded_metadata() { let authority_key = Pubkey::new_unique(); let program_data_key = Pubkey::new_unique(); From 07e65ea2ab9a8b423c37f44b6e1d9856b0f43088 Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 3 Jun 2026 17:49:20 +0100 Subject: [PATCH 2/3] Remove rent check --- program/src/processor/extend.rs | 20 ++++---------------- program/src/processor/write.rs | 14 ++------------ program/tests/extend.rs | 25 ------------------------- program/tests/write.rs | 25 ------------------------- 4 files changed, 6 insertions(+), 78 deletions(-) diff --git a/program/src/processor/extend.rs b/program/src/processor/extend.rs index d0e4fc4..a2bf53e 100644 --- a/program/src/processor/extend.rs +++ b/program/src/processor/extend.rs @@ -1,10 +1,5 @@ use core::mem::size_of; -use pinocchio::{ - account::AccountView, - error::ProgramError, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, Resize, -}; +use pinocchio::{account::AccountView, error::ProgramError, ProgramResult, Resize}; use crate::state::{buffer::Buffer, AccountDiscriminator}; @@ -39,7 +34,7 @@ pub fn extend(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramR // - must be a buffer or metadata account // - must have a valid authority // - must be rent exempt (pre-funded account) since we are reallocating the buffer - // account + // account (checked by the runtime) if account.is_data_empty() { return Err(ProgramError::InvalidAccountData); @@ -62,18 +57,11 @@ pub fn extend(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramR } } + // Reallocates the account size. + // The length of the data is never more than `10_000_000`; adding a `u16` // will never overflow the `usize` limit. let length = account.data_len() + extend_length as usize; - - let minimum_balance = Rent::get()?.try_minimum_balance(length)?; - - if account.lamports() < minimum_balance { - return Err(ProgramError::AccountNotRentExempt); - } - - // Reallocates the account size. - // SAFETY: `account` is not borrowed at this point. unsafe { account.resize_unchecked(length) } } diff --git a/program/src/processor/write.rs b/program/src/processor/write.rs index 60efd3a..2dafc9d 100644 --- a/program/src/processor/write.rs +++ b/program/src/processor/write.rs @@ -1,10 +1,6 @@ use core::cmp::max; -use pinocchio::{ - error::ProgramError, - sysvars::{rent::Rent, Sysvar}, - AccountView, ProgramResult, Resize, -}; +use pinocchio::{error::ProgramError, AccountView, ProgramResult, Resize}; use crate::state::{buffer::Buffer, header::Header, AccountDiscriminator}; @@ -36,7 +32,7 @@ pub fn write(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramRe // target_buffer // - must be initialized // - must be rent exempt (pre-funded account) since we are reallocating the buffer - // account + // account (checked by the runtime) // // source_buffer (if `args.data()` is empty) // - must be initialized @@ -95,12 +91,6 @@ pub fn write(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramRe (max(data.len(), offset + source_data.len()), source_data) }; - let minimum_balance = Rent::get()?.try_minimum_balance(required_length)?; - - if target_buffer.lamports() < minimum_balance { - return Err(ProgramError::AccountNotRentExempt); - } - // Writes the source data to the buffer account. // SAFETY: `target_buffer` account is not borrowed at this point. diff --git a/program/tests/extend.rs b/program/tests/extend.rs index ad11d0e..9cda525 100644 --- a/program/tests/extend.rs +++ b/program/tests/extend.rs @@ -299,31 +299,6 @@ fn fail_extend_with_wrong_authority() { ); } -#[test] -fn fail_extend_without_rent_for_growth() { - let buffer_key = Pubkey::new_unique(); - let buffer_account = - create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); - - process_instructions( - &[ - ( - &allocate(&buffer_key, &buffer_key, None, None, None), - &[Check::success()], - ), - ( - &extend(&buffer_key, &buffer_key, None, None, 1), - &[Check::err(ProgramError::AccountNotRentExempt)], - ), - ], - &[ - (buffer_key, buffer_account), - (PROGRAM_ID, Account::default()), - keyed_account_for_system_program(), - ], - ); -} - #[test] fn fail_extend_uninitialized_account() { let account_key = Pubkey::new_unique(); diff --git a/program/tests/write.rs b/program/tests/write.rs index b5d89a5..5fa17c6 100644 --- a/program/tests/write.rs +++ b/program/tests/write.rs @@ -265,28 +265,3 @@ fn fail_write_from_same_buffer() { ], ); } - -#[test] -fn fail_write_without_rent_for_growth() { - let buffer_key = Pubkey::new_unique(); - let buffer_account = - create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); - - process_instructions( - &[ - ( - &allocate(&buffer_key, &buffer_key, None, None, None), - &[Check::success()], - ), - ( - &write(&buffer_key, &buffer_key, None, 0, &[1]), - &[Check::err(ProgramError::AccountNotRentExempt)], - ), - ], - &[ - (buffer_key, buffer_account), - (PROGRAM_ID, Account::default()), - keyed_account_for_system_program(), - ], - ); -} From 6aacee2813accd674247b944c25d0fb25e668a2f Mon Sep 17 00:00:00 2001 From: febo Date: Wed, 3 Jun 2026 19:25:29 +0100 Subject: [PATCH 3/3] Update comments --- program/src/processor/allocate.rs | 3 ++- program/src/processor/extend.rs | 2 +- program/src/processor/initialize.rs | 14 ++++++++------ program/src/processor/set_data.rs | 2 ++ program/src/processor/write.rs | 4 ++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/program/src/processor/allocate.rs b/program/src/processor/allocate.rs index f02e10a..a0c2bfc 100644 --- a/program/src/processor/allocate.rs +++ b/program/src/processor/allocate.rs @@ -39,7 +39,8 @@ pub fn allocate(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra // buffer // - if pda, must have the correct derivation + seed; otherwise must be // a signer (match the authority) - // - must be rent exempt (pre-funded account) + // - must have lamports (pre-funded account); the runtime will ensure + // that the account is rent exempt let (is_pda, bump, canonical) = if buffer.address() == authority.address() { // A keypair buffer does not require a `seed` value. diff --git a/program/src/processor/extend.rs b/program/src/processor/extend.rs index a2bf53e..a3ab5e5 100644 --- a/program/src/processor/extend.rs +++ b/program/src/processor/extend.rs @@ -33,7 +33,7 @@ pub fn extend(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramR // - authority must be a signer (validated by `validate_authority`) // - must be a buffer or metadata account // - must have a valid authority - // - must be rent exempt (pre-funded account) since we are reallocating the buffer + // - must be rent exempt (pre-funded account) since we are reallocating the // account (checked by the runtime) if account.is_data_empty() { diff --git a/program/src/processor/initialize.rs b/program/src/processor/initialize.rs index 0090177..dd9846a 100644 --- a/program/src/processor/initialize.rs +++ b/program/src/processor/initialize.rs @@ -62,6 +62,8 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog // the remaining instruction data is used as the metadata account data; OR be a // pre-allocated buffer (i.e. `discriminator = 1`), in which case, no remaining // instruction data is allowed as the data must already be written to the account + // - must have lamports (pre-funded account); the runtime will ensure that the + // account is rent exempt let (derived_metadata, bump) = if canonical { derive_program_address(&[program.address().as_array(), args.seed.as_ref()], &ID) @@ -149,12 +151,6 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog } .invoke_signed(signer)?; - // The metadata account must have non-zero lamports. The runtime will - // then ensure that the account is rent exempt. - if metadata.lamports() == 0 { - return Err(ProgramError::AccountNotRentExempt); - } - // SAFETY: scoped mutable borrow of `metadata` account data. The data is // guaranteed to be allocated and assigned to the program. let metadata_account_data = unsafe { metadata.borrow_unchecked_mut() }; @@ -177,6 +173,12 @@ pub fn initialize(accounts: &mut [AccountView], instruction_data: &[u8]) -> Prog } }; + // The metadata account must have lamports. The runtime will + // then ensure that the account is rent exempt. + if metadata.lamports() == 0 { + return Err(ProgramError::AccountNotRentExempt); + } + // Initialize the metadata account. // SAFETY: there are no other active borrows to `metadata` account data and diff --git a/program/src/processor/set_data.rs b/program/src/processor/set_data.rs index 44a0d11..100fc4e 100644 --- a/program/src/processor/set_data.rs +++ b/program/src/processor/set_data.rs @@ -46,6 +46,8 @@ pub fn set_data(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra // metadata // - must be initialized // - must be mutable + // - must be rent exempt (pre-funded account) since we are reallocating the + // account (checked by the runtime) // SAFETY: Scoped immutable borrow of `metadata` account data for validation. let metadata_account_data = unsafe { metadata.borrow_unchecked() }; diff --git a/program/src/processor/write.rs b/program/src/processor/write.rs index 2dafc9d..90be4c5 100644 --- a/program/src/processor/write.rs +++ b/program/src/processor/write.rs @@ -31,8 +31,8 @@ pub fn write(accounts: &mut [AccountView], instruction_data: &[u8]) -> ProgramRe // target_buffer // - must be initialized - // - must be rent exempt (pre-funded account) since we are reallocating the buffer - // account (checked by the runtime) + // - must be rent exempt (pre-funded account) since we are reallocating + // the account (checked by the runtime) // // source_buffer (if `args.data()` is empty) // - must be initialized