use {
bincode::{deserialize, serialize},
litesvm::LiteSVM,
solana_account::Account,
solana_address::{address, Address},
solana_clock::Clock,
solana_instruction::{account_meta::AccountMeta, Instruction},
solana_keypair::Keypair,
solana_loader_v3_interface::{
get_program_data_address, instruction::UpgradeableLoaderInstruction,
state::UpgradeableLoaderState,
},
solana_message::Message,
solana_native_token::LAMPORTS_PER_SOL,
solana_sdk_ids::bpf_loader_upgradeable,
solana_signer::Signer,
solana_transaction::Transaction,
std::path::PathBuf,
};
fn read_counter_program() -> Vec<u8> {
let mut so_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
so_path.push("test_programs/target/deploy/counter.so");
std::fs::read(so_path).unwrap()
}
fn set_program_upgrade_authority(
svm: &mut LiteSVM,
program_id: Address,
authority: Address,
) -> Address {
let programdata_address = get_program_data_address(&program_id);
let mut programdata_account = svm.get_account(&programdata_address).unwrap();
let metadata_len = UpgradeableLoaderState::size_of_programdata_metadata();
let metadata =
deserialize::<UpgradeableLoaderState>(&programdata_account.data[..metadata_len]).unwrap();
let slot = match metadata {
UpgradeableLoaderState::ProgramData { slot, .. } => slot,
other => panic!("expected ProgramData account, got {other:?}"),
};
let mut data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
slot,
upgrade_authority_address: Some(authority),
})
.unwrap();
data.extend_from_slice(&programdata_account.data[metadata_len..]);
programdata_account.data = data;
svm.set_account(programdata_address, programdata_account)
.unwrap();
programdata_address
}
fn invoke_counter(
svm: &mut LiteSVM,
program_id: Address,
counter_address: Address,
payer: &Keypair,
deduper: u8,
) {
let payer_address = payer.pubkey();
let tx = Transaction::new(
&[payer],
Message::new_with_blockhash(
&[Instruction {
program_id,
accounts: vec![AccountMeta::new(counter_address, false)],
data: vec![0, deduper],
}],
Some(&payer_address),
&svm.latest_blockhash(),
),
svm.latest_blockhash(),
);
svm.send_transaction(tx).unwrap();
}
#[test_log::test]
fn close_upgradeable_program_keeps_vm_usable() {
let authority_kp = Keypair::new();
let authority = authority_kp.pubkey();
let program_id = address!("GtdambwDgHWrDJdVPBkEHGhCwokqgAoch162teUjJse2");
let counter_address = address!("J39wvrFY2AkoAUCke5347RMNk3ditxZfVidoZ7U6Fguf");
let mut svm = LiteSVM::new();
svm.airdrop(&authority, LAMPORTS_PER_SOL).unwrap();
svm.add_program(program_id, &read_counter_program())
.unwrap();
let programdata_address = set_program_upgrade_authority(&mut svm, program_id, authority);
let original_program_account = svm.get_account(&program_id).unwrap();
let original_programdata_account = svm.get_account(&programdata_address).unwrap();
{
svm.set_account(
counter_address,
Account {
lamports: 5,
data: vec![0_u8; std::mem::size_of::<u32>()],
owner: program_id,
..Default::default()
},
)
.unwrap();
invoke_counter(&mut svm, program_id, counter_address, &authority_kp, 0);
assert_eq!(
svm.get_account(&counter_address).unwrap().data,
1u32.to_le_bytes().to_vec()
);
}
let current_slot = svm.get_sysvar::<Clock>().slot;
svm.warp_to_slot(current_slot + 1);
{
let close_ix = Instruction::new_with_bytes(
bpf_loader_upgradeable::id(),
&serialize(&UpgradeableLoaderInstruction::Close).unwrap(),
vec![
AccountMeta::new(programdata_address, false),
AccountMeta::new(authority, false),
AccountMeta::new_readonly(authority, true),
AccountMeta::new(program_id, false),
],
);
let close_tx = Transaction::new(
&[&authority_kp],
Message::new_with_blockhash(&[close_ix], Some(&authority), &svm.latest_blockhash()),
svm.latest_blockhash(),
);
svm.send_transaction(close_tx).unwrap();
assert!(svm.get_account(&programdata_address).is_none());
}
{
svm.set_account(programdata_address, original_programdata_account)
.unwrap();
svm.set_account(program_id, original_program_account)
.unwrap();
invoke_counter(&mut svm, program_id, counter_address, &authority_kp, 1);
assert_eq!(
svm.get_account(&counter_address).unwrap().data,
2u32.to_le_bytes().to_vec()
);
}
}