Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ProgramTestContext::set_account not updating programs #34780

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion program-runtime/src/loaded_programs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ impl<FG: ForkGraph> LoadedPrograms<FG> {

/// 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<LoadedProgram>) -> Arc<LoadedProgram> {
let (was_occupied, entry) = self.replenish(key, entry);
Expand Down
Binary file added program-test/helloworld.so
Binary file not shown.
Binary file added program-test/helloworld0.so
Binary file not shown.
Binary file added program-test/helloworld1.so
Binary file not shown.
287 changes: 284 additions & 3 deletions program-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}