diff --git a/program-runtime/src/loaded_programs.rs b/program-runtime/src/loaded_programs.rs index b7b92a0409c800..64b08bd4218521 100644 --- a/program-runtime/src/loaded_programs.rs +++ b/program-runtime/src/loaded_programs.rs @@ -691,7 +691,7 @@ impl LoadedPrograms { /// Assign the program `entry` to the given `key` in the cache. /// This is typically called when a deployed program is managed (un-/re-/deployed) via - /// loader instructions. Because of the cooldown, entires can not have the same + /// loader instructions. Because of the cooldown, entries can not have the same /// deployment_slot and effective_slot. pub fn assign_program(&mut self, key: Pubkey, entry: Arc) -> Arc { let (was_occupied, entry) = self.replenish(key, entry); diff --git a/program-test/helloworld.so b/program-test/helloworld.so new file mode 100755 index 00000000000000..bbba24bf9bd87b Binary files /dev/null and b/program-test/helloworld.so differ diff --git a/program-test/helloworld0.so b/program-test/helloworld0.so new file mode 100755 index 00000000000000..6cf9ef3c449033 Binary files /dev/null and b/program-test/helloworld0.so differ diff --git a/program-test/helloworld1.so b/program-test/helloworld1.so new file mode 100755 index 00000000000000..ca2aade5f9e668 Binary files /dev/null and b/program-test/helloworld1.so differ diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index 32dbb276ee2c7a..79ee90f8d844dd 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -1149,7 +1149,7 @@ impl ProgramTestContext { bank } else { bank_forks - .insert(Bank::warp_from_parent( + .insert_from_ledger(Bank::warp_from_parent( bank, &Pubkey::default(), pre_warp_slot, @@ -1186,7 +1186,7 @@ impl ProgramTestContext { }); // warp_bank is frozen so go forward to get unfrozen bank at warp_slot - bank_forks.insert(Bank::new_from_parent( + bank_forks.insert_from_ledger(Bank::new_from_parent( warp_bank, &Pubkey::default(), warp_slot, @@ -1235,7 +1235,7 @@ impl ProgramTestContext { let mut warp_bank = Bank::new_from_parent(bank, &Pubkey::default(), warp_slot); warp_bank.force_reward_interval_end_for_tests(); - bank_forks.insert(warp_bank); + bank_forks.insert_from_ledger(warp_bank); // Update block commitment cache, otherwise banks server will poll at // the wrong slot @@ -1270,3 +1270,284 @@ impl ProgramTestContext { .register_hard_fork(hard_fork_slot) } } + +#[cfg(test)] +mod test { + use crate::{read_file, tokio, ProgramTest, ProgramTestContext}; + use solana_sdk::account::{Account, AccountSharedData}; + use solana_sdk::account_utils::StateMut; + use solana_sdk::bpf_loader_upgradeable::UpgradeableLoaderState; + use solana_sdk::instruction::AccountMeta; + use solana_sdk::signature::Signer; + use solana_sdk::transaction::Transaction; + use solana_sdk::{bpf_loader, bpf_loader_upgradeable}; + + use super::*; + + #[tokio::test] + async fn test_set_non_upgradeable_program_account_does_not_work() { + let program_id = Pubkey::new_unique(); + + let mut context = ProgramTest::default().start_with_context().await; + + set_non_upgradeable_program_account(&mut context, program_id, "helloworld0.so"); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 0" + ); + + set_non_upgradeable_program_account(&mut context, program_id, "helloworld1.so"); + + context.warp_to_slot(2).unwrap(); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 0" // TODO should be 1 + ); + } + + fn set_non_upgradeable_program_account( + context: &mut ProgramTestContext, + program_id: Pubkey, + path: &str, + ) { + let program_data = read_file(path); + + context.set_account( + &program_id, + &AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(program_data.len()).max(1), + data: program_data, + owner: bpf_loader::id(), + executable: true, + rent_epoch: 0, + }), + ); + } + + #[tokio::test] + async fn test_upgradeable_program_account_set_program_data_account_data_works() { + let program_id = Pubkey::new_unique(); + + let mut context = ProgramTest::default().start_with_context().await; + + let program_data_address = Pubkey::new_unique(); + context.set_account( + &program_id, + &upgradeable_program_account(program_data_address), + ); + + context.set_account( + &program_data_address, + &program_data_account("helloworld0.so", 0), + ); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 0" + ); + + context.set_account( + &program_data_address, + &program_data_account("helloworld1.so", 1), + ); + + context.warp_to_slot(2).unwrap(); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 1" + ); + } + + #[tokio::test] + async fn test_upgradeable_program_account_set_program_data_account_address_works() { + let program_id = Pubkey::new_unique(); + + let mut context = ProgramTest::default().start_with_context().await; + + let program_data_address = Pubkey::new_unique(); + context.set_account( + &program_id, + &upgradeable_program_account(program_data_address), + ); + + context.set_account( + &program_data_address, + &program_data_account("helloworld1.so", 0), + ); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 1" + ); + + context.warp_to_slot(2).unwrap(); + + let program_data_address = Pubkey::new_unique(); + context.set_account( + &program_id, + &upgradeable_program_account(program_data_address), + ); + context.set_account( + &program_data_address, + &program_data_account("helloworld0.so", 2), + ); + + context.warp_to_slot(3).unwrap(); + + let result = simulate_transaction(&mut context, program_id).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 0" + ); + } + + #[tokio::test] + async fn test_set_non_program_account_works() { + let program_id = Pubkey::new_unique(); + + let mut context = ProgramTest::default().start_with_context().await; + + let program_data_address = Pubkey::new_unique(); + context.set_account( + &program_id, + &upgradeable_program_account(program_data_address), + ); + context.set_account( + &program_data_address, + &program_data_account("helloworld.so", 0), + ); + + let account_address = Pubkey::new_unique(); + + context.set_account( + &account_address, + &AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(1).max(1), + data: vec![123], + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: 0, + }), + ); + + let result = + simulate_transaction_with_account(&mut context, program_id, account_address).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 123" + ); + + context.set_account( + &account_address, + &AccountSharedData::from(Account { + lamports: Rent::default().minimum_balance(1).max(1), + data: vec![234], + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: 0, + }), + ); + + let result = + simulate_transaction_with_account(&mut context, program_id, account_address).await; + assert_eq!( + result.simulation_details.unwrap().logs[1], + "Program log: Hello World Rust program entrypoint 234" + ); + } + + fn upgradeable_program_account(program_data_address: Pubkey) -> AccountSharedData { + let account_len = UpgradeableLoaderState::size_of_program(); + + let mut account = Account { + lamports: Rent::default().minimum_balance(account_len).max(1), + data: vec![0; account_len], + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: 0, + }; + + account + .set_state(&UpgradeableLoaderState::Program { + programdata_address: program_data_address, + }) + .unwrap(); + + AccountSharedData::from(account) + } + + fn program_data_account(path: &str, slot: Slot) -> AccountSharedData { + let program_data = read_file(path); + + let program_data_len = + UpgradeableLoaderState::size_of_programdata_metadata() + program_data.len(); + + let mut program_data_account = Account { + lamports: Rent::default().minimum_balance(program_data_len).max(1), + data: vec![0; program_data_len], + owner: bpf_loader_upgradeable::id(), + executable: true, + rent_epoch: 0, + }; + + program_data_account + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: None, + }) + .unwrap(); + + program_data_account.data[UpgradeableLoaderState::size_of_programdata_metadata()..] + .copy_from_slice(&program_data); + + AccountSharedData::from(program_data_account) + } + + async fn simulate_transaction( + context: &mut ProgramTestContext, + program_id: Pubkey, + ) -> solana_banks_interface::BanksTransactionResultWithSimulation { + let tx = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &[], + vec![AccountMeta::new_readonly(program_id, false)], + )], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.simulate_transaction(tx).await.unwrap() + } + + async fn simulate_transaction_with_account( + context: &mut ProgramTestContext, + program_id: Pubkey, + account_address: Pubkey, + ) -> solana_banks_interface::BanksTransactionResultWithSimulation { + let tx = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &[], + vec![ + AccountMeta::new_readonly(program_id, false), + AccountMeta::new_readonly(account_address, false), + ], + )], + Some(&context.payer.pubkey()), + &[&context.payer], + context.last_blockhash, + ); + + context.banks_client.simulate_transaction(tx).await.unwrap() + } +}