From 58ac8a70fb8190a35b22e060c138eb4eb04c018a Mon Sep 17 00:00:00 2001 From: febo Date: Tue, 12 May 2026 13:45:18 +0100 Subject: [PATCH 1/4] Update comments --- program/src/instruction.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/program/src/instruction.rs b/program/src/instruction.rs index d191501..1eb2820 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -82,8 +82,8 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Buffer or metadata account. /// 1. `[s]` Current authority account. - /// 2. `[o]` (optional) Program account. - /// 3. `[o]` (optional) Program data account. + /// 2. `[o]` Program account. + /// 3. `[o]` Program data account. /// /// Instruction data: /// @@ -111,9 +111,9 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Metadata account. /// 1. `[s]` Authority account. - /// 2. `[o]` (optional) Buffer account to copy data from. - /// 3. `[o]` (optional) Program account. - /// 4. `[o]` (optional) Program data account. + /// 2. `[o]` Buffer account to copy data from. + /// 3. `[o]` Program account. + /// 4. `[o]` Program data account. /// /// Instruction data: /// @@ -136,8 +136,8 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Metadata account. /// 1. `[s]` Authority account. - /// 2. `[o]` (optional) Program account. - /// 3. `[o]` (optional) Program data account. + /// 2. `[o]` Program account. + /// 3. `[o]` Program data account. SetImmutable, /// Resizes and withdraws excess lamports from a buffer or metadata account. @@ -156,8 +156,8 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Buffer or metadata account. /// 1. `[s]` Authority account. - /// 2. `[o]` (optional) Program account. - /// 3. `[o]` (optional) Program data account. + /// 2. `[o]` Program account. + /// 3. `[o]` Program data account. /// 5. `[w]` Destination account. /// 6. `[]` Rent sysvar account. Trim, @@ -180,8 +180,8 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Account to close. /// 1. `[s]` Metadata authority or buffer account. - /// 2. `[o]` (optional) Program account. - /// 3. `[o]` (optional) Program data account. + /// 2. `[o]` Program account. + /// 3. `[o]` Program data account. /// 5. `[w]` Destination account. Close, @@ -233,8 +233,8 @@ pub enum ProgramMetadataInstruction { /// /// 0. `[w]` Buffer or metadata account. /// 1. `[s]` Authority account. - /// 2. `[o]` (optional) Program account. - /// 3. `[o]` (optional) Program data account. + /// 2. `[o]` Program account. + /// 3. `[o]` Program data account. /// /// Instruction data: /// From f90f67848a18e7baf1720d2ca07f575db6210540 Mon Sep 17 00:00:00 2001 From: febo Date: Tue, 12 May 2026 20:23:54 +0100 Subject: [PATCH 2/4] Add tests --- program/tests/allocate.rs | 136 ++++++- program/tests/close.rs | 282 +++++++++++++++ program/tests/extend.rs | 97 +++++ program/tests/initialize.rs | 233 +++++++++++- program/tests/set_authority.rs | 342 ++++++++++++++++++ program/tests/set_data.rs | 517 +++++++++++++++++++++++++++ program/tests/set_immutable.rs | 343 ++++++++++++++++++ program/tests/setup/close.rs | 28 ++ program/tests/setup/mod.rs | 8 + program/tests/setup/set_authority.rs | 35 ++ program/tests/setup/set_data.rs | 51 +++ program/tests/setup/set_immutable.rs | 26 ++ program/tests/setup/write.rs | 2 + program/tests/trim.rs | 61 ++++ program/tests/write.rs | 221 ++++++++++++ 15 files changed, 2380 insertions(+), 2 deletions(-) create mode 100644 program/tests/close.rs create mode 100644 program/tests/set_authority.rs create mode 100644 program/tests/set_data.rs create mode 100644 program/tests/set_immutable.rs create mode 100644 program/tests/setup/close.rs create mode 100644 program/tests/setup/set_authority.rs create mode 100644 program/tests/setup/set_data.rs create mode 100644 program/tests/setup/set_immutable.rs create mode 100644 program/tests/write.rs diff --git a/program/tests/allocate.rs b/program/tests/allocate.rs index e11ade6..f0d5f13 100644 --- a/program/tests/allocate.rs +++ b/program/tests/allocate.rs @@ -6,7 +6,10 @@ use solana_account::Account; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sdk_ids::system_program; -use spl_program_metadata::state::{buffer::Buffer, SEED_LEN}; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, SEED_LEN}, +}; #[test] fn test_allocate_canonical() { @@ -420,3 +423,134 @@ fn test_allocate_with_unfunded_account() { ], ); } + +#[test] +fn fail_allocate_keypair_with_seed() { + let buffer_key = Pubkey::new_unique(); + let buffer_account = + create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + process_instruction( + ( + &allocate(&buffer_key, &buffer_key, None, None, Some(&seed)).unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_allocate_pda_with_wrong_seed() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let mut wrong_seed = [0u8; SEED_LEN]; + wrong_seed[0..5].copy_from_slice("other".as_bytes()); + + let (buffer_key, _) = Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let buffer_account = create_empty_account(Buffer::LEN, PROGRAM_ID); + + process_instruction( + ( + &allocate( + &buffer_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&wrong_seed), + ) + .unwrap(), + &[Check::err(ProgramError::InvalidSeeds)], + ), + &[ + (buffer_key, buffer_account), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_allocate_pda_for_non_executable_program() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = Account::default(); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (buffer_key, _) = Pubkey::find_program_address( + &[program_key.as_ref(), authority_key.as_ref(), &seed], + &PROGRAM_ID, + ); + let buffer_account = create_empty_account(Buffer::LEN, PROGRAM_ID); + + process_instruction( + ( + &allocate( + &buffer_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&seed), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::NotExecutableAccount as u32, + ))], + ), + &[ + (buffer_key, buffer_account), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_allocate_already_initialized_buffer() { + 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).unwrap(), + &[Check::success()], + ), + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::err(ProgramError::AccountAlreadyInitialized)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/close.rs b/program/tests/close.rs new file mode 100644 index 0000000..3d0d5fb --- /dev/null +++ b/program/tests/close.rs @@ -0,0 +1,282 @@ +mod setup; +pub use setup::*; + +use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; +use solana_account::Account; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_sdk_ids::system_program; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, header::Header, SEED_LEN}, +}; + +#[test] +fn test_close_metadata() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 7]; + let metadata_lamports = minimum_balance_for(Header::LEN + data.len()); + let metadata_account = create_funded_account(metadata_lamports, system_program::ID); + + let destination_key = Pubkey::new_unique(); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&metadata_key).data_slice(0, &[2]).build(), + ], + ), + ( + &close( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + &destination_key, + ) + .unwrap(), + &[ + Check::success(), + // metadata account + Check::account(&metadata_key).closed().build(), + // destination lamports + Check::account(&destination_key) + .lamports(metadata_lamports) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + (destination_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_close_buffer() { + let buffer_key = Pubkey::new_unique(); + let buffer_lamports = minimum_balance_for(Buffer::LEN); + let buffer_account = create_funded_account(buffer_lamports, system_program::ID); + + let destination_key = Pubkey::new_unique(); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&buffer_key).data_slice(0, &[1]).build(), + ], + ), + ( + &close(&buffer_key, &buffer_key, None, None, &destination_key).unwrap(), + &[ + Check::success(), + // buffer account + Check::account(&buffer_key).closed().build(), + // destination lamports + Check::account(&destination_key) + .lamports(buffer_lamports) + .build(), + ], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (destination_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_close_with_wrong_authority() { + let buffer_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + let buffer_lamports = minimum_balance_for(Buffer::LEN); + let buffer_account = create_funded_account(buffer_lamports, system_program::ID); + + let destination_key = Pubkey::new_unique(); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &close( + &buffer_key, + &wrong_authority_key, + None, + None, + &destination_key, + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (wrong_authority_key, Account::default()), + (destination_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_close_uninitialized_account() { + let account_key = Pubkey::new_unique(); + let destination_key = Pubkey::new_unique(); + + process_instruction( + ( + &close(&account_key, &account_key, None, None, &destination_key).unwrap(), + &[Check::err(ProgramError::UninitializedAccount)], + ), + &[ + (account_key, Account::default()), + (PROGRAM_ID, Account::default()), + (destination_key, Account::default()), + ], + ); +} + +#[test] +fn fail_close_account_with_empty_discriminator() { + let account_key = Pubkey::new_unique(); + let account = create_empty_account(Buffer::LEN, PROGRAM_ID); + let destination_key = Pubkey::new_unique(); + + process_instruction( + ( + &close(&account_key, &account_key, None, None, &destination_key).unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + &[ + (account_key, account), + (PROGRAM_ID, Account::default()), + (destination_key, Account::default()), + ], + ); +} + +#[test] +fn fail_close_immutable_metadata() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 7]; + let metadata_lamports = minimum_balance_for(Header::LEN + data.len()); + let metadata_account = create_funded_account(metadata_lamports, system_program::ID); + + let destination_key = Pubkey::new_unique(); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &close( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + &destination_key, + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::ImmutableMetadataAccount as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + (destination_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/extend.rs b/program/tests/extend.rs index b8ca853..36c03e6 100644 --- a/program/tests/extend.rs +++ b/program/tests/extend.rs @@ -3,6 +3,7 @@ pub use setup::*; use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; use solana_account::Account; +use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sdk_ids::system_program; use spl_program_metadata::state::{buffer::Buffer, SEED_LEN}; @@ -196,3 +197,99 @@ fn test_extend_buffer() { ], ); } + +#[test] +fn fail_extend_with_wrong_authority() { + let buffer_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + EXTEND_LENGTH), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &extend( + &buffer_key, + &wrong_authority_key, + None, + None, + EXTEND_LENGTH as u16, + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (wrong_authority_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[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).unwrap(), + &[Check::success()], + ), + ( + &extend(&buffer_key, &buffer_key, None, None, 1).unwrap(), + &[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(); + + process_instruction( + ( + &extend(&account_key, &account_key, None, None, EXTEND_LENGTH as u16).unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + &[ + (account_key, Account::default()), + (PROGRAM_ID, Account::default()), + ], + ); +} + +#[test] +fn fail_extend_with_invalid_instruction_data() { + let buffer_key = Pubkey::new_unique(); + let mut instruction = + extend(&buffer_key, &buffer_key, None, None, EXTEND_LENGTH as u16).unwrap(); + instruction.data.truncate(1); + + process_instruction( + ( + &instruction, + &[Check::err(ProgramError::InvalidInstructionData)], + ), + &[ + (buffer_key, Account::default()), + (PROGRAM_ID, Account::default()), + ], + ); +} diff --git a/program/tests/initialize.rs b/program/tests/initialize.rs index fb239f4..3100757 100644 --- a/program/tests/initialize.rs +++ b/program/tests/initialize.rs @@ -3,9 +3,13 @@ pub use setup::*; use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; use solana_account::Account; +use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sdk_ids::system_program; -use spl_program_metadata::state::header::Header; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, header::Header, SEED_LEN}, +}; #[test] fn test_initialize_canonical() { @@ -124,3 +128,230 @@ fn test_initialize_non_canonical() { ], ); } + +#[test] +fn test_initialize_from_buffer() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let data = [7u8; 12]; + let metadata_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&seed), + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key).data_slice(0, &[1]).build(), + ], + ), + ( + &write(&metadata_key, &authority_key, None, 0, &data).unwrap(), + &[ + Check::success(), + Check::account(&metadata_key) + .data_slice(Buffer::LEN, &data) + .build(), + ], + ), + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + None, + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key).data_slice(0, &[2]).build(), + Check::account(&metadata_key) + .data_slice(Header::LEN, &data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_initialize_with_wrong_metadata_pda() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let wrong_metadata_key = Pubkey::new_unique(); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 10), system_program::ID); + + let mut instruction = initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 10]), + ) + .unwrap(); + instruction.accounts[0].pubkey = wrong_metadata_key; + + process_instruction( + (&instruction, &[Check::err(ProgramError::InvalidSeeds)]), + &[ + (wrong_metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_initialize_without_data() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN), system_program::ID); + + process_instruction( + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + None, + ) + .unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_initialize_external_data_with_wrong_length() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 10), system_program::ID); + + process_instruction( + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 2, + }, + Some(&[1u8; 10]), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::InvalidDataLength as u32, + ))], + ), + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/set_authority.rs b/program/tests/set_authority.rs new file mode 100644 index 0000000..fafab1d --- /dev/null +++ b/program/tests/set_authority.rs @@ -0,0 +1,342 @@ +mod setup; +pub use setup::*; + +use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; +use solana_account::Account; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_sdk_ids::system_program; +use spl_program_metadata::state::{buffer::Buffer, header::Header, SEED_LEN}; + +#[test] +fn test_set_authority_metadata() { + let authority_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let initial_data = [1u8; 5]; + let updated_data = [4u8; 8]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + updated_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&initial_data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&new_authority_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &new_authority_key, + None, + None, + None, + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&updated_data), + ) + .unwrap(), + &[ + Check::success(), + // metadata data + Check::account(&metadata_key) + .data_slice(Header::LEN, &updated_data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (new_authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_set_authority_buffer() { + let buffer_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + let data = [5u8; 8]; + + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&buffer_key).data_slice(0, &[1]).build(), + ], + ), + ( + &set_authority( + &buffer_key, + &buffer_key, + None, + None, + Some(&new_authority_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &new_authority_key, None, 0, &data).unwrap(), + &[ + Check::success(), + // buffer data + Check::account(&buffer_key) + .data_slice(Buffer::LEN, &data) + .build(), + ], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (new_authority_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_set_authority_non_canonical_metadata() { + let authority_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = Pubkey::find_program_address( + &[program_key.as_ref(), authority_key.as_ref(), &seed], + &PROGRAM_ID, + ); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 5), system_program::ID); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + None, + InitializeArgs { + canonical: false, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 5]), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority( + &metadata_key, + &authority_key, + None, + None, + Some(&new_authority_key), + ) + .unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_authority_with_wrong_authority() { + let authority_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 5), system_program::ID); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 5]), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority( + &metadata_key, + &wrong_authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&new_authority_key), + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (wrong_authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_authority_remove_buffer_authority() { + 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).unwrap(), + &[Check::success()], + ), + ( + &set_authority(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::err(ProgramError::InvalidArgument)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_authority_with_missing_new_authority_bytes() { + let buffer_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + let buffer_account = + create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); + + let mut instruction = set_authority( + &buffer_key, + &buffer_key, + None, + None, + Some(&new_authority_key), + ) + .unwrap(); + instruction.data.truncate(2); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &instruction, + &[Check::err(ProgramError::InvalidInstructionData)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/set_data.rs b/program/tests/set_data.rs new file mode 100644 index 0000000..f5fdce3 --- /dev/null +++ b/program/tests/set_data.rs @@ -0,0 +1,517 @@ +mod setup; +pub use setup::*; + +use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; +use solana_account::Account; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_sdk_ids::system_program; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, header::Header, SEED_LEN}, +}; + +#[test] +fn test_set_data_instruction_data() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let initial_data = [1u8; 5]; + let updated_data = [2u8; 12]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + updated_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&initial_data), + ) + .unwrap(), + &[ + Check::success(), + // metadata data + Check::account(&metadata_key) + .data_slice(Header::LEN, &initial_data) + .build(), + ], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 1, + compression: 0, + format: 1, + data_source: Some(0), + }, + Some(&updated_data), + ) + .unwrap(), + &[ + Check::success(), + // data length + Check::account(&metadata_key) + .space(Header::LEN + updated_data.len()) + .build(), + // metadata data + Check::account(&metadata_key) + .data_slice(Header::LEN, &updated_data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_set_data_buffer() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let buffer_key = Pubkey::new_unique(); + let initial_data = [1u8; 5]; + let updated_data = [3u8; 9]; + + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + updated_data.len()), + system_program::ID, + ); + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + updated_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&initial_data), + ) + .unwrap(), + &[ + Check::success(), + // metadata data + Check::account(&metadata_key) + .data_slice(Header::LEN, &initial_data) + .build(), + ], + ), + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&buffer_key).data_slice(0, &[1]).build(), + ], + ), + ( + &write(&buffer_key, &buffer_key, None, 0, &updated_data).unwrap(), + &[ + Check::success(), + // buffer data + Check::account(&buffer_key) + .data_slice(Buffer::LEN, &updated_data) + .build(), + ], + ), + ( + &set_data( + &metadata_key, + &authority_key, + Some(&buffer_key), + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + None, + ) + .unwrap(), + &[ + Check::success(), + // data length + Check::account(&metadata_key) + .space(Header::LEN + updated_data.len()) + .build(), + // metadata data + Check::account(&metadata_key) + .data_slice(Header::LEN, &updated_data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_data_with_wrong_authority() { + let authority_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let data = [1u8; 5]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &wrong_authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&[2u8; 5]), + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (wrong_authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_data_from_empty_buffer_as_direct_data() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let buffer_key = Pubkey::new_unique(); + let data = [1u8; 5]; + + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + let buffer_account = + create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + Some(&buffer_key), + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + None, + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::InvalidDataLength as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_data_from_wrong_owner_buffer() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let buffer_key = Pubkey::new_unique(); + let data = [1u8; 5]; + + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + let buffer_account = + create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + Some(&buffer_key), + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + None, + ) + .unwrap(), + &[Check::err(ProgramError::InvalidAccountOwner)], + ), + ], + &[ + (metadata_key, metadata_account), + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_data_with_invalid_encoding() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let data = [1u8; 5]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 99, + compression: 0, + format: 0, + data_source: None, + }, + None, + ) + .unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/set_immutable.rs b/program/tests/set_immutable.rs new file mode 100644 index 0000000..157d2ed --- /dev/null +++ b/program/tests/set_immutable.rs @@ -0,0 +1,343 @@ +mod setup; +pub use setup::*; + +use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; +use solana_account::Account; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_sdk_ids::system_program; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, header::Header, SEED_LEN}, +}; + +#[test] +fn test_set_immutable_canonical() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 6]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&[2u8; 6]), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::ImmutableMetadataAccount as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_set_immutable_non_canonical() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = Pubkey::find_program_address( + &[program_key.as_ref(), authority_key.as_ref(), &seed], + &PROGRAM_ID, + ); + + let data = [1u8; 6]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + None, + InitializeArgs { + canonical: false, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable(&metadata_key, &authority_key, None, None).unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + None, + None, + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&[2u8; 6]), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::ImmutableMetadataAccount as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_immutable_with_wrong_authority() { + let authority_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 6]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &wrong_authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (wrong_authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_immutable_buffer_account() { + 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).unwrap(), + &[Check::success()], + ), + ( + &set_immutable(&buffer_key, &buffer_key, None, None).unwrap(), + &[Check::err(ProgramError::UninitializedAccount)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_immutable_twice() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 6]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::ImmutableMetadataAccount as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} diff --git a/program/tests/setup/close.rs b/program/tests/setup/close.rs new file mode 100644 index 0000000..8c2b117 --- /dev/null +++ b/program/tests/setup/close.rs @@ -0,0 +1,28 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use spl_program_metadata::instruction::ProgramMetadataInstruction; + +use super::PROGRAM_ID; + +pub fn close( + account: &Pubkey, + authority: &Pubkey, + program: Option<&Pubkey>, + program_data: Option<&Pubkey>, + destination: &Pubkey, +) -> Result { + let accounts = vec![ + AccountMeta::new(*account, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new_readonly(*program.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new_readonly(*program_data.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new(*destination, false), + ]; + + Ok(Instruction { + program_id: PROGRAM_ID, + accounts, + data: vec![ProgramMetadataInstruction::Close as u8], + }) +} diff --git a/program/tests/setup/mod.rs b/program/tests/setup/mod.rs index 5f87069..e0e66b6 100644 --- a/program/tests/setup/mod.rs +++ b/program/tests/setup/mod.rs @@ -1,12 +1,20 @@ mod allocate; +mod close; mod extend; mod initialize; +mod set_authority; +mod set_data; +mod set_immutable; mod trim; mod write; pub use allocate::*; +pub use close::*; pub use extend::*; pub use initialize::*; +pub use set_authority::*; +pub use set_data::*; +pub use set_immutable::*; pub use trim::*; pub use write::*; diff --git a/program/tests/setup/set_authority.rs b/program/tests/setup/set_authority.rs new file mode 100644 index 0000000..eb33ad1 --- /dev/null +++ b/program/tests/setup/set_authority.rs @@ -0,0 +1,35 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use spl_program_metadata::instruction::ProgramMetadataInstruction; + +use super::PROGRAM_ID; + +pub fn set_authority( + account: &Pubkey, + authority: &Pubkey, + program: Option<&Pubkey>, + program_data: Option<&Pubkey>, + new_authority: Option<&Pubkey>, +) -> Result { + let accounts = vec![ + AccountMeta::new(*account, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new_readonly(*program.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new_readonly(*program_data.unwrap_or(&PROGRAM_ID), false), + ]; + + let mut data = vec![ + ProgramMetadataInstruction::SetAuthority as u8, + new_authority.is_some() as u8, + ]; + if let Some(new_authority) = new_authority { + data.extend_from_slice(new_authority.as_ref()); + } + + Ok(Instruction { + program_id: PROGRAM_ID, + accounts, + data, + }) +} diff --git a/program/tests/setup/set_data.rs b/program/tests/setup/set_data.rs new file mode 100644 index 0000000..d31ca14 --- /dev/null +++ b/program/tests/setup/set_data.rs @@ -0,0 +1,51 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use spl_program_metadata::instruction::ProgramMetadataInstruction; + +use super::PROGRAM_ID; + +pub fn set_data( + metadata: &Pubkey, + authority: &Pubkey, + buffer: Option<&Pubkey>, + program: Option<&Pubkey>, + program_data: Option<&Pubkey>, + args: SetDataArgs, + instruction_data: Option<&[u8]>, +) -> Result { + let accounts = vec![ + AccountMeta::new(*metadata, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new_readonly(*buffer.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new_readonly(*program.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new_readonly(*program_data.unwrap_or(&PROGRAM_ID), false), + ]; + + let mut data = vec![ + ProgramMetadataInstruction::SetData as u8, + args.encoding, + args.compression, + args.format, + ]; + + if let Some(data_source) = args.data_source { + data.push(data_source); + if let Some(instruction_data) = instruction_data { + data.extend_from_slice(instruction_data); + } + } + + Ok(Instruction { + program_id: PROGRAM_ID, + accounts, + data, + }) +} + +pub struct SetDataArgs { + pub encoding: u8, + pub compression: u8, + pub format: u8, + pub data_source: Option, +} diff --git a/program/tests/setup/set_immutable.rs b/program/tests/setup/set_immutable.rs new file mode 100644 index 0000000..51b2de2 --- /dev/null +++ b/program/tests/setup/set_immutable.rs @@ -0,0 +1,26 @@ +use solana_instruction::{AccountMeta, Instruction}; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use spl_program_metadata::instruction::ProgramMetadataInstruction; + +use super::PROGRAM_ID; + +pub fn set_immutable( + metadata: &Pubkey, + authority: &Pubkey, + program: Option<&Pubkey>, + program_data: Option<&Pubkey>, +) -> Result { + let accounts = vec![ + AccountMeta::new(*metadata, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new_readonly(*program.unwrap_or(&PROGRAM_ID), false), + AccountMeta::new_readonly(*program_data.unwrap_or(&PROGRAM_ID), false), + ]; + + Ok(Instruction { + program_id: PROGRAM_ID, + accounts, + data: vec![ProgramMetadataInstruction::SetImmutable as u8], + }) +} diff --git a/program/tests/setup/write.rs b/program/tests/setup/write.rs index 04a5a90..cf09c56 100644 --- a/program/tests/setup/write.rs +++ b/program/tests/setup/write.rs @@ -8,12 +8,14 @@ use super::PROGRAM_ID; pub fn write( buffer: &Pubkey, authority: &Pubkey, + source_buffer: Option<&Pubkey>, offset: u32, data: &[u8], ) -> Result { let accounts = vec![ AccountMeta::new(*buffer, false), AccountMeta::new_readonly(*authority, true), + AccountMeta::new_readonly(*source_buffer.unwrap_or(&PROGRAM_ID), false), ]; let mut instruction_data = vec![ProgramMetadataInstruction::Write as u8]; diff --git a/program/tests/trim.rs b/program/tests/trim.rs index 4d33120..ab55e4b 100644 --- a/program/tests/trim.rs +++ b/program/tests/trim.rs @@ -304,3 +304,64 @@ fn fail_trim_non_rent_exempt_account() { ], ); } + +#[test] +fn fail_trim_with_wrong_authority() { + let buffer_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + EXCESS_LAMPORTS), + system_program::ID, + ); + + let destination_key = Pubkey::new_unique(); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &trim( + &buffer_key, + &wrong_authority_key, + None, + None, + &destination_key, + ) + .unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (wrong_authority_key, Account::default()), + (destination_key, Account::default()), + keyed_account_for_system_program(), + (rent::ID, rent_sysvar()), + ], + ); +} + +#[test] +fn fail_trim_account_with_empty_discriminator() { + let account_key = Pubkey::new_unique(); + let account = create_empty_account(Buffer::LEN, PROGRAM_ID); + let destination_key = Pubkey::new_unique(); + + process_instruction( + ( + &trim(&account_key, &account_key, None, None, &destination_key).unwrap(), + &[Check::err(ProgramError::UninitializedAccount)], + ), + &[ + (account_key, account), + (PROGRAM_ID, Account::default()), + (destination_key, Account::default()), + keyed_account_for_system_program(), + (rent::ID, rent_sysvar()), + ], + ); +} diff --git a/program/tests/write.rs b/program/tests/write.rs new file mode 100644 index 0000000..cb8e0ea --- /dev/null +++ b/program/tests/write.rs @@ -0,0 +1,221 @@ +mod setup; +pub use setup::*; + +use mollusk_svm::{program::keyed_account_for_system_program, result::Check}; +use solana_account::Account; +use solana_program_error::ProgramError; +use solana_pubkey::Pubkey; +use solana_sdk_ids::system_program; +use spl_program_metadata::state::buffer::Buffer; + +#[test] +fn test_write_instruction_data() { + let buffer_key = Pubkey::new_unique(); + let data = [1u8; 10]; + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&buffer_key).data_slice(0, &[1]).build(), + // data length + Check::account(&buffer_key).space(Buffer::LEN).build(), + ], + ), + ( + &write(&buffer_key, &buffer_key, None, 0, &data).unwrap(), + &[ + Check::success(), + // data length + Check::account(&buffer_key) + .space(Buffer::LEN + data.len()) + .build(), + // buffer data + Check::account(&buffer_key) + .data_slice(Buffer::LEN, &data) + .build(), + ], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_write_from_buffer() { + let source_key = Pubkey::new_unique(); + let target_key = Pubkey::new_unique(); + let data = [2u8; 12]; + + let source_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + let target_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate(&source_key, &source_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&source_key).data_slice(0, &[1]).build(), + ], + ), + ( + &write(&source_key, &source_key, None, 0, &data).unwrap(), + &[ + Check::success(), + // source buffer data + Check::account(&source_key) + .data_slice(Buffer::LEN, &data) + .build(), + ], + ), + ( + &allocate(&target_key, &target_key, None, None, None).unwrap(), + &[ + Check::success(), + // account discriminator + Check::account(&target_key).data_slice(0, &[1]).build(), + ], + ), + ( + &write(&target_key, &target_key, Some(&source_key), 0, &[]).unwrap(), + &[ + Check::success(), + // target buffer data + Check::account(&target_key) + .data_slice(Buffer::LEN, &data) + .build(), + ], + ), + ], + &[ + (source_key, source_account), + (target_key, target_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_write_with_wrong_authority() { + let buffer_key = Pubkey::new_unique(); + let wrong_authority_key = Pubkey::new_unique(); + let data = [9u8; 4]; + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &wrong_authority_key, None, 0, &data).unwrap(), + &[Check::err(ProgramError::IncorrectAuthority)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + (wrong_authority_key, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_write_empty_data_without_source_buffer() { + 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).unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &buffer_key, None, 0, &[]).unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_write_from_same_buffer() { + 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).unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &buffer_key, Some(&buffer_key), 0, &[]).unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + +#[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).unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &buffer_key, None, 0, &[1]).unwrap(), + &[Check::err(ProgramError::AccountNotRentExempt)], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} From 45f068086af31bf8a91b76756d73c35c43d34696 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 18 May 2026 17:21:34 +0100 Subject: [PATCH 3/4] More tests --- program/tests/extend.rs | 80 ++++++++- program/tests/initialize.rs | 222 ++++++++++++++++++++++++ program/tests/set_authority.rs | 177 ++++++++++++++++++- program/tests/set_data.rs | 308 ++++++++++++++++++++++++++++++++- program/tests/write.rs | 71 ++++++++ 5 files changed, 852 insertions(+), 6 deletions(-) diff --git a/program/tests/extend.rs b/program/tests/extend.rs index 36c03e6..d65734f 100644 --- a/program/tests/extend.rs +++ b/program/tests/extend.rs @@ -6,12 +6,12 @@ use solana_account::Account; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sdk_ids::system_program; -use spl_program_metadata::state::{buffer::Buffer, SEED_LEN}; +use spl_program_metadata::state::{buffer::Buffer, header::Header, SEED_LEN}; const EXTEND_LENGTH: usize = 200; #[test] -fn test_extend_canonical() { +fn test_extend_canonical_buffer() { let authority_key = Pubkey::new_unique(); let program_data_key = Pubkey::new_unique(); @@ -82,7 +82,7 @@ fn test_extend_canonical() { } #[test] -fn test_extend_non_canonical() { +fn test_extend_non_canonical_buffer() { let authority_key = Pubkey::new_unique(); let program_data_key = Pubkey::new_unique(); @@ -156,7 +156,7 @@ fn test_extend_non_canonical() { } #[test] -fn test_extend_buffer() { +fn test_extend_keypair_buffer() { let buffer_key = Pubkey::new_unique(); let buffer_account = create_funded_account( minimum_balance_for(Buffer::LEN + EXTEND_LENGTH), @@ -198,6 +198,78 @@ fn test_extend_buffer() { ); } +#[test] +fn test_extend_metadata() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let data = [1u8; 8]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len() + EXTEND_LENGTH), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &extend( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + EXTEND_LENGTH as u16, + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key) + .space(Header::LEN + data.len() + EXTEND_LENGTH) + .build(), + Check::account(&metadata_key) + .data_slice(Header::LEN, &data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_extend_with_wrong_authority() { let buffer_key = Pubkey::new_unique(); diff --git a/program/tests/initialize.rs b/program/tests/initialize.rs index 3100757..c61f999 100644 --- a/program/tests/initialize.rs +++ b/program/tests/initialize.rs @@ -211,6 +211,133 @@ fn test_initialize_from_buffer() { ); } +#[test] +fn fail_initialize_already_initialized_metadata() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 10]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + let instruction = initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(); + + process_instructions( + &[ + (&instruction, &[Check::success()]), + // second attempt should fail since the metadata account is already initialized + ( + &instruction, + &[Check::err(ProgramError::AccountAlreadyInitialized)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_initialize_from_buffer_with_instruction_data() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let buffer_data = [7u8; 12]; + let buffer_account = create_funded_account( + minimum_balance_for(Buffer::LEN + buffer_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &allocate( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&seed), + ) + .unwrap(), + &[Check::success()], + ), + ( + &write(&metadata_key, &authority_key, None, 0, &buffer_data).unwrap(), + &[Check::success()], + ), + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[8u8; 4]), // instruction data + ) + .unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + ], + &[ + (metadata_key, buffer_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_initialize_with_wrong_metadata_pda() { let authority_key = Pubkey::new_unique(); @@ -258,6 +385,53 @@ fn fail_initialize_with_wrong_metadata_pda() { ); } +#[test] +fn fail_initialize_without_rent_exemption() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = create_funded_account(0, system_program::ID); + + process_instruction( + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 10]), + ) + .unwrap(), + &[Check::err(ProgramError::AccountNotRentExempt)], + ), + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_initialize_without_data() { let authority_key = Pubkey::new_unique(); @@ -306,6 +480,54 @@ fn fail_initialize_without_data() { ); } +#[test] +fn fail_initialize_with_invalid_encoding() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 10), system_program::ID); + + process_instruction( + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 99, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 10]), + ) + .unwrap(), + &[Check::err(ProgramError::InvalidAccountData)], + ), + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_initialize_external_data_with_wrong_length() { let authority_key = Pubkey::new_unique(); diff --git a/program/tests/set_authority.rs b/program/tests/set_authority.rs index fafab1d..f85bd2b 100644 --- a/program/tests/set_authority.rs +++ b/program/tests/set_authority.rs @@ -6,7 +6,10 @@ use solana_account::Account; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; use solana_sdk_ids::system_program; -use spl_program_metadata::state::{buffer::Buffer, header::Header, SEED_LEN}; +use spl_program_metadata::{ + error::ProgramMetadataError, + state::{buffer::Buffer, header::Header, SEED_LEN}, +}; #[test] fn test_set_authority_metadata() { @@ -100,6 +103,104 @@ fn test_set_authority_metadata() { ); } +#[test] +fn test_remove_metadata_authority() { + let authority_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let initial_data = [1u8; 5]; + let updated_data = [8u8; 7]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + updated_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&initial_data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&new_authority_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority(&metadata_key, &new_authority_key, None, None, None).unwrap(), + &[Check::success()], + ), + // Set data with the program upgrade authority after removing the + // metadata authority to ensure the program upgrade authority can + // still update the metadata. + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&updated_data), + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key) + .data_slice(Header::LEN, &updated_data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (new_authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn test_set_authority_buffer() { let buffer_key = Pubkey::new_unique(); @@ -152,6 +253,80 @@ fn test_set_authority_buffer() { ); } +#[test] +fn fail_set_authority_immutable_metadata() { + let authority_key = Pubkey::new_unique(); + let new_authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + let metadata_account = + create_funded_account(minimum_balance_for(Header::LEN + 5), system_program::ID); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&[1u8; 5]), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_immutable( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_authority( + &metadata_key, + &authority_key, + Some(&program_key), + Some(&program_data_key), + Some(&new_authority_key), + ) + .unwrap(), + &[Check::err(ProgramError::Custom( + ProgramMetadataError::ImmutableMetadataAccount as u32, + ))], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn test_set_authority_non_canonical_metadata() { let authority_key = Pubkey::new_unique(); diff --git a/program/tests/set_data.rs b/program/tests/set_data.rs index f5fdce3..c45f70b 100644 --- a/program/tests/set_data.rs +++ b/program/tests/set_data.rs @@ -214,6 +214,168 @@ fn test_set_data_buffer() { ); } +#[test] +fn test_set_data_without_replacing_data() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [4u8; 6]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 1, + compression: 2, + format: 3, + data_source: None, + }, + None, + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key) + .space(Header::LEN + data.len()) + .build(), + Check::account(&metadata_key) + .data_slice(Header::LEN, &data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn test_set_data_shrinks_metadata_account() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let initial_data = [1u8; 12]; + let updated_data = [2u8; 4]; + + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + initial_data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&initial_data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(0), + }, + Some(&updated_data), + ) + .unwrap(), + &[ + Check::success(), + Check::account(&metadata_key) + .space(Header::LEN + updated_data.len()) + .build(), + Check::account(&metadata_key) + .data_slice(Header::LEN, &updated_data) + .build(), + ], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_set_data_with_wrong_authority() { let authority_key = Pubkey::new_unique(); @@ -369,6 +531,150 @@ fn fail_set_data_from_empty_buffer_as_direct_data() { ); } +#[test] +fn fail_set_data_with_invalid_compression() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 5]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 99, // <- invalid compression + format: 0, + data_source: None, + }, + None, + ) + .unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + +#[test] +fn fail_set_data_with_invalid_data_source() { + let authority_key = Pubkey::new_unique(); + + let program_data_key = Pubkey::new_unique(); + let program_data_account = setup_program_data_account(Some(&authority_key)); + + let program_key = Pubkey::new_unique(); + let program_account = setup_program_account(&program_data_key); + + let mut seed = [0u8; SEED_LEN]; + seed[0..3].copy_from_slice("idl".as_bytes()); + + let (metadata_key, _) = + Pubkey::find_program_address(&[program_key.as_ref(), &seed], &PROGRAM_ID); + + let data = [1u8; 5]; + let metadata_account = create_funded_account( + minimum_balance_for(Header::LEN + data.len()), + system_program::ID, + ); + + process_instructions( + &[ + ( + &initialize( + &authority_key, + &program_key, + Some(&program_data_key), + InitializeArgs { + canonical: true, + seed, + encoding: 0, + compression: 0, + format: 0, + data_source: 0, + }, + Some(&data), + ) + .unwrap(), + &[Check::success()], + ), + ( + &set_data( + &metadata_key, + &authority_key, + None, + Some(&program_key), + Some(&program_data_key), + SetDataArgs { + encoding: 0, + compression: 0, + format: 0, + data_source: Some(99), // <- invalid data source + }, + Some(&[2u8; 5]), + ) + .unwrap(), + &[Check::err(ProgramError::InvalidInstructionData)], + ), + ], + &[ + (metadata_key, metadata_account), + (PROGRAM_ID, Account::default()), + (authority_key, Account::default()), + (program_key, program_account), + (program_data_key, program_data_account), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_set_data_from_wrong_owner_buffer() { let authority_key = Pubkey::new_unique(); @@ -502,7 +808,7 @@ fn fail_set_data_with_invalid_encoding() { None, ) .unwrap(), - &[Check::err(ProgramError::InvalidAccountData)], + &[Check::err(ProgramError::InvalidInstructionData)], ), ], &[ diff --git a/program/tests/write.rs b/program/tests/write.rs index cb8e0ea..a83643e 100644 --- a/program/tests/write.rs +++ b/program/tests/write.rs @@ -115,6 +115,47 @@ fn test_write_from_buffer() { ); } +#[test] +fn test_write_with_non_zero_offset_and_overwrite() { + let buffer_key = Pubkey::new_unique(); + let initial_data: [u8; 3] = [9, 8, 7]; + let updated_data: [u8; 2] = [1, 2]; + let buffer_account = + create_funded_account(minimum_balance_for(Buffer::LEN + 8), system_program::ID); + + process_instructions( + &[ + ( + &allocate(&buffer_key, &buffer_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &write(&buffer_key, &buffer_key, None, 5, &initial_data).unwrap(), + &[ + Check::success(), + Check::account(&buffer_key) + .data_slice(Buffer::LEN, &[0, 0, 0, 0, 0, 9, 8, 7]) + .build(), + ], + ), + ( + &write(&buffer_key, &buffer_key, None, 6, &updated_data).unwrap(), + &[ + Check::success(), + Check::account(&buffer_key) + .data_slice(Buffer::LEN, &[0, 0, 0, 0, 0, 9, 1, 2]) + .build(), + ], + ), + ], + &[ + (buffer_key, buffer_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_write_with_wrong_authority() { let buffer_key = Pubkey::new_unique(); @@ -145,6 +186,36 @@ fn fail_write_with_wrong_authority() { ); } +#[test] +fn fail_write_from_wrong_owner_source_buffer() { + // A source buffer owned by a different program should not be + // allowed to write to the target buffer. + let source_key = Pubkey::new_unique(); + + let target_key = Pubkey::new_unique(); + let target_account = + create_funded_account(minimum_balance_for(Buffer::LEN), system_program::ID); + + process_instructions( + &[ + ( + &allocate(&target_key, &target_key, None, None, None).unwrap(), + &[Check::success()], + ), + ( + &write(&target_key, &target_key, Some(&source_key), 0, &[]).unwrap(), + &[Check::err(ProgramError::InvalidAccountOwner)], + ), + ], + &[ + (source_key, Account::default()), + (target_key, target_account), + (PROGRAM_ID, Account::default()), + keyed_account_for_system_program(), + ], + ); +} + #[test] fn fail_write_empty_data_without_source_buffer() { let buffer_key = Pubkey::new_unique(); From 84138cc0a0b8c8c0b847149ecffe3b923ea0d040 Mon Sep 17 00:00:00 2001 From: febo Date: Mon, 18 May 2026 17:21:53 +0100 Subject: [PATCH 4/4] Update error type --- program/src/processor/set_data.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/program/src/processor/set_data.rs b/program/src/processor/set_data.rs index 4474973..c482ccd 100644 --- a/program/src/processor/set_data.rs +++ b/program/src/processor/set_data.rs @@ -86,7 +86,10 @@ pub fn set_data(accounts: &mut [AccountView], instruction_data: &[u8]) -> Progra // Update header and data (if needed). - if let Some(data) = update_header(metadata, args, data)? { + if let Some(data) = update_header(metadata, args, data).map_err(|err| match err { + ProgramError::InvalidAccountData => ProgramError::InvalidInstructionData, + _ => err, + })? { // Realloc the metadata account if necessary. // SAFETY: There are no other active borrows to the `metadata` account data.