pub(crate) mod error;
mod source_buffer;
mod target_bpf_v2;
mod target_builtin;
mod target_core_bpf;
use {
crate::bank::{Bank, builtins::core_bpf_migration::target_bpf_v2::TargetBpfV2},
error::CoreBpfMigrationError,
num_traits::{CheckedAdd, CheckedSub},
solana_account::{AccountSharedData, ReadableAccount, WritableAccount},
solana_builtins::core_bpf_migration::CoreBpfMigrationConfig,
solana_compute_budget::compute_budget::ComputeBudget,
solana_hash::Hash,
solana_instruction::error::InstructionError,
solana_loader_v3_interface::state::UpgradeableLoaderState,
solana_program_runtime::{
deploy::deploy_program,
invoke_context::{EnvironmentConfig, InvokeContext},
loaded_programs::{LoadProgramMetrics, ProgramCacheForTxBatch},
sysvar_cache::SysvarCache,
},
solana_pubkey::Pubkey,
solana_sdk_ids::bpf_loader_upgradeable,
solana_svm_callback::InvokeContextCallback,
solana_transaction_context::transaction::TransactionContext,
source_buffer::SourceBuffer,
std::{cmp::Ordering, sync::atomic::Ordering::Relaxed},
target_builtin::TargetBuiltin,
target_core_bpf::TargetCoreBpf,
};
fn checked_add<T: CheckedAdd>(a: T, b: T) -> Result<T, CoreBpfMigrationError> {
a.checked_add(&b)
.ok_or(CoreBpfMigrationError::ArithmeticOverflow)
}
fn checked_sub<T: CheckedSub>(a: T, b: T) -> Result<T, CoreBpfMigrationError> {
a.checked_sub(&b)
.ok_or(CoreBpfMigrationError::ArithmeticOverflow)
}
impl Bank {
fn new_target_program_account(
&self,
program_data_address: &Pubkey,
) -> Result<AccountSharedData, CoreBpfMigrationError> {
let state = UpgradeableLoaderState::Program {
programdata_address: *program_data_address,
};
let lamports =
self.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::size_of_program());
let mut account =
AccountSharedData::new_data(lamports, &state, &bpf_loader_upgradeable::id())?;
account.set_executable(true);
Ok(account)
}
fn new_target_program_data_account(
&self,
source: &SourceBuffer,
upgrade_authority_address: Option<Pubkey>,
) -> Result<AccountSharedData, CoreBpfMigrationError> {
let buffer_metadata_size = UpgradeableLoaderState::size_of_buffer_metadata();
if let UpgradeableLoaderState::Buffer {
authority_address: buffer_authority,
} = bincode::deserialize(&source.buffer_account.data()[..buffer_metadata_size])?
{
if let Some(provided_authority) = upgrade_authority_address {
if upgrade_authority_address != buffer_authority {
return Err(CoreBpfMigrationError::UpgradeAuthorityMismatch(
provided_authority,
buffer_authority,
));
}
}
let elf = &source.buffer_account.data()[buffer_metadata_size..];
let programdata_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
let space = programdata_metadata_size + elf.len();
let lamports = self.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let programdata_metadata = UpgradeableLoaderState::ProgramData {
slot: self.slot,
upgrade_authority_address,
};
let mut account = AccountSharedData::new_data_with_space(
lamports,
&programdata_metadata,
space,
owner,
)?;
account.data_as_mut_slice()[programdata_metadata_size..].copy_from_slice(elf);
Ok(account)
} else {
Err(CoreBpfMigrationError::InvalidBufferAccount(
source.buffer_address,
))
}
}
fn directly_invoke_loader_v3_deploy(
&self,
program_id: &Pubkey,
programdata: &[u8],
) -> Result<(), InstructionError> {
let data_len = programdata.len();
let progradata_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
let elf = &programdata[progradata_metadata_size..];
let mut program_cache_for_tx_batch = ProgramCacheForTxBatch::new(self.slot);
let program_runtime_environments = self
.transaction_processor
.get_environments_for_epoch(self.epoch);
{
let compute_budget = self
.compute_budget()
.unwrap_or(ComputeBudget::new_with_defaults(
false, false,
));
let mut sysvar_cache = SysvarCache::default();
sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
if let Some(account) = self.get_account(pubkey) {
set_sysvar(account.data());
}
});
let mut dummy_transaction_context = TransactionContext::new(
vec![],
self.rent_collector.rent.clone(),
compute_budget.max_instruction_stack_depth,
compute_budget.max_instruction_trace_length,
1,
);
struct MockCallback {}
impl InvokeContextCallback for MockCallback {}
let feature_set = self.feature_set.runtime_features();
let mut dummy_invoke_context = InvokeContext::new(
&mut dummy_transaction_context,
&mut program_cache_for_tx_batch,
EnvironmentConfig::new(
Hash::default(),
0,
&MockCallback {},
&feature_set,
&program_runtime_environments,
&program_runtime_environments,
&sysvar_cache,
),
None,
compute_budget.to_budget(),
compute_budget.to_cost(),
);
let mut load_program_metrics = LoadProgramMetrics::default();
deploy_program(
dummy_invoke_context.get_log_collector(),
&mut load_program_metrics,
dummy_invoke_context.program_cache_for_tx_batch,
program_runtime_environments.program_runtime_v1.clone(),
program_id,
&bpf_loader_upgradeable::id(),
UpgradeableLoaderState::size_of_program().saturating_add(data_len),
elf,
self.slot,
)?;
load_program_metrics.submit_datapoint(&mut dummy_invoke_context.timings);
}
self.transaction_processor
.global_program_cache
.write()
.unwrap()
.merge(
&self.transaction_processor.environments,
self.slot,
&program_cache_for_tx_batch.drain_modified_entries(),
);
Ok(())
}
pub(crate) fn migrate_builtin_to_core_bpf(
&mut self,
builtin_program_id: &Pubkey,
config: &CoreBpfMigrationConfig,
allow_prefunded: bool,
) -> Result<(), CoreBpfMigrationError> {
datapoint_info!(config.datapoint_name, ("slot", self.slot, i64));
let target = TargetBuiltin::new_checked(
self,
builtin_program_id,
&config.migration_target,
allow_prefunded,
)?;
let source = if let Some(expected_hash) = config.verified_build_hash {
SourceBuffer::new_checked_with_verified_build_hash(
self,
&config.source_buffer_address,
expected_hash,
)?
} else {
SourceBuffer::new_checked(self, &config.source_buffer_address)?
};
let new_target_program_account =
self.new_target_program_account(&target.program_data_address)?;
let new_target_program_data_account =
self.new_target_program_data_account(&source, config.upgrade_authority_address)?;
let old_data_size = checked_add(
target.program_account.data().len(),
source.buffer_account.data().len(),
)?;
let new_data_size = checked_add(
new_target_program_account.data().len(),
new_target_program_data_account.data().len(),
)?;
self.directly_invoke_loader_v3_deploy(
&target.program_address,
new_target_program_data_account.data(),
)?;
let lamports_to_burn = checked_add(
target.program_account.lamports(),
source.buffer_account.lamports(),
)
.and_then(|v| checked_add(v, target.program_data_account_lamports))?;
let lamports_to_fund = checked_add(
new_target_program_account.lamports(),
new_target_program_data_account.lamports(),
)?;
self.update_captalization(lamports_to_burn, lamports_to_fund)?;
self.store_account(&target.program_address, &new_target_program_account);
self.store_account(
&target.program_data_address,
&new_target_program_data_account,
);
self.store_account(&source.buffer_address, &AccountSharedData::default());
self.transaction_processor
.builtin_program_ids
.write()
.unwrap()
.remove(&target.program_address);
self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size);
Ok(())
}
#[allow(dead_code)] pub(crate) fn upgrade_core_bpf_program(
&mut self,
core_bpf_program_address: &Pubkey,
source_buffer_address: &Pubkey,
datapoint_name: &'static str,
) -> Result<(), CoreBpfMigrationError> {
datapoint_info!(datapoint_name, ("slot", self.slot, i64));
let target = TargetCoreBpf::new_checked(self, core_bpf_program_address)?;
let source = SourceBuffer::new_checked(self, source_buffer_address)?;
let new_target_program_data_account =
self.new_target_program_data_account(&source, target.upgrade_authority_address)?;
let old_data_size = checked_add(
target.program_data_account.data().len(),
source.buffer_account.data().len(),
)?;
let new_data_size = new_target_program_data_account.data().len();
self.directly_invoke_loader_v3_deploy(
&target.program_address,
new_target_program_data_account.data(),
)?;
let lamports_to_burn = checked_add(
target.program_data_account.lamports(),
source.buffer_account.lamports(),
)?;
let lamports_to_fund = new_target_program_data_account.lamports();
self.update_captalization(lamports_to_burn, lamports_to_fund)?;
self.store_account(
&target.program_data_address,
&new_target_program_data_account,
);
self.store_account(&source.buffer_address, &AccountSharedData::default());
self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size);
Ok(())
}
#[allow(dead_code)] pub(crate) fn upgrade_loader_v2_program_with_loader_v3_program(
&mut self,
loader_v2_bpf_program_address: &Pubkey,
source_buffer_address: &Pubkey,
allow_prefunded: bool,
datapoint_name: &'static str,
) -> Result<(), CoreBpfMigrationError> {
datapoint_info!(datapoint_name, ("slot", self.slot, i64));
let target =
TargetBpfV2::new_checked(self, loader_v2_bpf_program_address, allow_prefunded)?;
let source = SourceBuffer::new_checked(self, source_buffer_address)?;
let new_target_program_account =
self.new_target_program_account(&target.program_data_address)?;
let new_target_program_data_account =
self.new_target_program_data_account(&source, None)?;
let old_data_size = checked_add(
target.program_account.data().len(),
source.buffer_account.data().len(),
)?;
let new_data_size = checked_add(
new_target_program_account.data().len(),
new_target_program_data_account.data().len(),
)?;
self.directly_invoke_loader_v3_deploy(
&target.program_address,
new_target_program_data_account.data(),
)?;
let lamports_to_burn = checked_add(
target.program_account.lamports(),
source.buffer_account.lamports(),
)
.and_then(|v| checked_add(v, target.program_data_account_lamports))?;
let lamports_to_fund = checked_add(
new_target_program_account.lamports(),
new_target_program_data_account.lamports(),
)?;
self.update_captalization(lamports_to_burn, lamports_to_fund)?;
self.store_account(&target.program_address, &new_target_program_account);
self.store_account(
&target.program_data_address,
&new_target_program_data_account,
);
self.store_account(&source.buffer_address, &AccountSharedData::default());
self.calculate_and_update_accounts_data_size_delta_off_chain(old_data_size, new_data_size);
Ok(())
}
fn update_captalization(
&mut self,
lamports_to_burn: u64,
lamports_to_fund: u64,
) -> Result<(), CoreBpfMigrationError> {
match lamports_to_burn.cmp(&lamports_to_fund) {
Ordering::Greater => {
self.capitalization
.fetch_sub(checked_sub(lamports_to_burn, lamports_to_fund)?, Relaxed);
}
Ordering::Less => {
self.capitalization
.fetch_add(checked_sub(lamports_to_fund, lamports_to_burn)?, Relaxed);
}
Ordering::Equal => (),
};
Ok(())
}
}
#[cfg(test)]
pub(crate) mod tests {
use {
super::*,
crate::{
bank::{
Bank,
test_utils::goto_end_of_slot,
tests::{create_genesis_config, create_simple_test_bank},
},
runtime_config::RuntimeConfig,
snapshot_bank_utils::{bank_from_snapshot_archives, bank_to_full_snapshot_archive},
snapshot_utils::create_tmp_accounts_dir_for_tests,
},
agave_feature_set::FeatureSet,
agave_snapshots::snapshot_config::SnapshotConfig,
assert_matches::assert_matches,
solana_account::{
AccountSharedData, ReadableAccount, WritableAccount, state_traits::StateMut,
},
solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING,
solana_builtins::{
BUILTINS,
core_bpf_migration::{CoreBpfMigrationConfig, CoreBpfMigrationTargetType},
prototype::{BuiltinPrototype, StatelessBuiltinPrototype},
},
solana_clock::Slot,
solana_epoch_schedule::EpochSchedule,
solana_feature_gate_interface::{self as feature, Feature},
solana_instruction::{AccountMeta, Instruction},
solana_keypair::Keypair,
solana_loader_v3_interface::{get_program_data_address, state::UpgradeableLoaderState},
solana_message::Message,
solana_native_token::LAMPORTS_PER_SOL,
solana_program_runtime::loaded_programs::{ProgramCacheEntry, ProgramCacheEntryType},
solana_pubkey::Pubkey,
solana_sdk_ids::{bpf_loader, bpf_loader_upgradeable, native_loader, system_program},
solana_signer::Signer,
solana_transaction::Transaction,
std::{fs::File, io::Read, sync::Arc},
test_case::test_case,
};
fn test_elf() -> Vec<u8> {
let mut elf = Vec::new();
File::open("../programs/bpf_loader/test_elfs/out/noop_aligned.so")
.unwrap()
.read_to_end(&mut elf)
.unwrap();
elf
}
pub(crate) struct TestContext {
target_program_address: Pubkey,
source_buffer_address: Pubkey,
upgrade_authority_address: Option<Pubkey>,
elf: Vec<u8>,
}
impl TestContext {
pub(crate) fn new(
bank: &Bank,
target_program_address: &Pubkey,
source_buffer_address: &Pubkey,
upgrade_authority_address: Option<Pubkey>,
) -> Self {
let elf = test_elf();
let source_buffer_account = {
let buffer_metadata_size = UpgradeableLoaderState::size_of_buffer_metadata();
let space = buffer_metadata_size + elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let buffer_metadata = UpgradeableLoaderState::Buffer {
authority_address: upgrade_authority_address,
};
let mut account = AccountSharedData::new_data_with_space(
lamports,
&buffer_metadata,
space,
owner,
)
.unwrap();
account.data_as_mut_slice()[buffer_metadata_size..].copy_from_slice(&elf);
account
};
bank.store_account_and_update_capitalization(
source_buffer_address,
&source_buffer_account,
);
Self {
target_program_address: *target_program_address,
source_buffer_address: *source_buffer_address,
upgrade_authority_address,
elf,
}
}
pub(crate) fn calculate_post_migration_capitalization_and_accounts_data_size_delta_off_chain(
&self,
bank: &Bank,
) -> (u64, i64) {
let builtin_account = bank
.get_account(&self.target_program_address)
.unwrap_or_default();
let source_buffer_account = bank.get_account(&self.source_buffer_address).unwrap();
let resulting_program_data_len = UpgradeableLoaderState::size_of_program();
let resulting_programdata_data_len =
UpgradeableLoaderState::size_of_programdata_metadata() + self.elf.len();
let expected_post_migration_capitalization = bank.capitalization()
- builtin_account.lamports()
- source_buffer_account.lamports()
+ bank.get_minimum_balance_for_rent_exemption(resulting_program_data_len)
+ bank.get_minimum_balance_for_rent_exemption(resulting_programdata_data_len);
let expected_post_migration_accounts_data_size_delta_off_chain =
bank.accounts_data_size_delta_off_chain.load(Relaxed)
+ resulting_program_data_len as i64
+ resulting_programdata_data_len as i64
- builtin_account.data().len() as i64
- source_buffer_account.data().len() as i64;
(
expected_post_migration_capitalization,
expected_post_migration_accounts_data_size_delta_off_chain,
)
}
fn calculate_post_upgrade_capitalization_and_accounts_data_size_delta_off_chain(
&self,
bank: &Bank,
) -> (u64, i64) {
let program_data_account = bank
.get_account(&get_program_data_address(&self.target_program_address))
.unwrap_or_default();
let source_buffer_account = bank.get_account(&self.source_buffer_address).unwrap();
let resulting_programdata_data_len =
UpgradeableLoaderState::size_of_programdata_metadata() + self.elf.len();
let expected_post_migration_capitalization = bank.capitalization()
- program_data_account.lamports()
- source_buffer_account.lamports()
+ bank.get_minimum_balance_for_rent_exemption(resulting_programdata_data_len);
let expected_post_migration_accounts_data_size_delta_off_chain =
bank.accounts_data_size_delta_off_chain.load(Relaxed)
+ resulting_programdata_data_len as i64
- program_data_account.data().len() as i64
- source_buffer_account.data().len() as i64;
(
expected_post_migration_capitalization,
expected_post_migration_accounts_data_size_delta_off_chain,
)
}
pub(crate) fn run_program_checks(&self, bank: &Bank, migration_or_upgrade_slot: Slot) {
assert!(bank.get_account(&self.source_buffer_address).is_none());
let program_account = bank.get_account(&self.target_program_address).unwrap();
let program_data_address = get_program_data_address(&self.target_program_address);
assert_eq!(program_account.owner(), &bpf_loader_upgradeable::id());
assert!(program_account.executable());
let program_account_state: UpgradeableLoaderState = program_account.state().unwrap();
assert_eq!(
program_account_state,
UpgradeableLoaderState::Program {
programdata_address: program_data_address
}
);
let program_data_account = bank.get_account(&program_data_address).unwrap();
assert_eq!(program_data_account.owner(), &bpf_loader_upgradeable::id());
let programdata_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
let program_data_account_state_metadata: UpgradeableLoaderState =
bincode::deserialize(&program_data_account.data()[..programdata_metadata_size])
.unwrap();
assert_eq!(
program_data_account_state_metadata,
UpgradeableLoaderState::ProgramData {
slot: migration_or_upgrade_slot,
upgrade_authority_address: self.upgrade_authority_address },
);
assert_eq!(
&program_data_account.data()[programdata_metadata_size..],
&self.elf,
);
assert!(
!bank
.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(&self.target_program_address)
);
let program_cache = bank
.transaction_processor
.global_program_cache
.read()
.unwrap();
let entries = program_cache.get_flattened_entries(true, true);
let target_entry = entries
.iter()
.find(|(program_id, _last_modification_slot, _entry)| {
program_id == &self.target_program_address
})
.map(|(_program_id, _last_modification_slot, entry)| entry)
.unwrap();
assert_eq!(
target_entry.account_size,
program_account.data().len() + program_data_account.data().len()
);
assert_eq!(target_entry.deployment_slot, migration_or_upgrade_slot);
assert_eq!(target_entry.effective_slot, migration_or_upgrade_slot + 1);
assert_matches!(target_entry.program, ProgramCacheEntryType::Loaded(..));
}
}
#[test_case(Some(Pubkey::new_unique()); "with_upgrade_authority")]
#[test_case(None; "without_upgrade_authority")]
fn test_migrate_builtin(upgrade_authority_address: Option<Pubkey>) {
let mut bank = create_simple_test_bank(0);
let builtin_id = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let builtin_account = {
let builtin_name = String::from("test_builtin");
let account =
AccountSharedData::new_data(1, &builtin_name, &native_loader::id()).unwrap();
bank.store_account_and_update_capitalization(&builtin_id, &account);
bank.add_builtin(
builtin_id,
builtin_name.as_str(),
ProgramCacheEntry::new_builtin(
0,
builtin_name.len(),
|_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
),
);
account
};
assert_eq!(&bank.get_account(&builtin_id).unwrap(), &builtin_account);
let test_context = TestContext::new(
&bank,
&builtin_id,
&source_buffer_address,
upgrade_authority_address,
);
let TestContext {
target_program_address: builtin_id,
source_buffer_address,
..
} = test_context;
let (
expected_post_migration_capitalization,
expected_post_migration_accounts_data_size_delta_off_chain,
) = test_context
.calculate_post_migration_capitalization_and_accounts_data_size_delta_off_chain(&bank);
let core_bpf_migration_config = CoreBpfMigrationConfig {
source_buffer_address,
upgrade_authority_address,
feature_id: Pubkey::new_unique(),
migration_target: CoreBpfMigrationTargetType::Builtin,
verified_build_hash: None,
datapoint_name: "test_migrate_builtin",
};
let migration_slot = bank.slot();
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();
test_context.run_program_checks(&bank, migration_slot);
assert_eq!(
bank.capitalization(),
expected_post_migration_capitalization
);
assert_eq!(
bank.accounts_data_size_delta_off_chain.load(Relaxed),
expected_post_migration_accounts_data_size_delta_off_chain
);
}
#[test_case(Some(Pubkey::new_unique()); "with_upgrade_authority")]
#[test_case(None; "without_upgrade_authority")]
fn test_migrate_stateless_builtin(upgrade_authority_address: Option<Pubkey>) {
let mut bank = create_simple_test_bank(0);
let builtin_id = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let test_context = TestContext::new(
&bank,
&builtin_id,
&source_buffer_address,
upgrade_authority_address,
);
let TestContext {
target_program_address: builtin_id,
source_buffer_address,
..
} = test_context;
assert!(bank.get_account(&builtin_id).is_none());
let (
expected_post_migration_capitalization,
expected_post_migration_accounts_data_size_delta_off_chain,
) = test_context
.calculate_post_migration_capitalization_and_accounts_data_size_delta_off_chain(&bank);
let expected_hash = {
let data = test_elf();
let end_offset = data.iter().rposition(|&x| x != 0).map_or(0, |i| i + 1);
solana_sha256_hasher::hash(&data[..end_offset])
};
let core_bpf_migration_config = CoreBpfMigrationConfig {
source_buffer_address,
upgrade_authority_address,
feature_id: Pubkey::new_unique(),
verified_build_hash: Some(expected_hash),
migration_target: CoreBpfMigrationTargetType::Stateless,
datapoint_name: "test_migrate_stateless_builtin",
};
let migration_slot = bank.slot();
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();
test_context.run_program_checks(&bank, migration_slot);
assert_eq!(
bank.capitalization(),
expected_post_migration_capitalization
);
assert_eq!(
bank.accounts_data_size_delta_off_chain.load(Relaxed),
expected_post_migration_accounts_data_size_delta_off_chain
);
}
#[test]
fn test_migrate_fail_authority_mismatch() {
let mut bank = create_simple_test_bank(0);
let builtin_id = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let upgrade_authority_address = Some(Pubkey::new_unique());
{
let builtin_name = String::from("test_builtin");
let account =
AccountSharedData::new_data(1, &builtin_name, &native_loader::id()).unwrap();
bank.store_account_and_update_capitalization(&builtin_id, &account);
bank.add_builtin(
builtin_id,
builtin_name.as_str(),
ProgramCacheEntry::new_builtin(
0,
builtin_name.len(),
|_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
),
);
account
};
let test_context = TestContext::new(
&bank,
&builtin_id,
&source_buffer_address,
upgrade_authority_address,
);
let TestContext {
target_program_address: builtin_id,
source_buffer_address,
..
} = test_context;
let core_bpf_migration_config = CoreBpfMigrationConfig {
source_buffer_address,
upgrade_authority_address: Some(Pubkey::new_unique()), feature_id: Pubkey::new_unique(),
migration_target: CoreBpfMigrationTargetType::Builtin,
verified_build_hash: None,
datapoint_name: "test_migrate_builtin",
};
assert_matches!(
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap_err(),
CoreBpfMigrationError::UpgradeAuthorityMismatch(_, _)
)
}
#[test]
fn test_migrate_fail_verified_build_mismatch() {
let mut bank = create_simple_test_bank(0);
let builtin_id = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let upgrade_authority_address = Some(Pubkey::new_unique());
{
let builtin_name = String::from("test_builtin");
let account =
AccountSharedData::new_data(1, &builtin_name, &native_loader::id()).unwrap();
bank.store_account_and_update_capitalization(&builtin_id, &account);
bank.add_builtin(
builtin_id,
builtin_name.as_str(),
ProgramCacheEntry::new_builtin(
0,
builtin_name.len(),
|_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
),
);
account
};
let test_context = TestContext::new(
&bank,
&builtin_id,
&source_buffer_address,
upgrade_authority_address,
);
let TestContext {
target_program_address: builtin_id,
source_buffer_address,
..
} = test_context;
let core_bpf_migration_config = CoreBpfMigrationConfig {
source_buffer_address,
upgrade_authority_address: None,
feature_id: Pubkey::new_unique(),
migration_target: CoreBpfMigrationTargetType::Builtin,
verified_build_hash: Some(Hash::default()),
datapoint_name: "test_migrate_builtin",
};
assert_matches!(
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap_err(),
CoreBpfMigrationError::BuildHashMismatch(_, _)
)
}
#[test]
fn test_migrate_none_authority_with_some_buffer_authority() {
let mut bank = create_simple_test_bank(0);
let builtin_id = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let upgrade_authority_address = Some(Pubkey::new_unique());
{
let builtin_name = String::from("test_builtin");
let account =
AccountSharedData::new_data(1, &builtin_name, &native_loader::id()).unwrap();
bank.store_account_and_update_capitalization(&builtin_id, &account);
bank.add_builtin(
builtin_id,
builtin_name.as_str(),
ProgramCacheEntry::new_builtin(
0,
builtin_name.len(),
|_invoke_context, _param0, _param1, _param2, _param3, _param4| {},
),
);
account
};
{
let elf = test_elf();
let buffer_metadata_size = UpgradeableLoaderState::size_of_buffer_metadata();
let space = buffer_metadata_size + elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let buffer_metadata = UpgradeableLoaderState::Buffer {
authority_address: upgrade_authority_address,
};
let mut account =
AccountSharedData::new_data_with_space(lamports, &buffer_metadata, space, owner)
.unwrap();
account.data_as_mut_slice()[buffer_metadata_size..].copy_from_slice(&elf);
bank.store_account_and_update_capitalization(&source_buffer_address, &account);
}
let core_bpf_migration_config = CoreBpfMigrationConfig {
source_buffer_address,
upgrade_authority_address: None, feature_id: Pubkey::new_unique(),
migration_target: CoreBpfMigrationTargetType::Builtin,
verified_build_hash: None,
datapoint_name: "test_migrate_builtin",
};
bank.migrate_builtin_to_core_bpf(&builtin_id, &core_bpf_migration_config, true)
.unwrap();
let program_data_address = get_program_data_address(&builtin_id);
let program_data_account = bank.get_account(&program_data_address).unwrap();
let program_data_account_state: UpgradeableLoaderState =
program_data_account.state().unwrap();
assert_eq!(
program_data_account_state,
UpgradeableLoaderState::ProgramData {
upgrade_authority_address: None,
slot: bank.slot(),
},
);
}
fn set_up_test_core_bpf_program(
bank: &mut Bank,
program_address: &Pubkey,
upgrade_authority_address: Option<Pubkey>,
) {
let programdata_address = get_program_data_address(program_address);
let program_account = {
let data = bincode::serialize(&UpgradeableLoaderState::Program {
programdata_address,
})
.unwrap();
let space = data.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let mut account = AccountSharedData::new(lamports, space, owner);
account.set_executable(true);
account.data_as_mut_slice().copy_from_slice(&data);
bank.store_account_and_update_capitalization(program_address, &account);
account
};
let program_data_account = {
let elf = [4u8; 20]; let programdata_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
let space = programdata_metadata_size + elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let programdata_metadata = UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address,
};
let mut account = AccountSharedData::new_data_with_space(
lamports,
&programdata_metadata,
space,
owner,
)
.unwrap();
account.data_as_mut_slice()[programdata_metadata_size..].copy_from_slice(&elf);
bank.store_account_and_update_capitalization(&programdata_address, &account);
account
};
assert_eq!(
&bank.get_account(program_address).unwrap(),
&program_account
);
assert_eq!(
&bank.get_account(&programdata_address).unwrap(),
&program_data_account
);
}
#[test_case(Some(Pubkey::new_unique()); "with_upgrade_authority")]
#[test_case(None; "without_upgrade_authority")]
fn test_upgrade_core_bpf_program(upgrade_authority_address: Option<Pubkey>) {
let mut bank = create_simple_test_bank(0);
let core_bpf_program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
set_up_test_core_bpf_program(
&mut bank,
&core_bpf_program_address,
upgrade_authority_address,
);
let test_context = TestContext::new(
&bank,
&core_bpf_program_address,
&source_buffer_address,
upgrade_authority_address,
);
let TestContext {
source_buffer_address,
..
} = test_context;
let (
expected_post_upgrade_capitalization,
expected_post_upgrade_accounts_data_size_delta_off_chain,
) = test_context
.calculate_post_upgrade_capitalization_and_accounts_data_size_delta_off_chain(&bank);
let upgrade_slot = bank.slot();
bank.upgrade_core_bpf_program(
&core_bpf_program_address,
&source_buffer_address,
"test_upgrade_core_bpf_program",
)
.unwrap();
test_context.run_program_checks(&bank, upgrade_slot);
assert_eq!(bank.capitalization(), expected_post_upgrade_capitalization);
assert_eq!(
bank.accounts_data_size_delta_off_chain.load(Relaxed),
expected_post_upgrade_accounts_data_size_delta_off_chain
);
}
#[test]
fn test_upgrade_fail_authority_mismatch() {
let mut bank = create_simple_test_bank(0);
let program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
let upgrade_authority_address = Some(Pubkey::new_unique());
set_up_test_core_bpf_program(&mut bank, &program_address, upgrade_authority_address);
let _test_context = TestContext::new(
&bank,
&program_address,
&source_buffer_address,
Some(Pubkey::new_unique()), );
assert_matches!(
bank.upgrade_core_bpf_program(
&program_address,
&source_buffer_address,
"test_upgrade_core_bpf_program"
)
.unwrap_err(),
CoreBpfMigrationError::UpgradeAuthorityMismatch(_, _)
)
}
#[test]
fn test_upgrade_none_authority_with_some_buffer_authority() {
let mut bank = create_simple_test_bank(0);
let program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
set_up_test_core_bpf_program(&mut bank, &program_address, None);
let _test_context = TestContext::new(
&bank,
&program_address,
&source_buffer_address,
Some(Pubkey::new_unique()), );
bank.upgrade_core_bpf_program(
&program_address,
&source_buffer_address,
"test_upgrade_core_bpf_program",
)
.unwrap();
let program_data_address = get_program_data_address(&program_address);
let program_data_account = bank.get_account(&program_data_address).unwrap();
let program_data_account_state: UpgradeableLoaderState =
program_data_account.state().unwrap();
assert_eq!(
program_data_account_state,
UpgradeableLoaderState::ProgramData {
upgrade_authority_address: None,
slot: bank.slot(),
},
);
}
mod cpi_mockup {
use {
solana_instruction::Instruction, solana_program_runtime::declare_process_instruction,
};
declare_process_instruction!(Entrypoint, 0, |invoke_context| {
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let target_program_id = instruction_context.get_key_of_instruction_account(0)?;
let instruction = Instruction::new_with_bytes(*target_program_id, &[], Vec::new());
invoke_context.native_invoke_signed(instruction, &[])
});
}
enum TestPrototype<'a> {
Builtin(&'a BuiltinPrototype),
#[allow(unused)]
Stateless(&'a StatelessBuiltinPrototype),
}
impl<'a> TestPrototype<'a> {
fn deconstruct(&'a self) -> (&'a Pubkey, &'a CoreBpfMigrationConfig) {
match self {
Self::Builtin(prototype) => (
&prototype.program_id,
prototype.core_bpf_migration_config.as_ref().unwrap(),
),
Self::Stateless(prototype) => (
&prototype.program_id,
prototype.core_bpf_migration_config.as_ref().unwrap(),
),
}
}
}
fn activate_feature_and_run_checks(
root_bank: Bank,
test_context: &TestContext,
program_id: &Pubkey,
feature_id: &Pubkey,
source_buffer_address: &Pubkey,
mint_keypair: &Keypair,
slots_per_epoch: u64,
cpi_program_id: &Pubkey,
) {
let (bank, bank_forks) = root_bank.wrap_with_bank_forks_for_tests();
let mut first_slot_in_next_epoch = slots_per_epoch + 1;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);
assert!(!bank.feature_set.is_active(feature_id));
assert!(bank.get_account(source_buffer_address).is_some());
bank.store_account_and_update_capitalization(
feature_id,
&feature::create_account(&Feature::default(), 42),
);
goto_end_of_slot(bank.clone());
first_slot_in_next_epoch += slots_per_epoch;
let migration_slot = first_slot_in_next_epoch;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);
assert!(bank.feature_set.is_active(feature_id));
test_context.run_program_checks(&bank, migration_slot);
goto_end_of_slot(bank.clone());
let next_slot = bank.slot() + 1;
let bank =
Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), next_slot);
bank.process_transaction(&Transaction::new(
&vec![&mint_keypair],
Message::new(
&[Instruction::new_with_bytes(*program_id, &[], Vec::new())],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
))
.unwrap();
bank.process_transaction(&Transaction::new(
&vec![&mint_keypair],
Message::new(
&[Instruction::new_with_bytes(
*cpi_program_id,
&[],
vec![AccountMeta::new_readonly(*program_id, false)],
)],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
))
.unwrap();
goto_end_of_slot(bank.clone());
first_slot_in_next_epoch += slots_per_epoch;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);
assert!(bank.feature_set.is_active(feature_id));
test_context.run_program_checks(&bank, migration_slot);
bank.process_transaction(&Transaction::new(
&vec![&mint_keypair],
Message::new(
&[Instruction::new_with_bytes(*program_id, &[], Vec::new())],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
))
.unwrap();
bank.process_transaction(&Transaction::new(
&vec![&mint_keypair],
Message::new(
&[Instruction::new_with_bytes(
*cpi_program_id,
&[],
vec![AccountMeta::new_readonly(*program_id, false)],
)],
Some(&mint_keypair.pubkey()),
),
bank.last_blockhash(),
))
.unwrap();
}
#[test_case(TestPrototype::Builtin(&BUILTINS[0]); "system")]
#[test_case(TestPrototype::Builtin(&BUILTINS[1]); "vote")]
#[test_case(TestPrototype::Builtin(&BUILTINS[2]); "bpf_loader_deprecated")]
#[test_case(TestPrototype::Builtin(&BUILTINS[3]); "bpf_loader")]
fn test_migrate_builtin_e2e(prototype: TestPrototype) {
let (mut genesis_config, mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);
let (builtin_id, config) = prototype.deconstruct();
let feature_id = &config.feature_id;
let source_buffer_address = &config.source_buffer_address;
let upgrade_authority_address = config.upgrade_authority_address;
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(feature_id);
root_bank.feature_set = Arc::new(feature_set);
let test_context = TestContext::new(
&root_bank,
builtin_id,
source_buffer_address,
upgrade_authority_address,
);
activate_feature_and_run_checks(
root_bank,
&test_context,
builtin_id,
feature_id,
source_buffer_address,
&mint_keypair,
slots_per_epoch,
&cpi_program_id,
);
}
#[test]
fn test_migrate_builtin_e2e_failure() {
let (genesis_config, _mint_keypair) = create_genesis_config(0);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let test_prototype = TestPrototype::Builtin(&BUILTINS[0]); let (builtin_id, config) = test_prototype.deconstruct();
let feature_id = &config.feature_id;
let source_buffer_address = &config.source_buffer_address;
let upgrade_authority_address = Some(Pubkey::new_unique());
let mut feature_set = FeatureSet::all_enabled();
feature_set.inactive_mut().insert(*feature_id);
root_bank.feature_set = Arc::new(feature_set);
let _test_context = TestContext::new(
&root_bank,
builtin_id,
source_buffer_address,
upgrade_authority_address,
);
let (bank, bank_forks) = root_bank.wrap_with_bank_forks_for_tests();
bank.store_account_and_update_capitalization(
source_buffer_address,
&AccountSharedData::default(),
);
bank.store_account_and_update_capitalization(
feature_id,
&feature::create_account(&Feature::default(), 42),
);
goto_end_of_slot(bank.clone());
let bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33);
assert!(bank.feature_set.is_active(feature_id));
assert!(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(builtin_id)
);
assert_eq!(
bank.get_account(builtin_id).unwrap().owner(),
&native_loader::id()
);
goto_end_of_slot(bank.clone());
let bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 96);
assert!(bank.feature_set.is_active(feature_id));
assert!(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(builtin_id)
);
assert_eq!(
bank.get_account(builtin_id).unwrap().owner(),
&native_loader::id()
);
}
#[test]
fn test_migrate_builtin_e2e_init_after_failed_migration() {
let (genesis_config, _mint_keypair) = create_genesis_config(0);
let test_prototype = TestPrototype::Builtin(&BUILTINS[0]); let (builtin_id, config) = test_prototype.deconstruct();
let feature_id = &config.feature_id;
let mut bank = Bank::new_for_tests(&genesis_config);
let mut feature_set = FeatureSet::all_enabled();
feature_set.active_mut().insert(*feature_id, 0);
bank.feature_set = Arc::new(feature_set);
bank.store_account_and_update_capitalization(
feature_id,
&feature::create_account(
&Feature {
activated_at: Some(0),
},
42,
),
);
bank.transaction_processor
.global_program_cache
.write()
.unwrap()
.remove_programs(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.clone()
.into_iter(),
);
bank.transaction_processor
.builtin_program_ids
.write()
.unwrap()
.clear();
bank.compute_and_apply_features_after_snapshot_restore();
assert!(bank.feature_set.is_active(feature_id));
assert!(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(builtin_id)
);
assert_eq!(
bank.get_account(builtin_id).unwrap().owner(),
&native_loader::id()
);
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
goto_end_of_slot(bank.clone());
let bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33);
assert!(bank.feature_set.is_active(feature_id));
assert!(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(builtin_id)
);
assert_eq!(
bank.get_account(builtin_id).unwrap().owner(),
&native_loader::id()
);
}
#[test]
fn test_migrate_builtin_e2e_init_after_successful_migration() {
let (mut genesis_config, _mint_keypair) = create_genesis_config(0);
let test_prototype = TestPrototype::Builtin(&BUILTINS[0]); let (builtin_id, config) = test_prototype.deconstruct();
let feature_id = &config.feature_id;
let upgrade_authority_address = Some(Pubkey::new_unique());
let elf = test_elf();
let program_data_metadata_size = UpgradeableLoaderState::size_of_programdata_metadata();
let program_data_size = program_data_metadata_size + elf.len();
let builtin_program_data_address = get_program_data_address(builtin_id);
let builtin_program_account = AccountSharedData::new_data(
100_000,
&UpgradeableLoaderState::Program {
programdata_address: builtin_program_data_address,
},
&bpf_loader_upgradeable::id(),
)
.unwrap();
let mut builtin_program_data_account = AccountSharedData::new_data_with_space(
100_000,
&UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address,
},
program_data_size,
&bpf_loader_upgradeable::id(),
)
.unwrap();
builtin_program_data_account.data_as_mut_slice()[program_data_metadata_size..]
.copy_from_slice(&elf);
genesis_config
.accounts
.insert(*builtin_id, builtin_program_account.into());
genesis_config.accounts.insert(
builtin_program_data_address,
builtin_program_data_account.into(),
);
let check_builtin_is_bpf = |bank: &Bank| {
assert!(
!bank
.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.contains(builtin_id)
);
let fetched_builtin_program_account = bank.get_account(builtin_id).unwrap();
assert_eq!(
fetched_builtin_program_account.owner(),
&bpf_loader_upgradeable::id()
);
assert_eq!(
bincode::deserialize::<UpgradeableLoaderState>(
fetched_builtin_program_account.data()
)
.unwrap(),
UpgradeableLoaderState::Program {
programdata_address: builtin_program_data_address
}
);
let fetched_builtin_program_data_account =
bank.get_account(&builtin_program_data_address).unwrap();
assert_eq!(
fetched_builtin_program_data_account.owner(),
&bpf_loader_upgradeable::id()
);
assert_eq!(
bincode::deserialize::<UpgradeableLoaderState>(
&fetched_builtin_program_data_account.data()[..program_data_metadata_size]
)
.unwrap(),
UpgradeableLoaderState::ProgramData {
slot: 0,
upgrade_authority_address
}
);
assert_eq!(
&fetched_builtin_program_data_account.data()[program_data_metadata_size..],
elf,
);
};
let mut bank = Bank::new_for_tests(&genesis_config);
check_builtin_is_bpf(&bank);
let mut feature_set = FeatureSet::all_enabled();
feature_set.active_mut().insert(*feature_id, 0);
bank.feature_set = Arc::new(feature_set);
bank.store_account_and_update_capitalization(
feature_id,
&feature::create_account(
&Feature {
activated_at: Some(0),
},
42,
),
);
bank.transaction_processor
.global_program_cache
.write()
.unwrap()
.remove_programs(
bank.transaction_processor
.builtin_program_ids
.read()
.unwrap()
.clone()
.into_iter(),
);
bank.transaction_processor
.builtin_program_ids
.write()
.unwrap()
.clear();
bank.compute_and_apply_features_after_snapshot_restore();
check_builtin_is_bpf(&bank);
}
#[test]
fn test_upgrade_loader_v2_program_with_loader_v3_program() {
let mut bank = create_simple_test_bank(0);
let bpf_loader_v2_program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
{
let program_account = {
let elf = [4u8; 200]; let space = elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader::id();
let mut account = AccountSharedData::new(lamports, space, owner);
account.set_executable(true);
account.data_as_mut_slice().copy_from_slice(&elf);
bank.store_account_and_update_capitalization(
&bpf_loader_v2_program_address,
&account,
);
account
};
assert_eq!(
&bank.get_account(&bpf_loader_v2_program_address).unwrap(),
&program_account
);
};
let test_context = TestContext::new(
&bank,
&bpf_loader_v2_program_address,
&source_buffer_address,
None,
);
let TestContext {
source_buffer_address,
..
} = test_context;
let (
expected_post_upgrade_capitalization,
expected_post_upgrade_accounts_data_size_delta_off_chain,
) = test_context
.calculate_post_migration_capitalization_and_accounts_data_size_delta_off_chain(&bank);
let upgrade_slot = bank.slot();
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap();
test_context.run_program_checks(&bank, upgrade_slot);
assert_eq!(bank.capitalization(), expected_post_upgrade_capitalization);
assert_eq!(
bank.accounts_data_size_delta_off_chain.load(Relaxed),
expected_post_upgrade_accounts_data_size_delta_off_chain
);
let migrated_program_account = bank.get_account(&bpf_loader_v2_program_address).unwrap();
assert_eq!(
migrated_program_account.owner(),
&bpf_loader_upgradeable::id()
);
}
#[test]
fn test_upgrade_loader_v2_program_with_loader_v3_program_fail_invalid_buffer() {
let mut bank = create_simple_test_bank(0);
let bpf_loader_v2_program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
{
let program_account = {
let elf = [4u8; 200]; let space = elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader::id();
let mut account = AccountSharedData::new(lamports, space, owner);
account.set_executable(true);
account.data_as_mut_slice().copy_from_slice(&elf);
bank.store_account_and_update_capitalization(
&bpf_loader_v2_program_address,
&account,
);
account
};
assert_eq!(
&bank.get_account(&bpf_loader_v2_program_address).unwrap(),
&program_account
);
};
{
let elf = test_elf();
let buffer_metadata_size = UpgradeableLoaderState::size_of_buffer_metadata();
let space = buffer_metadata_size + elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader_upgradeable::id();
let buffer_metadata = UpgradeableLoaderState::Program {
programdata_address: Pubkey::new_unique(),
};
let mut account =
AccountSharedData::new_data_with_space(lamports, &buffer_metadata, space, owner)
.unwrap();
account.data_as_mut_slice()[buffer_metadata_size..].copy_from_slice(&elf);
bank.store_account_and_update_capitalization(&source_buffer_address, &account);
}
assert_matches!(
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap_err(),
CoreBpfMigrationError::InvalidBufferAccount(_)
)
}
fn mock_bpf_loader_v2_program(bank: &Bank) -> AccountSharedData {
let elf = [4u8; 200]; let space = elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader::id();
let mut account = AccountSharedData::new(lamports, space, owner);
account.set_executable(true);
account.data_as_mut_slice().copy_from_slice(&elf);
account
}
#[test]
fn test_replace_spl_token_with_p_token_e2e() {
let (mut genesis_config, mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let feature_id = agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;
{
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(&program_id, &program_account);
assert_eq!(
&root_bank.get_account(&program_id).unwrap(),
&program_account
);
};
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&feature_id);
root_bank.feature_set = Arc::new(feature_set);
let test_context = TestContext::new(&root_bank, &program_id, &source_buffer_address, None);
activate_feature_and_run_checks(
root_bank,
&test_context,
&program_id,
&feature_id,
&source_buffer_address,
&mint_keypair,
slots_per_epoch,
&cpi_program_id,
);
}
#[test]
fn test_replace_spl_token_with_p_token_e2e_failure() {
let (genesis_config, _mint_keypair) = create_genesis_config(0);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let feature_id = &agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = &agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
&agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;
{
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(program_id, &program_account);
assert_eq!(
&root_bank.get_account(program_id).unwrap(),
&program_account
);
};
let mut feature_set = FeatureSet::all_enabled();
feature_set.inactive_mut().insert(*feature_id);
root_bank.feature_set = Arc::new(feature_set);
let _test_context = TestContext::new(&root_bank, program_id, source_buffer_address, None);
let (bank, bank_forks) = root_bank.wrap_with_bank_forks_for_tests();
bank.store_account_and_update_capitalization(
source_buffer_address,
&AccountSharedData::default(),
);
bank.store_account_and_update_capitalization(
feature_id,
&feature::create_account(&Feature::default(), 42),
);
goto_end_of_slot(bank.clone());
let bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 33);
assert!(bank.feature_set.is_active(feature_id));
assert_eq!(
bank.get_account(program_id).unwrap().owner(),
&bpf_loader::id()
);
goto_end_of_slot(bank.clone());
let bank = Bank::new_from_parent_with_bank_forks(&bank_forks, bank, &Pubkey::default(), 96);
assert!(bank.feature_set.is_active(feature_id));
assert_eq!(
bank.get_account(program_id).unwrap().owner(),
&bpf_loader::id()
);
}
#[test]
fn test_startup_from_snapshot_after_replace_spl_token_with_p_token() {
let (genesis_config, _mint_keypair) = create_genesis_config(0);
let mut bank = Bank::new_for_tests(&genesis_config);
let bpf_loader_v2_program_address = Pubkey::new_unique();
let source_buffer_address = Pubkey::new_unique();
{
let program_account = {
let elf = [4u8; 200]; let space = elf.len();
let lamports = bank.get_minimum_balance_for_rent_exemption(space);
let owner = &bpf_loader::id();
let mut account = AccountSharedData::new(lamports, space, owner);
account.set_executable(true);
account.data_as_mut_slice().copy_from_slice(&elf);
bank.store_account_and_update_capitalization(
&bpf_loader_v2_program_address,
&account,
);
account
};
assert_eq!(
&bank.get_account(&bpf_loader_v2_program_address).unwrap(),
&program_account
);
};
let test_context = TestContext::new(
&bank,
&bpf_loader_v2_program_address,
&source_buffer_address,
None,
);
let TestContext {
source_buffer_address,
..
} = test_context;
let upgrade_slot = bank.slot();
bank.upgrade_loader_v2_program_with_loader_v3_program(
&bpf_loader_v2_program_address,
&source_buffer_address,
true,
"test_upgrade_loader_v2_program_with_loader_v3_program",
)
.unwrap();
test_context.run_program_checks(&bank, upgrade_slot);
bank.fill_bank_with_ticks_for_tests();
bank.squash();
bank.force_flush_accounts_cache();
let (_tmp_dir, accounts_dir) = create_tmp_accounts_dir_for_tests();
let bank_snapshots_dir = tempfile::TempDir::new().unwrap();
let snapshot_archives_dir = tempfile::TempDir::new().unwrap();
let snapshot_archive_format = SnapshotConfig::default().archive_format;
let full_snapshot_archive_info = bank_to_full_snapshot_archive(
bank_snapshots_dir.path(),
&bank,
None,
snapshot_archives_dir.path(),
snapshot_archives_dir.path(),
snapshot_archive_format,
)
.unwrap();
let roundtrip_bank = bank_from_snapshot_archives(
&[accounts_dir],
bank_snapshots_dir.path(),
&full_snapshot_archive_info,
None,
&genesis_config,
&RuntimeConfig::default(),
None,
None,
false,
false,
false,
ACCOUNTS_DB_CONFIG_FOR_TESTING,
None,
Arc::default(),
)
.unwrap();
let entry = roundtrip_bank
.load_program(&bpf_loader_v2_program_address, upgrade_slot)
.unwrap();
let mut program_cache = roundtrip_bank
.transaction_processor
.global_program_cache
.write()
.unwrap();
program_cache.assign_program(
&roundtrip_bank.transaction_processor.environments,
bpf_loader_v2_program_address,
upgrade_slot,
entry,
);
drop(program_cache);
test_context.run_program_checks(&roundtrip_bank, upgrade_slot);
assert_eq!(bank, roundtrip_bank);
}
#[test]
fn test_replace_spl_token_with_p_token_and_funded_program_data_account_e2e() {
let (mut genesis_config, mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let feature_id = agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;
{
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(&program_id, &program_account);
assert_eq!(
&root_bank.get_account(&program_id).unwrap(),
&program_account
);
};
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&feature_id);
root_bank.feature_set = Arc::new(feature_set);
let test_context = TestContext::new(&root_bank, &program_id, &source_buffer_address, None);
let program_data_account = AccountSharedData::new(1_000_000_000, 0, &system_program::ID);
root_bank.store_account_and_update_capitalization(
&get_program_data_address(&program_id),
&program_data_account,
);
activate_feature_and_run_checks(
root_bank,
&test_context,
&program_id,
&feature_id,
&source_buffer_address,
&mint_keypair,
slots_per_epoch,
&cpi_program_id,
);
}
#[test]
fn test_replace_spl_token_with_p_token_and_existing_program_data_account_failure() {
let (mut genesis_config, _mint_keypair) =
create_genesis_config(1_000_000 * LAMPORTS_PER_SOL);
let slots_per_epoch = 32;
genesis_config.epoch_schedule =
EpochSchedule::custom(slots_per_epoch, slots_per_epoch, false);
let mut root_bank = Bank::new_for_tests(&genesis_config);
let feature_id = agave_feature_set::replace_spl_token_with_p_token::id();
let program_id = agave_feature_set::replace_spl_token_with_p_token::SPL_TOKEN_PROGRAM_ID;
let source_buffer_address =
agave_feature_set::replace_spl_token_with_p_token::PTOKEN_PROGRAM_BUFFER;
let program_account = mock_bpf_loader_v2_program(&root_bank);
root_bank.store_account_and_update_capitalization(&program_id, &program_account);
assert_eq!(
&root_bank.get_account(&program_id).unwrap(),
&program_account
);
let cpi_program_id = Pubkey::new_unique();
let cpi_program_name = "mock_cpi_program";
root_bank.add_builtin(
cpi_program_id,
cpi_program_name,
ProgramCacheEntry::new_builtin(0, cpi_program_name.len(), cpi_mockup::Entrypoint::vm),
);
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&feature_id);
root_bank.feature_set = Arc::new(feature_set);
let _test_context = TestContext::new(&root_bank, &program_id, &source_buffer_address, None);
let program_data_account =
AccountSharedData::new(1_000_000_000, 0, &bpf_loader_upgradeable::ID);
root_bank.store_account_and_update_capitalization(
&get_program_data_address(&program_id),
&program_data_account,
);
let (bank, bank_forks) = root_bank.wrap_with_bank_forks_for_tests();
let mut first_slot_in_next_epoch = slots_per_epoch + 1;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);
assert!(!bank.feature_set.is_active(&feature_id));
assert!(bank.get_account(&source_buffer_address).is_some());
bank.store_account_and_update_capitalization(
&feature_id,
&feature::create_account(&Feature::default(), 42),
);
goto_end_of_slot(bank.clone());
first_slot_in_next_epoch += slots_per_epoch;
let _migration_slot = first_slot_in_next_epoch;
let bank = Bank::new_from_parent_with_bank_forks(
&bank_forks,
bank,
&Pubkey::default(),
first_slot_in_next_epoch,
);
assert!(bank.feature_set.is_active(&feature_id));
let program_account = bank.get_account(&program_id).unwrap();
assert_eq!(program_account.owner(), &bpf_loader::id());
let program_data_account = bank
.get_account(&get_program_data_address(&program_id))
.unwrap();
assert_eq!(program_data_account.data().len(), 0);
assert_eq!(program_data_account.lamports(), 1_000_000_000);
}
}