From ac35c1991288feb0137b2ac2dde85120121fc1b9 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 16 Jan 2025 12:14:41 +0000 Subject: [PATCH 1/5] Add PdaInfo trait --- program/src/processor/mod.rs | 12 ++++++------ program/src/state/buffer.rs | 16 +++++++++++++++- program/src/state/header.rs | 17 ++++++++++++++++- program/src/state/mod.rs | 13 +++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index c45fa9d..7c7b139 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; -use crate::state::{header::Header, AccountDiscriminator}; +use crate::state::{header::Header, AccountDiscriminator, PdaInfo}; pub mod allocate; pub mod close; @@ -113,8 +113,8 @@ fn validate_metadata(metadata: &AccountInfo) -> Result<&Header, ProgramError> { /// - [explicit] The `authority` account must match the authority set on the `metadata` account OR /// it must be the program upgrade authority if the `metadata` account is canonical (see `is_program_authority`). #[inline(always)] -fn validate_authority( - metadata_header: &Header, +fn validate_authority( + pda_info: &T, authority: &AccountInfo, program: &AccountInfo, program_data: &AccountInfo, @@ -124,14 +124,14 @@ fn validate_authority( return Err(ProgramError::MissingRequiredSignature); } // The authority is the set authority. - let explicitly_authorized = match metadata_header.authority.as_ref() { + let explicitly_authorized = match pda_info.authority() { Some(metadata_authority) => metadata_authority == authority.key(), None => false, }; // The authority is the program upgrade authority for canonical metadata accounts. let authorized = explicitly_authorized - || (metadata_header.canonical() - && program.key() == &metadata_header.program + || (pda_info.is_canonical() + && program.key() == pda_info.program() && is_program_authority(program, program_data, authority.key())?); if !authorized { return Err(ProgramError::IncorrectAuthority); diff --git a/program/src/state/buffer.rs b/program/src/state/buffer.rs index f5e6f64..d2ec979 100644 --- a/program/src/state/buffer.rs +++ b/program/src/state/buffer.rs @@ -1,6 +1,6 @@ use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; -use super::{AccountDiscriminator, ZeroableOption, SEED_LEN}; +use super::{AccountDiscriminator, PdaInfo, ZeroableOption, SEED_LEN}; /// Buffer account header. #[repr(C)] @@ -65,3 +65,17 @@ impl Buffer { &mut *(bytes.as_mut_ptr() as *mut Self) } } + +impl PdaInfo for Buffer { + fn program(&self) -> &Pubkey { + self.program.as_ref().unwrap() + } + + fn authority(&self) -> Option<&Pubkey> { + self.authority.as_ref() + } + + fn is_canonical(&self) -> bool { + self.canonical() && self.authority().is_none() + } +} diff --git a/program/src/state/header.rs b/program/src/state/header.rs index 1de5dcd..8291588 100644 --- a/program/src/state/header.rs +++ b/program/src/state/header.rs @@ -1,7 +1,8 @@ use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; use super::{ - AccountDiscriminator, Compression, DataSource, Encoding, Format, ZeroableOption, SEED_LEN, + AccountDiscriminator, Compression, DataSource, Encoding, Format, PdaInfo, ZeroableOption, + SEED_LEN, }; /// Metadata account header. @@ -106,3 +107,17 @@ impl Header { &mut *(bytes.as_mut_ptr() as *mut Self) } } + +impl PdaInfo for Header { + fn program(&self) -> &Pubkey { + &self.program + } + + fn authority(&self) -> Option<&Pubkey> { + self.authority.as_ref() + } + + fn is_canonical(&self) -> bool { + self.canonical() + } +} diff --git a/program/src/state/mod.rs b/program/src/state/mod.rs index c8eed95..c0022b0 100644 --- a/program/src/state/mod.rs +++ b/program/src/state/mod.rs @@ -32,6 +32,19 @@ impl<'a> Metadata<'a> { } } +/// Trait representing the common information of a PDA account. +pub(crate) trait PdaInfo { + /// The program that this PDA is associated with. + fn program(&self) -> &Pubkey; + + /// The (optional) authority of the PDA. + fn authority(&self) -> Option<&Pubkey>; + + /// Indicates whether the PDA represents a canonical PDA for + /// the program. + fn is_canonical(&self) -> bool; +} + /// Account discriminators. #[repr(u8)] #[derive(Clone, Copy, Debug)] From 497ad1853754b93e2d42fdaacc57ea0c0cba6d4a Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 16 Jan 2025 12:15:15 +0000 Subject: [PATCH 2/5] Refactor authority validation --- program/src/processor/close.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/program/src/processor/close.rs b/program/src/processor/close.rs index d879e39..dd18e64 100644 --- a/program/src/processor/close.rs +++ b/program/src/processor/close.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; -use crate::state::AccountDiscriminator; +use crate::state::{buffer::Buffer, AccountDiscriminator}; use super::{validate_authority, validate_metadata}; @@ -15,6 +15,10 @@ pub fn close(accounts: &[AccountInfo]) -> ProgramResult { return Err(ProgramError::NotEnoughAccountKeys); }; + if !authority.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + let account_data = if account.data_is_empty() { return Err(ProgramError::UninitializedAccount); } else { @@ -25,19 +29,20 @@ pub fn close(accounts: &[AccountInfo]) -> ProgramResult { // - account: program owned is implicitly checked since we are writing // to the account - match AccountDiscriminator::try_from(account_data[0])? { - AccountDiscriminator::Buffer => { - // The authority of a buffer account must be the buffer account - // itself. - if !(account.key() == authority.key() && authority.is_signer()) { - return Err(ProgramError::MissingRequiredSignature); + // 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() { + match AccountDiscriminator::try_from(account_data[0])? { + AccountDiscriminator::Buffer => { + let buffer = unsafe { Buffer::load_unchecked(account_data) }; + validate_authority(buffer, authority, program, program_data)? } + AccountDiscriminator::Metadata => { + let header = validate_metadata(account)?; + validate_authority(header, authority, program, program_data)? + } + _ => return Err(ProgramError::InvalidAccountData), } - AccountDiscriminator::Metadata => { - let header = validate_metadata(account)?; - validate_authority(header, authority, program, program_data)? - } - _ => return Err(ProgramError::InvalidAccountData), } // Move the lamports to the destination account. From 05b22d85f19213853d7aa6b980fb7c5ad83f872a Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 16 Jan 2025 12:23:12 +0000 Subject: [PATCH 3/5] Remove unnecessary condition --- program/src/state/buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/src/state/buffer.rs b/program/src/state/buffer.rs index d2ec979..1978603 100644 --- a/program/src/state/buffer.rs +++ b/program/src/state/buffer.rs @@ -76,6 +76,6 @@ impl PdaInfo for Buffer { } fn is_canonical(&self) -> bool { - self.canonical() && self.authority().is_none() + self.canonical() } } From 308c17b5ec7d1ffc9cbb52c59cf7f262192cf668 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 16 Jan 2025 12:27:11 +0000 Subject: [PATCH 4/5] Fix PdaInfo trait --- program/src/processor/mod.rs | 2 +- program/src/state/buffer.rs | 4 ++-- program/src/state/header.rs | 4 ++-- program/src/state/mod.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 7c7b139..e994b00 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -131,7 +131,7 @@ fn validate_authority( // The authority is the program upgrade authority for canonical metadata accounts. let authorized = explicitly_authorized || (pda_info.is_canonical() - && program.key() == pda_info.program() + && pda_info.program() == Some(program.key()) && is_program_authority(program, program_data, authority.key())?); if !authorized { return Err(ProgramError::IncorrectAuthority); diff --git a/program/src/state/buffer.rs b/program/src/state/buffer.rs index 1978603..3406a5a 100644 --- a/program/src/state/buffer.rs +++ b/program/src/state/buffer.rs @@ -67,8 +67,8 @@ impl Buffer { } impl PdaInfo for Buffer { - fn program(&self) -> &Pubkey { - self.program.as_ref().unwrap() + fn program(&self) -> Option<&Pubkey> { + self.program.as_ref() } fn authority(&self) -> Option<&Pubkey> { diff --git a/program/src/state/header.rs b/program/src/state/header.rs index 8291588..40197a0 100644 --- a/program/src/state/header.rs +++ b/program/src/state/header.rs @@ -109,8 +109,8 @@ impl Header { } impl PdaInfo for Header { - fn program(&self) -> &Pubkey { - &self.program + fn program(&self) -> Option<&Pubkey> { + Some(&self.program) } fn authority(&self) -> Option<&Pubkey> { diff --git a/program/src/state/mod.rs b/program/src/state/mod.rs index c0022b0..61aced4 100644 --- a/program/src/state/mod.rs +++ b/program/src/state/mod.rs @@ -35,7 +35,7 @@ impl<'a> Metadata<'a> { /// Trait representing the common information of a PDA account. pub(crate) trait PdaInfo { /// The program that this PDA is associated with. - fn program(&self) -> &Pubkey; + fn program(&self) -> Option<&Pubkey>; /// The (optional) authority of the PDA. fn authority(&self) -> Option<&Pubkey>; From 55e29ac0b141b2a26c444ea3c41e7ec43a2b8e99 Mon Sep 17 00:00:00 2001 From: febo Date: Sat, 18 Jan 2025 02:59:12 +0000 Subject: [PATCH 5/5] Rename trait --- Cargo.lock | 4 ++-- program/src/processor/mod.rs | 20 +++++++++++--------- program/src/state/buffer.rs | 14 +++++--------- program/src/state/header.rs | 14 +++++--------- program/src/state/mod.rs | 15 ++++++--------- 5 files changed, 29 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afab388..7703f29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1731,9 +1731,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2999d06e0c7b659daa8d41d56d906c26531e551d22e49c044339024d3e195ebc" +checksum = "43a8a77bf74e1c4050a2bad53648acfcfbf22b1806473f9c9334a58ef3b38ec4" [[package]] name = "pinocchio-pubkey" diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index e994b00..27acb5a 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -1,6 +1,6 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; -use crate::state::{header::Header, AccountDiscriminator, PdaInfo}; +use crate::state::{header::Header, Account, AccountDiscriminator}; pub mod allocate; pub mod close; @@ -113,8 +113,8 @@ fn validate_metadata(metadata: &AccountInfo) -> Result<&Header, ProgramError> { /// - [explicit] The `authority` account must match the authority set on the `metadata` account OR /// it must be the program upgrade authority if the `metadata` account is canonical (see `is_program_authority`). #[inline(always)] -fn validate_authority( - pda_info: &T, +fn validate_authority( + account: &T, authority: &AccountInfo, program: &AccountInfo, program_data: &AccountInfo, @@ -123,19 +123,21 @@ fn validate_authority( if !authority.is_signer() { return Err(ProgramError::MissingRequiredSignature); } + // The authority is the set authority. - let explicitly_authorized = match pda_info.authority() { + let explicitly_authorized = match account.get_authority() { Some(metadata_authority) => metadata_authority == authority.key(), None => false, }; + // The authority is the program upgrade authority for canonical metadata accounts. let authorized = explicitly_authorized - || (pda_info.is_canonical() - && pda_info.program() == Some(program.key()) + || (account.is_canonical(program.key()) && is_program_authority(program, program_data, authority.key())?); + if !authorized { - return Err(ProgramError::IncorrectAuthority); + Err(ProgramError::IncorrectAuthority) + } else { + Ok(()) } - - Ok(()) } diff --git a/program/src/state/buffer.rs b/program/src/state/buffer.rs index 3406a5a..264a1c5 100644 --- a/program/src/state/buffer.rs +++ b/program/src/state/buffer.rs @@ -1,6 +1,6 @@ use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; -use super::{AccountDiscriminator, PdaInfo, ZeroableOption, SEED_LEN}; +use super::{Account, AccountDiscriminator, ZeroableOption, SEED_LEN}; /// Buffer account header. #[repr(C)] @@ -66,16 +66,12 @@ impl Buffer { } } -impl PdaInfo for Buffer { - fn program(&self) -> Option<&Pubkey> { - self.program.as_ref() - } - - fn authority(&self) -> Option<&Pubkey> { +impl Account for Buffer { + fn get_authority(&self) -> Option<&Pubkey> { self.authority.as_ref() } - fn is_canonical(&self) -> bool { - self.canonical() + fn is_canonical(&self, program: &Pubkey) -> bool { + self.canonical() && self.program.as_ref() == Some(program) } } diff --git a/program/src/state/header.rs b/program/src/state/header.rs index 40197a0..3ef6406 100644 --- a/program/src/state/header.rs +++ b/program/src/state/header.rs @@ -1,7 +1,7 @@ use pinocchio::{program_error::ProgramError, pubkey::Pubkey}; use super::{ - AccountDiscriminator, Compression, DataSource, Encoding, Format, PdaInfo, ZeroableOption, + Account, AccountDiscriminator, Compression, DataSource, Encoding, Format, ZeroableOption, SEED_LEN, }; @@ -108,16 +108,12 @@ impl Header { } } -impl PdaInfo for Header { - fn program(&self) -> Option<&Pubkey> { - Some(&self.program) - } - - fn authority(&self) -> Option<&Pubkey> { +impl Account for Header { + fn get_authority(&self) -> Option<&Pubkey> { self.authority.as_ref() } - fn is_canonical(&self) -> bool { - self.canonical() + fn is_canonical(&self, program: &Pubkey) -> bool { + self.canonical() && self.program == *program } } diff --git a/program/src/state/mod.rs b/program/src/state/mod.rs index 61aced4..2018e83 100644 --- a/program/src/state/mod.rs +++ b/program/src/state/mod.rs @@ -32,17 +32,14 @@ impl<'a> Metadata<'a> { } } -/// Trait representing the common information of a PDA account. -pub(crate) trait PdaInfo { - /// The program that this PDA is associated with. - fn program(&self) -> Option<&Pubkey>; - - /// The (optional) authority of the PDA. - fn authority(&self) -> Option<&Pubkey>; +/// Utility trait for an account. +pub(crate) trait Account { + /// Returns the account authority, if there is one. + fn get_authority(&self) -> Option<&Pubkey>; /// Indicates whether the PDA represents a canonical PDA for - /// the program. - fn is_canonical(&self) -> bool; + /// the given program. + fn is_canonical(&self, program: &Pubkey) -> bool; } /// Account discriminators.