4747 lines
168 KiB
Rust
4747 lines
168 KiB
Rust
#![cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
|
|
#![allow(clippy::clone_on_copy)]
|
|
#![allow(clippy::needless_range_loop)]
|
|
#![allow(clippy::redundant_clone)]
|
|
#![allow(clippy::needless_borrow)]
|
|
#![allow(clippy::cmp_owned)]
|
|
#![allow(clippy::needless_collect)]
|
|
#![allow(clippy::match_like_matches_macro)]
|
|
#![allow(clippy::unnecessary_cast)]
|
|
#![allow(clippy::uninlined_format_args)]
|
|
|
|
#[cfg(feature = "sbf_rust")]
|
|
use {
|
|
itertools::izip,
|
|
solana_account_decoder::parse_bpf_loader::{
|
|
parse_bpf_upgradeable_loader, BpfUpgradeableLoaderAccountType,
|
|
},
|
|
solana_accounts_db::transaction_results::{
|
|
DurableNonceFee, InnerInstruction, TransactionExecutionDetails, TransactionExecutionResult,
|
|
TransactionResults,
|
|
},
|
|
solana_ledger::token_balances::collect_token_balances,
|
|
solana_program_runtime::{
|
|
compute_budget::ComputeBudget,
|
|
compute_budget_processor::process_compute_budget_instructions, timings::ExecuteTimings,
|
|
},
|
|
solana_rbpf::vm::ContextObject,
|
|
solana_runtime::{
|
|
bank::TransactionBalancesSet,
|
|
loader_utils::{
|
|
create_program, load_and_finalize_program, load_program, load_program_from_file,
|
|
load_upgradeable_buffer, load_upgradeable_program, set_upgrade_authority,
|
|
upgrade_program,
|
|
},
|
|
},
|
|
solana_sbf_rust_invoke::instructions::*,
|
|
solana_sbf_rust_realloc::instructions::*,
|
|
solana_sbf_rust_realloc_invoke::instructions::*,
|
|
solana_sdk::{
|
|
account::{ReadableAccount, WritableAccount},
|
|
account_utils::StateMut,
|
|
bpf_loader_upgradeable,
|
|
clock::MAX_PROCESSING_AGE,
|
|
compute_budget::ComputeBudgetInstruction,
|
|
entrypoint::MAX_PERMITTED_DATA_INCREASE,
|
|
feature_set::{self, FeatureSet},
|
|
fee::FeeStructure,
|
|
loader_instruction,
|
|
message::{v0::LoadedAddresses, SanitizedMessage},
|
|
signature::keypair_from_seed,
|
|
stake,
|
|
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
|
|
sysvar::{self, clock, rent},
|
|
transaction::VersionedTransaction,
|
|
},
|
|
solana_transaction_status::{
|
|
map_inner_instructions, ConfirmedTransactionWithStatusMeta, TransactionStatusMeta,
|
|
TransactionWithStatusMeta, VersionedTransactionWithStatusMeta,
|
|
},
|
|
std::collections::HashMap,
|
|
};
|
|
use {
|
|
solana_program_runtime::invoke_context::mock_process_instruction,
|
|
solana_runtime::{
|
|
bank::Bank,
|
|
bank_client::BankClient,
|
|
bank_forks::BankForks,
|
|
genesis_utils::{
|
|
bootstrap_validator_stake_lamports, create_genesis_config,
|
|
create_genesis_config_with_leader_ex, GenesisConfigInfo,
|
|
},
|
|
},
|
|
solana_sdk::{
|
|
account::AccountSharedData,
|
|
bpf_loader, bpf_loader_deprecated,
|
|
client::SyncClient,
|
|
clock::UnixTimestamp,
|
|
fee_calculator::FeeRateGovernor,
|
|
genesis_config::ClusterType,
|
|
hash::Hash,
|
|
instruction::{AccountMeta, Instruction, InstructionError},
|
|
message::Message,
|
|
pubkey::Pubkey,
|
|
rent::Rent,
|
|
signature::{Keypair, Signer},
|
|
system_program,
|
|
transaction::{SanitizedTransaction, Transaction, TransactionError},
|
|
},
|
|
std::{
|
|
cell::RefCell,
|
|
str::FromStr,
|
|
sync::{Arc, RwLock},
|
|
time::Duration,
|
|
},
|
|
};
|
|
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn process_transaction_and_record_inner(
|
|
bank: &Bank,
|
|
tx: Transaction,
|
|
) -> (
|
|
Result<(), TransactionError>,
|
|
Vec<Vec<InnerInstruction>>,
|
|
Vec<String>,
|
|
) {
|
|
let signature = tx.signatures.first().unwrap().clone();
|
|
let txs = vec![tx];
|
|
let tx_batch = bank.prepare_batch_for_tests(txs);
|
|
let mut results = bank
|
|
.load_execute_and_commit_transactions(
|
|
&tx_batch,
|
|
MAX_PROCESSING_AGE,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
&mut ExecuteTimings::default(),
|
|
None,
|
|
)
|
|
.0;
|
|
let result = results
|
|
.fee_collection_results
|
|
.swap_remove(0)
|
|
.and_then(|_| bank.get_signature_status(&signature).unwrap());
|
|
let execution_details = results
|
|
.execution_results
|
|
.swap_remove(0)
|
|
.details()
|
|
.expect("tx should be executed")
|
|
.clone();
|
|
let inner_instructions = execution_details
|
|
.inner_instructions
|
|
.expect("cpi recording should be enabled");
|
|
let log_messages = execution_details
|
|
.log_messages
|
|
.expect("log recording should be enabled");
|
|
(result, inner_instructions, log_messages)
|
|
}
|
|
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn execute_transactions(
|
|
bank: &Bank,
|
|
txs: Vec<Transaction>,
|
|
) -> Vec<Result<ConfirmedTransactionWithStatusMeta, TransactionError>> {
|
|
let batch = bank.prepare_batch_for_tests(txs.clone());
|
|
let mut timings = ExecuteTimings::default();
|
|
let mut mint_decimals = HashMap::new();
|
|
let tx_pre_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);
|
|
let (
|
|
TransactionResults {
|
|
execution_results, ..
|
|
},
|
|
TransactionBalancesSet {
|
|
pre_balances,
|
|
post_balances,
|
|
..
|
|
},
|
|
) = bank.load_execute_and_commit_transactions(
|
|
&batch,
|
|
std::usize::MAX,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
&mut timings,
|
|
None,
|
|
);
|
|
let tx_post_token_balances = collect_token_balances(&bank, &batch, &mut mint_decimals);
|
|
|
|
izip!(
|
|
txs.iter(),
|
|
execution_results.into_iter(),
|
|
pre_balances.into_iter(),
|
|
post_balances.into_iter(),
|
|
tx_pre_token_balances.into_iter(),
|
|
tx_post_token_balances.into_iter(),
|
|
)
|
|
.map(
|
|
|(
|
|
tx,
|
|
execution_result,
|
|
pre_balances,
|
|
post_balances,
|
|
pre_token_balances,
|
|
post_token_balances,
|
|
)| {
|
|
match execution_result {
|
|
TransactionExecutionResult::Executed { details, .. } => {
|
|
let TransactionExecutionDetails {
|
|
status,
|
|
log_messages,
|
|
inner_instructions,
|
|
durable_nonce_fee,
|
|
return_data,
|
|
executed_units,
|
|
..
|
|
} = details;
|
|
|
|
let lamports_per_signature = match durable_nonce_fee {
|
|
Some(DurableNonceFee::Valid(lamports_per_signature)) => {
|
|
Some(lamports_per_signature)
|
|
}
|
|
Some(DurableNonceFee::Invalid) => None,
|
|
None => bank.get_lamports_per_signature_for_blockhash(
|
|
&tx.message().recent_blockhash,
|
|
),
|
|
}
|
|
.expect("lamports_per_signature must be available");
|
|
let fee = bank.get_fee_for_message_with_lamports_per_signature(
|
|
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
|
|
lamports_per_signature,
|
|
);
|
|
|
|
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
|
map_inner_instructions(inner_instructions).collect()
|
|
});
|
|
|
|
let tx_status_meta = TransactionStatusMeta {
|
|
status,
|
|
fee,
|
|
pre_balances,
|
|
post_balances,
|
|
pre_token_balances: Some(pre_token_balances),
|
|
post_token_balances: Some(post_token_balances),
|
|
inner_instructions,
|
|
log_messages,
|
|
rewards: None,
|
|
loaded_addresses: LoadedAddresses::default(),
|
|
return_data,
|
|
compute_units_consumed: Some(executed_units),
|
|
};
|
|
|
|
Ok(ConfirmedTransactionWithStatusMeta {
|
|
slot: bank.slot(),
|
|
tx_with_meta: TransactionWithStatusMeta::Complete(
|
|
VersionedTransactionWithStatusMeta {
|
|
transaction: VersionedTransaction::from(tx.clone()),
|
|
meta: tx_status_meta,
|
|
},
|
|
),
|
|
block_time: None,
|
|
})
|
|
}
|
|
TransactionExecutionResult::NotExecuted(err) => Err(err.clone()),
|
|
}
|
|
},
|
|
)
|
|
.collect()
|
|
}
|
|
|
|
fn load_program_and_advance_slot(
|
|
bank_client: &mut BankClient,
|
|
bank_forks: &RwLock<BankForks>,
|
|
loader_id: &Pubkey,
|
|
payer_keypair: &Keypair,
|
|
name: &str,
|
|
) -> (Arc<Bank>, Pubkey) {
|
|
let pubkey = load_program(bank_client, loader_id, payer_keypair, name);
|
|
(
|
|
bank_client
|
|
.advance_slot(1, bank_forks, &Pubkey::default())
|
|
.expect("Failed to advance the slot"),
|
|
pubkey,
|
|
)
|
|
}
|
|
|
|
fn load_upgradeable_program_wrapper(
|
|
bank_client: &BankClient,
|
|
mint_keypair: &Keypair,
|
|
authority_keypair: &Keypair,
|
|
name: &str,
|
|
) -> Pubkey {
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
bank_client,
|
|
mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
authority_keypair,
|
|
name,
|
|
);
|
|
program_keypair.pubkey()
|
|
}
|
|
|
|
fn load_upgradeable_program_and_advance_slot(
|
|
bank_client: &mut BankClient,
|
|
bank_forks: &RwLock<BankForks>,
|
|
mint_keypair: &Keypair,
|
|
authority_keypair: &Keypair,
|
|
name: &str,
|
|
) -> (Arc<Bank>, Pubkey) {
|
|
let program_id =
|
|
load_upgradeable_program_wrapper(bank_client, mint_keypair, authority_keypair, name);
|
|
|
|
// load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective
|
|
// after 2 slots. They need to be called individually to create the correct fork graph in between.
|
|
bank_client
|
|
.advance_slot(1, bank_forks, &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
let bank = bank_client
|
|
.advance_slot(1, bank_forks, &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
(bank, program_id)
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
|
|
fn test_program_sbf_sanity() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[
|
|
("alloc", true),
|
|
("alt_bn128", true),
|
|
("alt_bn128_compression", true),
|
|
("sbf_to_sbf", true),
|
|
("float", true),
|
|
("multiple_static", true),
|
|
("noop", true),
|
|
("noop++", true),
|
|
("panic", false),
|
|
("poseidon", true),
|
|
("relative_call", true),
|
|
("return_data", true),
|
|
("sanity", true),
|
|
("sanity++", true),
|
|
("secp256k1_recover", true),
|
|
("sha", true),
|
|
("stdlib", true),
|
|
("struct_pass", true),
|
|
("struct_ret", true),
|
|
]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[
|
|
("solana_sbf_rust_128bit", true),
|
|
("solana_sbf_rust_alloc", true),
|
|
("solana_sbf_rust_alt_bn128", true),
|
|
("solana_sbf_rust_alt_bn128_compression", true),
|
|
("solana_sbf_rust_curve25519", true),
|
|
("solana_sbf_rust_custom_heap", true),
|
|
("solana_sbf_rust_dep_crate", true),
|
|
("solana_sbf_rust_external_spend", false),
|
|
("solana_sbf_rust_iter", true),
|
|
("solana_sbf_rust_many_args", true),
|
|
("solana_sbf_rust_membuiltins", true),
|
|
("solana_sbf_rust_noop", true),
|
|
("solana_sbf_rust_panic", false),
|
|
("solana_sbf_rust_param_passing", true),
|
|
("solana_sbf_rust_poseidon", true),
|
|
("solana_sbf_rust_rand", true),
|
|
("solana_sbf_rust_sanity", true),
|
|
("solana_sbf_rust_secp256k1_recover", true),
|
|
("solana_sbf_rust_sha", true),
|
|
]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
println!("Test program: {:?}", program.0);
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
// Call user program
|
|
let (_, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program.0,
|
|
);
|
|
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
AccountMeta::new(Keypair::new().pubkey(), false),
|
|
];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
if program.1 {
|
|
assert!(result.is_ok(), "{result:?}");
|
|
} else {
|
|
assert!(result.is_err(), "{result:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
|
|
fn test_program_sbf_loader_deprecated() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[("deprecated_loader")]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[("solana_sbf_rust_deprecated_loader")]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
println!("Test program: {:?}", program);
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
genesis_config
|
|
.accounts
|
|
.remove(&solana_sdk::feature_set::disable_deploy_of_alloc_free_syscall::id())
|
|
.unwrap();
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let program_id = create_program(&bank, &bpf_loader_deprecated::id(), program);
|
|
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[255], account_metas);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|
|
|
|
/// This test is written with bpf_loader v2 specific instructions, which will be
|
|
/// deprecated when `disable_bpf_loader_instructions` feature is activated.
|
|
///
|
|
/// The same test has been migrated to
|
|
/// `test_sol_alloc_free_no_longer_deployable_with_upgradeable_loader` with a new version
|
|
/// of bpf_upgradeable_loader!
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_sol_alloc_free_no_longer_deployable() {
|
|
solana_logger::setup();
|
|
|
|
let program_keypair = Keypair::new();
|
|
let program_address = program_keypair.pubkey();
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
// deactivate `disable_bpf_loader_instructions` feature so that the program
|
|
// can be loaded, finalized and tested.
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::disable_bpf_loader_instructions::id());
|
|
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id());
|
|
|
|
let (bank, _bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
|
|
let elf = load_program_from_file("solana_sbf_rust_deprecated_loader");
|
|
let mut program_account = AccountSharedData::new(1, elf.len(), &bpf_loader::id());
|
|
program_account
|
|
.data_as_mut_slice()
|
|
.get_mut(..)
|
|
.unwrap()
|
|
.copy_from_slice(&elf);
|
|
bank.store_account(&program_address, &program_account);
|
|
|
|
let finalize_tx = Transaction::new(
|
|
&[&mint_keypair, &program_keypair],
|
|
Message::new(
|
|
&[loader_instruction::finalize(
|
|
&program_keypair.pubkey(),
|
|
&bpf_loader::id(),
|
|
)],
|
|
Some(&mint_keypair.pubkey()),
|
|
),
|
|
bank.last_blockhash(),
|
|
);
|
|
|
|
// Try and deploy a program that depends on _sol_alloc_free
|
|
assert_eq!(
|
|
bank.process_transaction(&finalize_tx).unwrap_err(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
#[should_panic(
|
|
expected = "called `Result::unwrap()` on an `Err` value: TransactionError(InstructionError(1, InvalidAccountData))"
|
|
)]
|
|
fn test_sol_alloc_free_no_longer_deployable_with_upgradeable_loader() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
// Populate loader account with `solana_sbf_rust_deprecated_loader` elf, which
|
|
// depends on `sol_alloc_free_` syscall. This can be verified with
|
|
// $ elfdump solana_sbf_rust_deprecated_loader.so
|
|
// : 0000000000001ab8 000000070000000a R_BPF_64_32 0000000000000000 sol_alloc_free_
|
|
// In the symbol table, there is `sol_alloc_free_`.
|
|
// In fact, `sol_alloc_free_` is called from sbf allocator, which is originated from
|
|
// AccountInfo::realloc() in the program code.
|
|
|
|
// Expect that deployment to fail. B/C during deployment, there is an elf
|
|
// verification step, which uses the runtime to look up relocatable symbols
|
|
// in elf inside syscall table. In this case, `sol_alloc_free_` can't be
|
|
// found in syscall table. Hence, the verification fails and the deployment
|
|
// fails.
|
|
let (_bank, _program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_deprecated_loader",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_duplicate_accounts() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[("dup_accounts")]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[("solana_sbf_rust_dup_accounts")]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
println!("Test program: {:?}", program);
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program,
|
|
);
|
|
let payee_account = AccountSharedData::new(10, 1, &program_id);
|
|
let payee_pubkey = Pubkey::new_unique();
|
|
bank.store_account(&payee_pubkey, &payee_account);
|
|
let account = AccountSharedData::new(10, 1, &program_id);
|
|
|
|
let pubkey = Pubkey::new_unique();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
AccountMeta::new(payee_pubkey, false),
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new(pubkey, false),
|
|
];
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(data[0], 1);
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(data[0], 2);
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(data[0], 3);
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let lamports = bank_client.get_balance(&pubkey).unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(lamports, 11);
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let lamports = bank_client.get_balance(&pubkey).unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(lamports, 12);
|
|
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let lamports = bank_client.get_balance(&pubkey).unwrap();
|
|
assert!(result.is_ok());
|
|
assert_eq!(lamports, 13);
|
|
|
|
let keypair = Keypair::new();
|
|
let pubkey = keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
AccountMeta::new(payee_pubkey, false),
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(pubkey, true),
|
|
AccountMeta::new_readonly(program_id, false),
|
|
];
|
|
bank.store_account(&pubkey, &account);
|
|
let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &keypair], message);
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_error_handling() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[("error_handling")]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[("solana_sbf_rust_error_handling")]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
println!("Test program: {:?}", program);
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program,
|
|
);
|
|
|
|
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok());
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[3], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(0))
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[4], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[5], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let result = result.unwrap_err().unwrap();
|
|
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
|
|
{
|
|
assert_eq!(
|
|
result,
|
|
TransactionError::InstructionError(0, InstructionError::InvalidError)
|
|
);
|
|
}
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[6], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let result = result.unwrap_err().unwrap();
|
|
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
|
|
{
|
|
assert_eq!(
|
|
result,
|
|
TransactionError::InstructionError(0, InstructionError::InvalidError)
|
|
);
|
|
}
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[7], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
let result = result.unwrap_err().unwrap();
|
|
if TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) != result
|
|
{
|
|
assert_eq!(
|
|
result,
|
|
TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed)
|
|
);
|
|
}
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[8], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[9], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
|
|
fn test_return_data_and_log_data_syscall() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[("log_data")]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[("solana_sbf_rust_log_data")]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program,
|
|
);
|
|
|
|
bank.freeze();
|
|
|
|
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
|
|
let instruction =
|
|
Instruction::new_with_bytes(program_id, &[1, 2, 3, 0, 4, 5, 6], account_metas);
|
|
|
|
let blockhash = bank.last_blockhash();
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
|
|
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction);
|
|
|
|
let result = bank.simulate_transaction(&sanitized_tx, false);
|
|
|
|
assert!(result.result.is_ok());
|
|
|
|
assert_eq!(result.logs[1], "Program data: AQID BAUG");
|
|
|
|
assert_eq!(
|
|
result.logs[3],
|
|
format!("Program return: {} CAFE", program_id)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_sanity() {
|
|
solana_logger::setup();
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Debug)]
|
|
enum Languages {
|
|
C,
|
|
Rust,
|
|
}
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.push((Languages::C, "invoke", "invoked", "noop"));
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.push((
|
|
Languages::Rust,
|
|
"solana_sbf_rust_invoke",
|
|
"solana_sbf_rust_invoked",
|
|
"solana_sbf_rust_noop",
|
|
));
|
|
}
|
|
for program in programs.iter() {
|
|
println!("Test program: {:?}", program);
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let invoke_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program.1,
|
|
);
|
|
|
|
let invoked_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program.2,
|
|
);
|
|
|
|
let (bank, noop_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program.3,
|
|
);
|
|
|
|
let argument_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(42, 100, &invoke_program_id);
|
|
bank.store_account(&argument_keypair.pubkey(), &account);
|
|
|
|
let invoked_argument_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(10, 10, &invoked_program_id);
|
|
bank.store_account(&invoked_argument_keypair.pubkey(), &account);
|
|
|
|
let from_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(84, 0, &system_program::id());
|
|
bank.store_account(&from_keypair.pubkey(), &account);
|
|
|
|
let (derived_key1, bump_seed1) =
|
|
Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id);
|
|
let (derived_key2, bump_seed2) =
|
|
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id);
|
|
let (derived_key3, bump_seed3) =
|
|
Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id);
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(argument_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(invoked_program_id, false),
|
|
AccountMeta::new(invoked_argument_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(invoked_program_id, false),
|
|
AccountMeta::new(argument_keypair.pubkey(), true),
|
|
AccountMeta::new(derived_key1, false),
|
|
AccountMeta::new(derived_key2, false),
|
|
AccountMeta::new_readonly(derived_key3, false),
|
|
AccountMeta::new_readonly(system_program::id(), false),
|
|
AccountMeta::new(from_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(solana_sdk::ed25519_program::id(), false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
];
|
|
|
|
// success cases
|
|
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&[TEST_SUCCESS, bump_seed1, bump_seed2, bump_seed3],
|
|
account_metas.clone(),
|
|
);
|
|
let noop_instruction = Instruction::new_with_bytes(noop_program_id, &[], vec![]);
|
|
let message = Message::new(&[instruction, noop_instruction], Some(&mint_pubkey));
|
|
let tx = Transaction::new(
|
|
&[
|
|
&mint_keypair,
|
|
&argument_keypair,
|
|
&invoked_argument_keypair,
|
|
&from_keypair,
|
|
],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, inner_instructions, _log_messages) =
|
|
process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(result, Ok(()));
|
|
|
|
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
|
|
.iter()
|
|
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
|
|
.cloned()
|
|
.collect();
|
|
let expected_invoked_programs = match program.0 {
|
|
Languages::C => vec![
|
|
system_program::id(),
|
|
system_program::id(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
],
|
|
Languages::Rust => vec![
|
|
system_program::id(),
|
|
system_program::id(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
system_program::id(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
],
|
|
};
|
|
assert_eq!(invoked_programs.len(), expected_invoked_programs.len());
|
|
assert_eq!(invoked_programs, expected_invoked_programs);
|
|
let no_invoked_programs: Vec<Pubkey> = inner_instructions[1]
|
|
.iter()
|
|
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
|
|
.cloned()
|
|
.collect();
|
|
assert_eq!(no_invoked_programs.len(), 0);
|
|
|
|
// failure cases
|
|
|
|
let do_invoke_failure_test_local =
|
|
|test: u8,
|
|
expected_error: TransactionError,
|
|
expected_invoked_programs: &[Pubkey],
|
|
expected_log_messages: Option<Vec<String>>| {
|
|
println!("Running failure test #{:?}", test);
|
|
let instruction_data = &[test, bump_seed1, bump_seed2, bump_seed3];
|
|
let signers = vec![
|
|
&mint_keypair,
|
|
&argument_keypair,
|
|
&invoked_argument_keypair,
|
|
&from_keypair,
|
|
];
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let tx = Transaction::new(&signers, message.clone(), bank.last_blockhash());
|
|
let (result, inner_instructions, log_messages) =
|
|
process_transaction_and_record_inner(&bank, tx);
|
|
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
|
|
.iter()
|
|
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
|
|
.cloned()
|
|
.collect();
|
|
assert_eq!(result, Err(expected_error));
|
|
assert_eq!(invoked_programs, expected_invoked_programs);
|
|
if let Some(expected_log_messages) = expected_log_messages {
|
|
assert_eq!(log_messages.len(), expected_log_messages.len());
|
|
expected_log_messages
|
|
.into_iter()
|
|
.zip(log_messages)
|
|
.for_each(|(expected_log_message, log_message)| {
|
|
if expected_log_message != String::from("skip") {
|
|
assert_eq!(log_message, expected_log_message);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
let program_lang = match program.0 {
|
|
Languages::Rust => "Rust",
|
|
Languages::C => "C",
|
|
};
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_PRIVILEGE_ESCALATION_SIGNER,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_PRIVILEGE_ESCALATION_WRITABLE,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_PPROGRAM_NOT_EXECUTABLE,
|
|
TransactionError::InstructionError(0, InstructionError::AccountNotExecutable),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_EMPTY_ACCOUNTS_SLICE,
|
|
TransactionError::InstructionError(0, InstructionError::MissingAccount),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_CAP_SEEDS,
|
|
TransactionError::InstructionError(0, InstructionError::MaxSeedLengthExceeded),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_CAP_SIGNERS,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_MAX_INSTRUCTION_DATA_LEN_EXCEEDED,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
Some(vec![
|
|
format!("Program {invoke_program_id} invoke [1]"),
|
|
format!("Program log: invoke {program_lang} program"),
|
|
"Program log: Test max instruction data len exceeded".into(),
|
|
"skip".into(), // don't compare compute consumption logs
|
|
format!("Program {invoke_program_id} failed: Invoked an instruction with data that is too large (10241 > 10240)"),
|
|
]),
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
Some(vec![
|
|
format!("Program {invoke_program_id} invoke [1]"),
|
|
format!("Program log: invoke {program_lang} program"),
|
|
"Program log: Test max instruction accounts exceeded".into(),
|
|
"skip".into(), // don't compare compute consumption logs
|
|
format!("Program {invoke_program_id} failed: Invoked an instruction with too many accounts (256 > 255)"),
|
|
]),
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_MAX_ACCOUNT_INFOS_EXCEEDED,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
Some(vec![
|
|
format!("Program {invoke_program_id} invoke [1]"),
|
|
format!("Program log: invoke {program_lang} program"),
|
|
"Program log: Test max account infos exceeded".into(),
|
|
"skip".into(), // don't compare compute consumption logs
|
|
format!("Program {invoke_program_id} failed: Invoked an instruction with too many account info's (129 > 128)"),
|
|
]),
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_RETURN_ERROR,
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42)),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_PRIVILEGE_DEESCALATION_ESCALATION_SIGNER,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_PRIVILEGE_DEESCALATION_ESCALATION_WRITABLE,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_WRITABLE_DEESCALATION_WRITABLE,
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_NESTED_INVOKE_TOO_DEEP,
|
|
TransactionError::InstructionError(0, InstructionError::CallDepth),
|
|
&[
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
invoked_program_id.clone(),
|
|
],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_CALL_PRECOMPILE,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_RETURN_DATA_TOO_LARGE,
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
&[],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_DUPLICATE_PRIVILEGE_ESCALATION_SIGNER,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
do_invoke_failure_test_local(
|
|
TEST_DUPLICATE_PRIVILEGE_ESCALATION_WRITABLE,
|
|
TransactionError::InstructionError(0, InstructionError::PrivilegeEscalation),
|
|
&[invoked_program_id.clone()],
|
|
None,
|
|
);
|
|
|
|
// Check resulting state
|
|
|
|
assert_eq!(43, bank.get_balance(&derived_key1));
|
|
let account = bank.get_account(&derived_key1).unwrap();
|
|
assert_eq!(&invoke_program_id, account.owner());
|
|
assert_eq!(
|
|
MAX_PERMITTED_DATA_INCREASE,
|
|
bank.get_account(&derived_key1).unwrap().data().len()
|
|
);
|
|
for i in 0..20 {
|
|
assert_eq!(i as u8, account.data()[i]);
|
|
}
|
|
|
|
// Attempt to realloc into unauthorized address space
|
|
let account = AccountSharedData::new(84, 0, &system_program::id());
|
|
bank.store_account(&from_keypair.pubkey(), &account);
|
|
bank.store_account(&derived_key1, &AccountSharedData::default());
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&[
|
|
TEST_ALLOC_ACCESS_VIOLATION,
|
|
bump_seed1,
|
|
bump_seed2,
|
|
bump_seed3,
|
|
],
|
|
account_metas.clone(),
|
|
);
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let tx = Transaction::new(
|
|
&[
|
|
&mint_keypair,
|
|
&argument_keypair,
|
|
&invoked_argument_keypair,
|
|
&from_keypair,
|
|
],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, inner_instructions, _log_messages) =
|
|
process_transaction_and_record_inner(&bank, tx);
|
|
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
|
|
.iter()
|
|
.map(|ix| &message.account_keys[ix.instruction.program_id_index as usize])
|
|
.cloned()
|
|
.collect();
|
|
assert_eq!(invoked_programs, vec![]);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_program_id_spoofing() {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let malicious_swap_pubkey = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_spoof1",
|
|
);
|
|
|
|
let (bank, malicious_system_pubkey) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_spoof1_system",
|
|
);
|
|
|
|
let from_pubkey = Pubkey::new_unique();
|
|
let account = AccountSharedData::new(10, 0, &system_program::id());
|
|
bank.store_account(&from_pubkey, &account);
|
|
|
|
let to_pubkey = Pubkey::new_unique();
|
|
let account = AccountSharedData::new(0, 0, &system_program::id());
|
|
bank.store_account(&to_pubkey, &account);
|
|
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(system_program::id(), false),
|
|
AccountMeta::new_readonly(malicious_system_pubkey, false),
|
|
AccountMeta::new(from_pubkey, false),
|
|
AccountMeta::new(to_pubkey, false),
|
|
];
|
|
|
|
let instruction =
|
|
Instruction::new_with_bytes(malicious_swap_pubkey, &[], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature)
|
|
);
|
|
assert_eq!(10, bank.get_balance(&from_pubkey));
|
|
assert_eq!(0, bank.get_balance(&to_pubkey));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_caller_has_access_to_cpi_program() {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let caller_pubkey = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_caller_access",
|
|
);
|
|
|
|
let (_bank, caller2_pubkey) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_caller_access",
|
|
);
|
|
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(caller_pubkey, false),
|
|
AccountMeta::new_readonly(caller2_pubkey, false),
|
|
];
|
|
let instruction = Instruction::new_with_bytes(caller_pubkey, &[1], account_metas.clone());
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::MissingAccount)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_ro_modify() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_pubkey) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_ro_modify",
|
|
);
|
|
|
|
let test_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(10, 0, &system_program::id());
|
|
bank.store_account(&test_keypair.pubkey(), &account);
|
|
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(system_program::id(), false),
|
|
AccountMeta::new(test_keypair.pubkey(), true),
|
|
];
|
|
|
|
let instruction = Instruction::new_with_bytes(program_pubkey, &[1], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_pubkey, &[3], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_pubkey, &[4], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &test_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_call_depth() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_call_depth",
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bincode(
|
|
program_id,
|
|
&(ComputeBudget::default().max_call_depth - 1),
|
|
vec![],
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok());
|
|
|
|
let instruction =
|
|
Instruction::new_with_bincode(program_id, &ComputeBudget::default().max_call_depth, vec![]);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_compute_budget() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
let message = Message::new(
|
|
&[
|
|
ComputeBudgetInstruction::set_compute_unit_limit(150),
|
|
Instruction::new_with_bincode(program_id, &0, vec![]),
|
|
],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(1, InstructionError::ProgramFailedToComplete),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assert_instruction_count() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[
|
|
("alloc", 11502),
|
|
("sbf_to_sbf", 313),
|
|
("multiple_static", 208),
|
|
("noop", 5),
|
|
("noop++", 5),
|
|
("relative_call", 210),
|
|
("return_data", 980),
|
|
("sanity", 2377),
|
|
("sanity++", 2277),
|
|
("secp256k1_recover", 25383),
|
|
("sha", 1355),
|
|
("struct_pass", 108),
|
|
("struct_ret", 122),
|
|
]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[
|
|
("solana_sbf_rust_128bit", 1218),
|
|
("solana_sbf_rust_alloc", 5077),
|
|
("solana_sbf_rust_custom_heap", 398),
|
|
("solana_sbf_rust_dep_crate", 2),
|
|
("solana_sbf_rust_iter", 1514),
|
|
("solana_sbf_rust_many_args", 1289),
|
|
("solana_sbf_rust_mem", 2067),
|
|
("solana_sbf_rust_membuiltins", 1539),
|
|
("solana_sbf_rust_noop", 275),
|
|
("solana_sbf_rust_param_passing", 146),
|
|
("solana_sbf_rust_rand", 378),
|
|
("solana_sbf_rust_sanity", 51953),
|
|
("solana_sbf_rust_secp256k1_recover", 91185),
|
|
("solana_sbf_rust_sha", 24059),
|
|
]);
|
|
}
|
|
|
|
println!("\n {:36} expected actual diff", "SBF program");
|
|
for (program_name, expected_consumption) in programs.iter() {
|
|
let loader_id = bpf_loader::id();
|
|
let program_key = Pubkey::new_unique();
|
|
let mut transaction_accounts = vec![
|
|
(program_key, AccountSharedData::new(0, 0, &loader_id)),
|
|
(
|
|
Pubkey::new_unique(),
|
|
AccountSharedData::new(0, 0, &program_key),
|
|
),
|
|
];
|
|
let instruction_accounts = vec![AccountMeta {
|
|
pubkey: transaction_accounts[1].0,
|
|
is_signer: false,
|
|
is_writable: false,
|
|
}];
|
|
transaction_accounts[0]
|
|
.1
|
|
.set_data_from_slice(&load_program_from_file(program_name));
|
|
transaction_accounts[0].1.set_executable(true);
|
|
|
|
let prev_compute_meter = RefCell::new(0);
|
|
print!(" {:36} {:8}", program_name, *expected_consumption);
|
|
mock_process_instruction(
|
|
&loader_id,
|
|
vec![0],
|
|
&[],
|
|
transaction_accounts,
|
|
instruction_accounts,
|
|
Ok(()),
|
|
solana_bpf_loader_program::Entrypoint::vm,
|
|
|invoke_context| {
|
|
*prev_compute_meter.borrow_mut() = invoke_context.get_remaining();
|
|
solana_bpf_loader_program::test_utils::load_all_invoked_programs(invoke_context);
|
|
},
|
|
|invoke_context| {
|
|
let consumption = prev_compute_meter
|
|
.borrow()
|
|
.saturating_sub(invoke_context.get_remaining());
|
|
let diff: i64 = consumption as i64 - *expected_consumption as i64;
|
|
println!(
|
|
"{:6} {:+5} ({:+3.0}%)",
|
|
consumption,
|
|
diff,
|
|
100.0_f64 * consumption as f64 / *expected_consumption as f64 - 100.0_f64,
|
|
);
|
|
assert!(consumption <= *expected_consumption);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_instruction_introspection() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50_000);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_instruction_introspection",
|
|
);
|
|
|
|
// Passing transaction
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(sysvar::instructions::id(), false),
|
|
];
|
|
let instruction0 = Instruction::new_with_bytes(program_id, &[0u8, 0u8], account_metas.clone());
|
|
let instruction1 = Instruction::new_with_bytes(program_id, &[0u8, 1u8], account_metas.clone());
|
|
let instruction2 = Instruction::new_with_bytes(program_id, &[0u8, 2u8], account_metas);
|
|
let message = Message::new(
|
|
&[instruction0, instruction1, instruction2],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
|
|
assert!(result.is_ok());
|
|
|
|
// writable special instructions11111 key, should not be allowed
|
|
let account_metas = vec![AccountMeta::new(sysvar::instructions::id(), false)];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
// sysvar write locks are demoted to read only. So this will no longer
|
|
// cause InvalidAccountIndex error.
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete),
|
|
);
|
|
|
|
// No accounts, should error
|
|
let instruction = Instruction::new_with_bytes(program_id, &[0], vec![]);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_err());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::NotEnoughAccountKeys)
|
|
);
|
|
assert!(bank.get_account(&sysvar::instructions::id()).is_none());
|
|
}
|
|
|
|
/// This test is to test bpf_loader v2 `Finalize` instruction with different
|
|
/// programs. It is going to be deprecated once we activate
|
|
/// `disable_bpf_loader_instructions`.
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_test_use_latest_executor() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
// deactivate `disable_bpf_loader_instructions` feature so that the program
|
|
// can be loaded, finalized and tested.
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::disable_bpf_loader_instructions::id());
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id());
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let panic_id = load_program(
|
|
&bank_client,
|
|
&bpf_loader::id(),
|
|
&mint_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
|
|
// Write the panic program into the program account
|
|
let (program_keypair, instruction) = load_and_finalize_program(
|
|
&bank_client,
|
|
&bpf_loader::id(),
|
|
None,
|
|
&mint_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
|
|
// Finalize the panic program, but fail the tx
|
|
let message = Message::new(
|
|
&[
|
|
instruction,
|
|
Instruction::new_with_bytes(panic_id, &[0], vec![]),
|
|
],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
assert!(bank_client
|
|
.send_and_confirm_message(&[&mint_keypair, &program_keypair], message)
|
|
.is_err());
|
|
|
|
// Write the noop program into the same program account
|
|
let (program_keypair, instruction) = load_and_finalize_program(
|
|
&bank_client,
|
|
&bpf_loader::id(),
|
|
Some(program_keypair),
|
|
&mint_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair, &program_keypair], message)
|
|
.unwrap();
|
|
|
|
// Call the noop program, should get noop not panic
|
|
let message = Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_keypair.pubkey(),
|
|
&[0],
|
|
vec![],
|
|
)],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
assert!(bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message)
|
|
.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_upgrade() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
|
|
// Deploy upgrade program
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
let mut instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
|
|
// Call upgrade program
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
|
|
// Upgrade program
|
|
let buffer_keypair = Keypair::new();
|
|
upgrade_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_id,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgraded",
|
|
);
|
|
bank_client.set_sysvar_for_tests(&clock::Clock {
|
|
slot: 2,
|
|
..clock::Clock::default()
|
|
});
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
// Call upgraded program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(43))
|
|
);
|
|
|
|
// Set a new authority
|
|
let new_authority_keypair = Keypair::new();
|
|
set_upgrade_authority(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&program_id,
|
|
&authority_keypair,
|
|
Some(&new_authority_keypair.pubkey()),
|
|
);
|
|
|
|
// Upgrade back to the original program
|
|
let buffer_keypair = Keypair::new();
|
|
upgrade_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_id,
|
|
&new_authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
// Call original program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
}
|
|
|
|
fn get_stable_genesis_config() -> GenesisConfigInfo {
|
|
let validator_pubkey =
|
|
Pubkey::from_str("GLh546CXmtZdvpEzL8sxzqhhUf7KPvmGaRpFHB5W1sjV").unwrap();
|
|
let mint_keypair = Keypair::from_base58_string(
|
|
"4YTH9JSRgZocmK9ezMZeJCCV2LVeR2NatTBA8AFXkg2x83fqrt8Vwyk91961E7ns4vee9yUBzuDfztb8i9iwTLFd",
|
|
);
|
|
let voting_keypair = Keypair::from_base58_string(
|
|
"4EPWEn72zdNY1JSKkzyZ2vTZcKdPW3jM5WjAgUadnoz83FR5cDFApbo7s5mwBcYXn8afVe2syReJaqBi4fkhG3mH",
|
|
);
|
|
let stake_pubkey = Pubkey::from_str("HGq9JF77xFXRgWRJy8VQuhdbdugrT856RvQDzr1KJo6E").unwrap();
|
|
|
|
let mut genesis_config = create_genesis_config_with_leader_ex(
|
|
123,
|
|
&mint_keypair.pubkey(),
|
|
&validator_pubkey,
|
|
&voting_keypair.pubkey(),
|
|
&stake_pubkey,
|
|
bootstrap_validator_stake_lamports(),
|
|
42,
|
|
FeeRateGovernor::new(0, 0), // most tests can't handle transaction fees
|
|
Rent::free(), // most tests don't expect rent
|
|
ClusterType::Development,
|
|
vec![],
|
|
);
|
|
genesis_config.creation_time = Duration::ZERO.as_secs() as UnixTimestamp;
|
|
|
|
GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
voting_keypair,
|
|
validator_pubkey,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_stable_genesis_and_bank() {
|
|
// The purpose of this test is to exercise various code branches of runtime/VM and
|
|
// assert that the resulting bank hash matches with the expected value.
|
|
// The assert check is commented out by default. Please refer to the last few lines
|
|
// of the test to enable the assertion.
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = get_stable_genesis_config();
|
|
let bank = Bank::new_for_tests(&genesis_config);
|
|
let bank = Arc::new(bank);
|
|
let bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
// Deploy upgradeable program
|
|
let buffer_keypair = Keypair::from_base58_string(
|
|
"4q4UvWxh2oMifTGbChDeWCbdN8eJEUQ1E6cuNnmymJ6AN5CMUT2VW5A1RKnG9dy7ypLczB9inMUAafh5TkpXrtxg",
|
|
);
|
|
let program_keypair = Keypair::from_base58_string(
|
|
"3LQpBxgpaFNJPit5a8t51pJKMkUmNUn5PhSTcuuhuuBxe43cTeqVPhMtKkFNr5VpFzCExf4ihibvuZgGxmjy6t8n",
|
|
);
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::from_base58_string(
|
|
"285XFW2NTWd6CMvtHzvYYS1kWzmzcGBnyEXbH1v8hq6YJqJsLMTYMPkbEQqeE7m7UqhoMeK5V3HMJLf9DdxwU2Gy",
|
|
);
|
|
|
|
let instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
|
|
// Call program before its deployed
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::ProgramAccountNotFound
|
|
);
|
|
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
// Deploy indirect invocation program
|
|
let indirect_program_keypair = Keypair::from_base58_string(
|
|
"2BgE4gD5wUCwiAVPYbmWd2xzXSsD9W2fWgNjwmVkm8WL7i51vK9XAXNnX1VB6oKQZmjaUPRd5RzE6RggB9DeKbZC",
|
|
);
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&indirect_program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
let invoke_instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
let indirect_invoke_instruction = Instruction::new_with_bytes(
|
|
indirect_program_keypair.pubkey(),
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// Prepare redeployment
|
|
let buffer_keypair = Keypair::from_base58_string(
|
|
"5T5L31FiUphXh4N6mxiWhEKPrdLhvMJSbaHo1Ne7zZYkw6YT1fVkqsWdA6pHMtqATiMTc4sfx5yTV9M9AnWDoBkW",
|
|
);
|
|
load_upgradeable_buffer(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
let redeployment_instruction = bpf_loader_upgradeable::upgrade(
|
|
&program_id,
|
|
&buffer_keypair.pubkey(),
|
|
&authority_keypair.pubkey(),
|
|
&mint_keypair.pubkey(),
|
|
);
|
|
|
|
// Redeployment causes programs to be unavailable to both top-level-instructions and CPI instructions
|
|
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
|
|
// Call upgradeable program
|
|
let result =
|
|
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
|
|
assert!(result.is_ok());
|
|
|
|
// Upgrade the program and invoke in same tx
|
|
let message = Message::new(
|
|
&[redeployment_instruction.clone(), invoke_instruction],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let tx = Transaction::new(
|
|
&[&mint_keypair, &authority_keypair],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
|
|
);
|
|
}
|
|
|
|
// Prepare undeployment
|
|
let (programdata_address, _) = Pubkey::find_program_address(
|
|
&[program_keypair.pubkey().as_ref()],
|
|
&bpf_loader_upgradeable::id(),
|
|
);
|
|
let undeployment_instruction = bpf_loader_upgradeable::close_any(
|
|
&programdata_address,
|
|
&mint_keypair.pubkey(),
|
|
Some(&authority_keypair.pubkey()),
|
|
Some(&program_id),
|
|
);
|
|
|
|
let invoke_instruction =
|
|
Instruction::new_with_bytes(program_id, &[1], vec![AccountMeta::new(clock::id(), false)]);
|
|
let indirect_invoke_instruction = Instruction::new_with_bytes(
|
|
indirect_program_keypair.pubkey(),
|
|
&[1],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// Undeployment is visible to both top-level-instructions and CPI instructions
|
|
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
|
|
// Call upgradeable program
|
|
let result =
|
|
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
|
|
assert!(result.is_ok());
|
|
|
|
// Undeploy the program and invoke in same tx
|
|
let message = Message::new(
|
|
&[undeployment_instruction.clone(), invoke_instruction],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let tx = Transaction::new(
|
|
&[&mint_keypair, &authority_keypair],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
|
|
);
|
|
}
|
|
|
|
bank.freeze();
|
|
let expected_hash = Hash::from_str("2A2vqbUKExRbnaAzSnDFXdsBZRZSpCjGZCAA3mFZG2sV")
|
|
.expect("Failed to generate hash");
|
|
println!("Stable test produced bank hash: {}", bank.hash());
|
|
println!("Expected hash: {}", expected_hash);
|
|
|
|
// Enable the following code to match the bank hash with the expected bank hash.
|
|
// Follow these steps.
|
|
// 1. Run this test on the baseline/master commit, and get the expected bank hash.
|
|
// 2. Update the `expected_hash` to match the expected bank hash.
|
|
// 3. Run the test in the PR branch that's being tested.
|
|
// If the hash doesn't match, the PR likely has runtime changes that can lead to
|
|
// consensus failure.
|
|
// assert_eq!(bank.hash(), expected_hash);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_in_same_tx_as_deployment() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
// Deploy upgradeable program
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::new();
|
|
|
|
// Deploy indirect invocation program
|
|
let indirect_program_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&indirect_program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
let invoke_instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
let indirect_invoke_instruction = Instruction::new_with_bytes(
|
|
indirect_program_keypair.pubkey(),
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// Prepare deployment
|
|
let program = load_upgradeable_buffer(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
let deployment_instructions = bpf_loader_upgradeable::deploy_with_max_program_len(
|
|
&mint_keypair.pubkey(),
|
|
&program_keypair.pubkey(),
|
|
&buffer_keypair.pubkey(),
|
|
&authority_keypair.pubkey(),
|
|
1.max(
|
|
bank_client
|
|
.get_minimum_balance_for_rent_exemption(
|
|
bpf_loader_upgradeable::UpgradeableLoaderState::size_of_program(),
|
|
)
|
|
.unwrap(),
|
|
),
|
|
program.len() * 2,
|
|
)
|
|
.unwrap();
|
|
|
|
let bank = bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance slot");
|
|
|
|
// Deployment is invisible to both top-level-instructions and CPI instructions
|
|
for (index, invoke_instruction) in [invoke_instruction, indirect_invoke_instruction]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let mut instructions = deployment_instructions.clone();
|
|
instructions.push(invoke_instruction);
|
|
let tx = Transaction::new(
|
|
&[&mint_keypair, &program_keypair, &authority_keypair],
|
|
Message::new(&instructions, Some(&mint_keypair.pubkey())),
|
|
bank.last_blockhash(),
|
|
);
|
|
if index == 0 {
|
|
let results = execute_transactions(&bank, vec![tx]);
|
|
assert_eq!(
|
|
results[0].as_ref().unwrap_err(),
|
|
&TransactionError::ProgramAccountNotFound,
|
|
);
|
|
} else {
|
|
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(2, InstructionError::InvalidAccountData),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_in_same_tx_as_redeployment() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
// Deploy upgradeable program
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
// Deploy indirect invocation program
|
|
let indirect_program_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&indirect_program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
// Deploy panic program
|
|
let panic_program_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&panic_program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
|
|
let invoke_instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
let indirect_invoke_instruction = Instruction::new_with_bytes(
|
|
indirect_program_keypair.pubkey(),
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective
|
|
// after 2 slots. They need to be called individually to create the correct fork graph in between.
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.unwrap();
|
|
let bank = bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.unwrap();
|
|
|
|
// Prepare redeployment
|
|
let buffer_keypair = Keypair::new();
|
|
load_upgradeable_buffer(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
let redeployment_instruction = bpf_loader_upgradeable::upgrade(
|
|
&program_id,
|
|
&buffer_keypair.pubkey(),
|
|
&authority_keypair.pubkey(),
|
|
&mint_keypair.pubkey(),
|
|
);
|
|
|
|
// Redeployment causes programs to be unavailable to both top-level-instructions and CPI instructions
|
|
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
|
|
// Call upgradeable program
|
|
let result =
|
|
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
|
|
assert!(result.is_ok());
|
|
|
|
// Upgrade the program and invoke in same tx
|
|
let message = Message::new(
|
|
&[redeployment_instruction.clone(), invoke_instruction],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let tx = Transaction::new(
|
|
&[&mint_keypair, &authority_keypair],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_in_same_tx_as_undeployment() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
// Deploy upgradeable program
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
// Deploy indirect invocation program
|
|
let indirect_program_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&indirect_program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
let invoke_instruction =
|
|
Instruction::new_with_bytes(program_id, &[0], vec![AccountMeta::new(clock::id(), false)]);
|
|
let indirect_invoke_instruction = Instruction::new_with_bytes(
|
|
indirect_program_keypair.pubkey(),
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// load_upgradeable_program sets clock sysvar to 1, which causes the program to be effective
|
|
// after 2 slots. They need to be called individually to create the correct fork graph in between.
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.unwrap();
|
|
let bank = bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.unwrap();
|
|
|
|
// Prepare undeployment
|
|
let (programdata_address, _) = Pubkey::find_program_address(
|
|
&[program_keypair.pubkey().as_ref()],
|
|
&bpf_loader_upgradeable::id(),
|
|
);
|
|
let undeployment_instruction = bpf_loader_upgradeable::close_any(
|
|
&programdata_address,
|
|
&mint_keypair.pubkey(),
|
|
Some(&authority_keypair.pubkey()),
|
|
Some(&program_id),
|
|
);
|
|
|
|
// Undeployment is visible to both top-level-instructions and CPI instructions
|
|
for invoke_instruction in [invoke_instruction, indirect_invoke_instruction] {
|
|
// Call upgradeable program
|
|
let result =
|
|
bank_client.send_and_confirm_instruction(&mint_keypair, invoke_instruction.clone());
|
|
assert!(result.is_ok());
|
|
|
|
// Upgrade the program and invoke in same tx
|
|
let message = Message::new(
|
|
&[undeployment_instruction.clone(), invoke_instruction],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let tx = Transaction::new(
|
|
&[&mint_keypair, &authority_keypair],
|
|
message.clone(),
|
|
bank.last_blockhash(),
|
|
);
|
|
let (result, _, _) = process_transaction_and_record_inner(&bank, tx);
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(1, InstructionError::InvalidAccountData),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_invoke_upgradeable_via_cpi() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, invoke_and_return) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
// Deploy upgradeable program
|
|
let program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
bank_client.set_sysvar_for_tests(&clock::Clock {
|
|
slot: 2,
|
|
..clock::Clock::default()
|
|
});
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance slot");
|
|
|
|
let mut instruction = Instruction::new_with_bytes(
|
|
invoke_and_return,
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// Call invoker program to invoke the upgradeable program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
|
|
// Upgrade program
|
|
let buffer_keypair = Keypair::new();
|
|
upgrade_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_id,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgraded",
|
|
);
|
|
bank_client.set_sysvar_for_tests(&clock::Clock {
|
|
slot: 3,
|
|
..clock::Clock::default()
|
|
});
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance slot");
|
|
|
|
// Call the upgraded program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(43))
|
|
);
|
|
|
|
// Set a new authority
|
|
let new_authority_keypair = Keypair::new();
|
|
set_upgrade_authority(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&program_id,
|
|
&authority_keypair,
|
|
Some(&new_authority_keypair.pubkey()),
|
|
);
|
|
|
|
// Upgrade back to the original program
|
|
let buffer_keypair = Keypair::new();
|
|
upgrade_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_id,
|
|
&new_authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance slot");
|
|
|
|
// Call original program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(feature = "sbf_c", feature = "sbf_rust"))]
|
|
fn test_program_sbf_disguised_as_sbf_loader() {
|
|
solana_logger::setup();
|
|
|
|
let mut programs = Vec::new();
|
|
#[cfg(feature = "sbf_c")]
|
|
{
|
|
programs.extend_from_slice(&[("noop")]);
|
|
}
|
|
#[cfg(feature = "sbf_rust")]
|
|
{
|
|
programs.extend_from_slice(&[("solana_sbf_rust_noop")]);
|
|
}
|
|
|
|
for program in programs.iter() {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
bank.deactivate_feature(
|
|
&solana_sdk::feature_set::remove_bpf_loader_incorrect_program_id::id(),
|
|
);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
program,
|
|
);
|
|
|
|
let account_metas = vec![AccountMeta::new_readonly(program_id, false)];
|
|
let instruction = Instruction::new_with_bytes(bpf_loader::id(), &[1], account_metas);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::IncorrectProgramId)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_c")]
|
|
fn test_program_reads_from_program_account() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"read_program",
|
|
);
|
|
let account_metas = vec![AccountMeta::new_readonly(program_id, false)];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[], account_metas);
|
|
bank_client
|
|
.send_and_confirm_instruction(&mint_keypair, instruction)
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_c")]
|
|
fn test_program_sbf_c_dup() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
|
|
let account_address = Pubkey::new_unique();
|
|
let account = AccountSharedData::new_data(42, &[1_u8, 2, 3], &system_program::id()).unwrap();
|
|
bank.store_account(&account_address, &account);
|
|
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
let (_, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"ser",
|
|
);
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(account_address, false),
|
|
AccountMeta::new_readonly(account_address, false),
|
|
];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[4, 5, 6, 7], account_metas);
|
|
bank_client
|
|
.send_and_confirm_instruction(&mint_keypair, instruction)
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_upgrade_via_cpi() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, invoke_and_return) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
// Deploy upgradeable program
|
|
let buffer_keypair = Keypair::new();
|
|
let program_keypair = Keypair::new();
|
|
let program_id = program_keypair.pubkey();
|
|
let authority_keypair = Keypair::new();
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&program_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
let program_account = bank_client.get_account(&program_id).unwrap().unwrap();
|
|
let Ok(bpf_loader_upgradeable::UpgradeableLoaderState::Program {
|
|
programdata_address,
|
|
}) = program_account.state()
|
|
else {
|
|
unreachable!()
|
|
};
|
|
let original_programdata = bank_client
|
|
.get_account_data(&programdata_address)
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
let mut instruction = Instruction::new_with_bytes(
|
|
invoke_and_return,
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(program_id, false),
|
|
AccountMeta::new_readonly(clock::id(), false),
|
|
],
|
|
);
|
|
|
|
// Call the upgradable program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(42))
|
|
);
|
|
|
|
// Load the buffer account
|
|
let buffer_keypair = Keypair::new();
|
|
load_upgradeable_buffer(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&buffer_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgraded",
|
|
);
|
|
|
|
// Upgrade program via CPI
|
|
let mut upgrade_instruction = bpf_loader_upgradeable::upgrade(
|
|
&program_id,
|
|
&buffer_keypair.pubkey(),
|
|
&authority_keypair.pubkey(),
|
|
&mint_keypair.pubkey(),
|
|
);
|
|
upgrade_instruction.program_id = invoke_and_return;
|
|
upgrade_instruction
|
|
.accounts
|
|
.insert(0, AccountMeta::new(bpf_loader_upgradeable::id(), false));
|
|
let message = Message::new(&[upgrade_instruction], Some(&mint_keypair.pubkey()));
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair, &authority_keypair], message)
|
|
.unwrap();
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
// Call the upgraded program
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::Custom(43))
|
|
);
|
|
|
|
// Validate that the programdata was actually overwritten
|
|
let programdata = bank_client
|
|
.get_account_data(&programdata_address)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_ne!(programdata, original_programdata);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_set_upgrade_authority_via_cpi() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
// Deploy CPI invoker program
|
|
let invoke_and_return = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
// Deploy upgradeable program
|
|
let program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_upgradeable",
|
|
);
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
// Set program upgrade authority instruction to invoke via CPI
|
|
let new_upgrade_authority_key = Keypair::new().pubkey();
|
|
let mut set_upgrade_authority_instruction = bpf_loader_upgradeable::set_upgrade_authority(
|
|
&program_id,
|
|
&authority_keypair.pubkey(),
|
|
Some(&new_upgrade_authority_key),
|
|
);
|
|
|
|
// Invoke set_upgrade_authority via CPI invoker program
|
|
set_upgrade_authority_instruction.program_id = invoke_and_return;
|
|
set_upgrade_authority_instruction
|
|
.accounts
|
|
.insert(0, AccountMeta::new(bpf_loader_upgradeable::id(), false));
|
|
|
|
let message = Message::new(
|
|
&[set_upgrade_authority_instruction],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair, &authority_keypair], message)
|
|
.unwrap();
|
|
|
|
// Assert upgrade authority was changed
|
|
let program_account_data = bank_client.get_account_data(&program_id).unwrap().unwrap();
|
|
let program_account = parse_bpf_upgradeable_loader(&program_account_data).unwrap();
|
|
|
|
let upgrade_authority_key = match program_account {
|
|
BpfUpgradeableLoaderAccountType::Program(ui_program) => {
|
|
let program_data_account_key = Pubkey::from_str(&ui_program.program_data).unwrap();
|
|
let program_data_account_data = bank_client
|
|
.get_account_data(&program_data_account_key)
|
|
.unwrap()
|
|
.unwrap();
|
|
let program_data_account =
|
|
parse_bpf_upgradeable_loader(&program_data_account_data).unwrap();
|
|
|
|
match program_data_account {
|
|
BpfUpgradeableLoaderAccountType::ProgramData(ui_program_data) => ui_program_data
|
|
.authority
|
|
.map(|a| Pubkey::from_str(&a).unwrap()),
|
|
_ => None,
|
|
}
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
assert_eq!(Some(new_upgrade_authority_key), upgrade_authority_key);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_upgradeable_locks() {
|
|
fn setup_program_upgradeable_locks(
|
|
payer_keypair: &Keypair,
|
|
buffer_keypair: &Keypair,
|
|
program_keypair: &Keypair,
|
|
) -> (Arc<Bank>, Transaction, Transaction) {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(2_000_000_000);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
load_upgradeable_program(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
buffer_keypair,
|
|
program_keypair,
|
|
payer_keypair,
|
|
"solana_sbf_rust_panic",
|
|
);
|
|
|
|
// Load the buffer account
|
|
load_upgradeable_buffer(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
buffer_keypair,
|
|
&payer_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
let bank = bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
bank_client
|
|
.send_and_confirm_instruction(
|
|
&mint_keypair,
|
|
system_instruction::transfer(
|
|
&mint_keypair.pubkey(),
|
|
&payer_keypair.pubkey(),
|
|
1_000_000_000,
|
|
),
|
|
)
|
|
.unwrap();
|
|
|
|
let invoke_tx = Transaction::new(
|
|
&[payer_keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_keypair.pubkey(),
|
|
&[0; 0],
|
|
vec![],
|
|
)],
|
|
Some(&payer_keypair.pubkey()),
|
|
),
|
|
bank.last_blockhash(),
|
|
);
|
|
let upgrade_tx = Transaction::new(
|
|
&[payer_keypair],
|
|
Message::new(
|
|
&[bpf_loader_upgradeable::upgrade(
|
|
&program_keypair.pubkey(),
|
|
&buffer_keypair.pubkey(),
|
|
&payer_keypair.pubkey(),
|
|
&payer_keypair.pubkey(),
|
|
)],
|
|
Some(&payer_keypair.pubkey()),
|
|
),
|
|
bank.last_blockhash(),
|
|
);
|
|
|
|
(bank, invoke_tx, upgrade_tx)
|
|
}
|
|
|
|
let payer_keypair = keypair_from_seed(&[56u8; 32]).unwrap();
|
|
let buffer_keypair = keypair_from_seed(&[11; 32]).unwrap();
|
|
let program_keypair = keypair_from_seed(&[77u8; 32]).unwrap();
|
|
|
|
let results1 = {
|
|
let (bank, invoke_tx, upgrade_tx) =
|
|
setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair);
|
|
execute_transactions(&bank, vec![upgrade_tx, invoke_tx])
|
|
};
|
|
|
|
let results2 = {
|
|
let (bank, invoke_tx, upgrade_tx) =
|
|
setup_program_upgradeable_locks(&payer_keypair, &buffer_keypair, &program_keypair);
|
|
execute_transactions(&bank, vec![invoke_tx, upgrade_tx])
|
|
};
|
|
|
|
assert!(matches!(
|
|
results1[0],
|
|
Ok(ConfirmedTransactionWithStatusMeta {
|
|
tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
|
|
meta: TransactionStatusMeta { status: Ok(()), .. },
|
|
..
|
|
}),
|
|
..
|
|
})
|
|
));
|
|
assert_eq!(results1[1], Err(TransactionError::AccountInUse));
|
|
|
|
assert!(matches!(
|
|
results2[0],
|
|
Ok(ConfirmedTransactionWithStatusMeta {
|
|
tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
|
|
meta: TransactionStatusMeta {
|
|
status: Err(TransactionError::InstructionError(
|
|
0,
|
|
InstructionError::ProgramFailedToComplete
|
|
)),
|
|
..
|
|
},
|
|
..
|
|
}),
|
|
..
|
|
})
|
|
));
|
|
assert_eq!(results2[1], Err(TransactionError::AccountInUse));
|
|
}
|
|
|
|
/// This test is to test bpf_loader v2 `Finalize` instruction. It is going to be
|
|
/// deprecated once we activate `disable_bpf_loader_instructions`.
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_finalize() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
// deactivate `disable_bpf_loader_instructions` feature so that the program
|
|
// can be loaded, finalized and tested.
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::disable_bpf_loader_instructions::id());
|
|
|
|
genesis_config
|
|
.accounts
|
|
.remove(&feature_set::deprecate_executable_meta_update_in_bpf_loader::id());
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
|
|
let (_, program_pubkey) = load_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&bpf_loader::id(),
|
|
&mint_keypair,
|
|
"solana_sbf_rust_finalize",
|
|
);
|
|
|
|
// Write the noop program into the same program account
|
|
let (program_keypair, _instruction) = load_and_finalize_program(
|
|
&bank_client,
|
|
&bpf_loader::id(),
|
|
None,
|
|
&mint_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
|
|
let account_metas = vec![
|
|
AccountMeta::new(program_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(bpf_loader::id(), false),
|
|
AccountMeta::new(rent::id(), false),
|
|
];
|
|
let instruction = Instruction::new_with_bytes(program_pubkey, &[], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair, &program_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_ro_account_modify() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_ro_account_modify",
|
|
);
|
|
|
|
let argument_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(42, 100, &program_id);
|
|
bank.store_account(&argument_keypair.pubkey(), &account);
|
|
|
|
let from_keypair = Keypair::new();
|
|
let account = AccountSharedData::new(84, 0, &system_program::id());
|
|
bank.store_account(&from_keypair.pubkey(), &account);
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new_readonly(argument_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(program_id, false),
|
|
];
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[0], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[1], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
|
);
|
|
|
|
let instruction = Instruction::new_with_bytes(program_id, &[2], account_metas.clone());
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let result = bank_client.send_and_confirm_message(&[&mint_keypair], message);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_realloc() {
|
|
solana_logger::setup();
|
|
|
|
const START_BALANCE: u64 = 100_000_000_000;
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(1_000_000_000_000);
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let signer = &[&mint_keypair];
|
|
for direct_mapping in [false, true] {
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let feature_set = Arc::make_mut(&mut bank.feature_set);
|
|
// by default test banks have all features enabled, so we only need to
|
|
// disable when needed
|
|
if !direct_mapping {
|
|
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
|
|
}
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_realloc",
|
|
);
|
|
|
|
let mut bump = 0;
|
|
let keypair = Keypair::new();
|
|
let pubkey = keypair.pubkey();
|
|
let account = AccountSharedData::new(START_BALANCE, 5, &program_id);
|
|
bank.store_account(&pubkey, &account);
|
|
|
|
// Realloc RO account
|
|
let mut instruction = realloc(&program_id, &pubkey, 0, &mut bump);
|
|
instruction.accounts[0].is_writable = false;
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(signer, Message::new(&[instruction], Some(&mint_pubkey),),)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
|
);
|
|
|
|
// Realloc account to overflow
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, usize::MAX, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc account to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc account to max then undo
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc_extend_and_undo(
|
|
&program_id,
|
|
&pubkey,
|
|
MAX_PERMITTED_DATA_INCREASE,
|
|
&mut bump,
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc account to max + 1 then undo
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc_extend_and_undo(
|
|
&program_id,
|
|
&pubkey,
|
|
MAX_PERMITTED_DATA_INCREASE + 1,
|
|
&mut bump,
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc to max + 1
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(
|
|
&program_id,
|
|
&pubkey,
|
|
MAX_PERMITTED_DATA_INCREASE + 1,
|
|
&mut bump
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc to max length in max increase increments
|
|
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
|
|
let mut bump = i as u64;
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc_extend_and_fill(
|
|
&program_id,
|
|
&pubkey,
|
|
MAX_PERMITTED_DATA_INCREASE,
|
|
1,
|
|
&mut bump,
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
}
|
|
for i in 0..data.len() {
|
|
assert_eq!(data[i], 1);
|
|
}
|
|
|
|
// and one more time should fail
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc_extend(
|
|
&program_id,
|
|
&pubkey,
|
|
MAX_PERMITTED_DATA_INCREASE,
|
|
&mut bump
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc to 6 bytes
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, 6, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(6, data.len());
|
|
|
|
// Extend by 2 bytes and write a u64. This ensures that we can do writes that span the original
|
|
// account length (6 bytes) and the realloc data (2 bytes).
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[extend_and_write_u64(
|
|
&program_id,
|
|
&pubkey,
|
|
0x1122334455667788,
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(8, data.len());
|
|
assert_eq!(0x1122334455667788, unsafe { *data.as_ptr().cast::<u64>() });
|
|
|
|
// Realloc to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc and assign
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_id,
|
|
&[REALLOC_AND_ASSIGN],
|
|
vec![AccountMeta::new(pubkey, false)],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(&solana_sdk::system_program::id(), account.owner());
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
|
|
// Realloc to 0 with wrong owner
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
|
|
);
|
|
|
|
// realloc and assign to self via cpi
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_id,
|
|
&[REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
|
|
vec![
|
|
AccountMeta::new(pubkey, true),
|
|
AccountMeta::new(solana_sdk::system_program::id(), false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
|
|
);
|
|
|
|
// Assign to self and realloc via cpi
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_id,
|
|
&[ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
|
|
vec![
|
|
AccountMeta::new(pubkey, true),
|
|
AccountMeta::new(solana_sdk::system_program::id(), false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(&program_id, account.owner());
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
|
|
// Realloc to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// zero-init
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
program_id,
|
|
&[ZERO_INIT],
|
|
vec![AccountMeta::new(pubkey, true)],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_realloc_invoke() {
|
|
solana_logger::setup();
|
|
|
|
const START_BALANCE: u64 = 100_000_000_000;
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(1_000_000_000_000);
|
|
genesis_config.rent = Rent::default();
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let signer = &[&mint_keypair];
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let realloc_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_realloc",
|
|
);
|
|
|
|
let (bank, realloc_invoke_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_realloc_invoke",
|
|
);
|
|
|
|
let mut bump = 0;
|
|
let keypair = Keypair::new();
|
|
let pubkey = keypair.pubkey().clone();
|
|
let account = AccountSharedData::new(START_BALANCE, 5, &realloc_program_id);
|
|
bank.store_account(&pubkey, &account);
|
|
let invoke_keypair = Keypair::new();
|
|
let invoke_pubkey = invoke_keypair.pubkey().clone();
|
|
|
|
// Realloc RO account
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_ZERO_RO],
|
|
vec![
|
|
AccountMeta::new_readonly(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ReadonlyDataModified)
|
|
);
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(account.lamports(), START_BALANCE);
|
|
|
|
// Realloc account to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(account.lamports(), START_BALANCE);
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc to max + 1
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_MAX_PLUS_ONE],
|
|
vec![
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc to max twice
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_MAX_TWICE],
|
|
vec![
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc account to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(account.lamports(), START_BALANCE);
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc and assign
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_AND_ASSIGN],
|
|
vec![
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(&solana_sdk::system_program::id(), account.owner());
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
|
|
// Realloc to 0 with wrong owner
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
|
|
);
|
|
|
|
// realloc and assign to self via system program
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_AND_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM],
|
|
vec![
|
|
AccountMeta::new(pubkey, true),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::AccountDataSizeChanged)
|
|
);
|
|
|
|
// Assign to self and realloc via system program
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_ASSIGN_TO_SELF_VIA_SYSTEM_PROGRAM_AND_REALLOC],
|
|
vec![
|
|
AccountMeta::new(pubkey, true),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let account = bank.get_account(&pubkey).unwrap();
|
|
assert_eq!(&realloc_program_id, account.owner());
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(2 * MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
|
|
// Realloc to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc to 100 and check via CPI
|
|
let invoke_account = AccountSharedData::new(START_BALANCE, 5, &realloc_invoke_program_id);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_INVOKE_CHECK],
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client
|
|
.get_account_data(&invoke_pubkey)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(100, data.len());
|
|
for i in 0..5 {
|
|
assert_eq!(data[i], 0);
|
|
}
|
|
for i in 5..data.len() {
|
|
assert_eq!(data[i], 2);
|
|
}
|
|
|
|
// Create account, realloc, check
|
|
let new_keypair = Keypair::new();
|
|
let new_pubkey = new_keypair.pubkey().clone();
|
|
let mut instruction_data = vec![];
|
|
instruction_data.extend_from_slice(&[INVOKE_CREATE_ACCOUNT_REALLOC_CHECK, 1]);
|
|
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
&[&mint_keypair, &new_keypair],
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&instruction_data,
|
|
vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(new_pubkey, true),
|
|
AccountMeta::new(solana_sdk::system_program::id(), false),
|
|
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&new_pubkey).unwrap().unwrap();
|
|
assert_eq!(200, data.len());
|
|
let account = bank.get_account(&new_pubkey).unwrap();
|
|
assert_eq!(&realloc_invoke_program_id, account.owner());
|
|
|
|
// Invoke, dealloc, and assign
|
|
let pre_len = 100;
|
|
let new_len = pre_len * 2;
|
|
let mut invoke_account = AccountSharedData::new(START_BALANCE, pre_len, &realloc_program_id);
|
|
invoke_account.set_data_from_slice(&vec![1; pre_len]);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
let mut instruction_data = vec![];
|
|
instruction_data.extend_from_slice(&[INVOKE_DEALLOC_AND_ASSIGN, 1]);
|
|
instruction_data.extend_from_slice(&pre_len.to_le_bytes());
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&instruction_data,
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client
|
|
.get_account_data(&invoke_pubkey)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(new_len, data.len());
|
|
for i in 0..new_len {
|
|
assert_eq!(data[i], 0);
|
|
}
|
|
|
|
// Realloc to max invoke max
|
|
let invoke_account = AccountSharedData::new(42, 0, &realloc_invoke_program_id);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_MAX_INVOKE_MAX],
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// CPI realloc extend then local realloc extend
|
|
for (cpi_extend_bytes, local_extend_bytes, should_succeed) in [
|
|
(0, 0, true),
|
|
(MAX_PERMITTED_DATA_INCREASE, 0, true),
|
|
(0, MAX_PERMITTED_DATA_INCREASE, true),
|
|
(MAX_PERMITTED_DATA_INCREASE, 1, false),
|
|
(1, MAX_PERMITTED_DATA_INCREASE, false),
|
|
] {
|
|
let invoke_account = AccountSharedData::new(100_000_000, 0, &realloc_invoke_program_id);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
let mut instruction_data = vec![];
|
|
instruction_data.extend_from_slice(&[INVOKE_REALLOC_TO_THEN_LOCAL_REALLOC_EXTEND, 1]);
|
|
instruction_data.extend_from_slice(&cpi_extend_bytes.to_le_bytes());
|
|
instruction_data.extend_from_slice(&local_extend_bytes.to_le_bytes());
|
|
|
|
let result = bank_client.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&instruction_data,
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
);
|
|
|
|
if should_succeed {
|
|
assert!(
|
|
result.is_ok(),
|
|
"cpi: {cpi_extend_bytes} local: {local_extend_bytes}, err: {:?}",
|
|
result.err()
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc),
|
|
"cpi: {cpi_extend_bytes} local: {local_extend_bytes}",
|
|
);
|
|
}
|
|
}
|
|
|
|
// Realloc invoke max twice
|
|
let invoke_account = AccountSharedData::new(42, 0, &realloc_program_id);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_INVOKE_MAX_TWICE],
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc to 0
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[realloc(&realloc_program_id, &pubkey, 0, &mut bump)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!(0, data.len());
|
|
|
|
// Realloc to max length in max increase increments
|
|
for i in 0..MAX_PERMITTED_DATA_LENGTH as usize / MAX_PERMITTED_DATA_INCREASE {
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_EXTEND_MAX, 1, i as u8, (i / 255) as u8],
|
|
vec![
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client.get_account_data(&pubkey).unwrap().unwrap();
|
|
assert_eq!((i + 1) * MAX_PERMITTED_DATA_INCREASE, data.len());
|
|
}
|
|
for i in 0..data.len() {
|
|
assert_eq!(data[i], 1);
|
|
}
|
|
|
|
// and one more time should fail
|
|
assert_eq!(
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&[INVOKE_REALLOC_EXTEND_MAX, 2, 1, 1],
|
|
vec![
|
|
AccountMeta::new(pubkey, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
)
|
|
)
|
|
.unwrap_err()
|
|
.unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
|
|
// Realloc recursively and fill data
|
|
let invoke_keypair = Keypair::new();
|
|
let invoke_pubkey = invoke_keypair.pubkey().clone();
|
|
let invoke_account = AccountSharedData::new(START_BALANCE, 0, &realloc_invoke_program_id);
|
|
bank.store_account(&invoke_pubkey, &invoke_account);
|
|
let mut instruction_data = vec![];
|
|
instruction_data.extend_from_slice(&[INVOKE_REALLOC_RECURSIVE, 1]);
|
|
instruction_data.extend_from_slice(&100_usize.to_le_bytes());
|
|
bank_client
|
|
.send_and_confirm_message(
|
|
signer,
|
|
Message::new(
|
|
&[Instruction::new_with_bytes(
|
|
realloc_invoke_program_id,
|
|
&instruction_data,
|
|
vec![
|
|
AccountMeta::new(invoke_pubkey, false),
|
|
AccountMeta::new_readonly(realloc_invoke_program_id, false),
|
|
],
|
|
)],
|
|
Some(&mint_pubkey),
|
|
),
|
|
)
|
|
.unwrap();
|
|
let data = bank_client
|
|
.get_account_data(&invoke_pubkey)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(200, data.len());
|
|
for i in 0..100 {
|
|
assert_eq!(data[i], 1);
|
|
}
|
|
for i in 100..200 {
|
|
assert_eq!(data[i], 2);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_processed_inner_instruction() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let sibling_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_sibling_instructions",
|
|
);
|
|
let sibling_inner_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_sibling_inner_instructions",
|
|
);
|
|
let noop_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
let (_, invoke_and_return_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke_and_return",
|
|
);
|
|
|
|
let instruction2 = Instruction::new_with_bytes(
|
|
noop_program_id,
|
|
&[43],
|
|
vec![
|
|
AccountMeta::new_readonly(noop_program_id, false),
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
],
|
|
);
|
|
let instruction1 = Instruction::new_with_bytes(
|
|
noop_program_id,
|
|
&[42],
|
|
vec![
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(noop_program_id, false),
|
|
],
|
|
);
|
|
let instruction0 = Instruction::new_with_bytes(
|
|
sibling_program_id,
|
|
&[1, 2, 3, 0, 4, 5, 6],
|
|
vec![
|
|
AccountMeta::new(mint_keypair.pubkey(), true),
|
|
AccountMeta::new_readonly(noop_program_id, false),
|
|
AccountMeta::new_readonly(invoke_and_return_program_id, false),
|
|
AccountMeta::new_readonly(sibling_inner_program_id, false),
|
|
],
|
|
);
|
|
let message = Message::new(
|
|
&[instruction2, instruction1, instruction0],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
assert!(bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message)
|
|
.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_fees() {
|
|
solana_logger::setup();
|
|
|
|
let congestion_multiplier = 1;
|
|
|
|
let GenesisConfigInfo {
|
|
mut genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(500_000_000);
|
|
|
|
genesis_config.fee_rate_governor = FeeRateGovernor::new(congestion_multiplier, 0);
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let fee_structure =
|
|
FeeStructure::new(0.000005, 0.0, vec![(200, 0.0000005), (1400000, 0.000005)]);
|
|
bank.fee_structure = fee_structure.clone();
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_noop",
|
|
);
|
|
|
|
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
|
|
let message = Message::new(
|
|
&[Instruction::new_with_bytes(program_id, &[], vec![])],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
|
|
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
|
|
let expected_normal_fee = fee_structure.calculate_fee(
|
|
&sanitized_message,
|
|
congestion_multiplier,
|
|
&process_compute_budget_instructions(sanitized_message.program_instructions_iter())
|
|
.unwrap_or_default()
|
|
.into(),
|
|
false,
|
|
);
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message)
|
|
.unwrap();
|
|
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
|
|
assert_eq!(pre_balance - post_balance, expected_normal_fee);
|
|
|
|
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
|
|
let message = Message::new(
|
|
&[
|
|
ComputeBudgetInstruction::set_compute_unit_price(1),
|
|
Instruction::new_with_bytes(program_id, &[], vec![]),
|
|
],
|
|
Some(&mint_keypair.pubkey()),
|
|
);
|
|
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
|
|
let expected_prioritized_fee = fee_structure.calculate_fee(
|
|
&sanitized_message,
|
|
congestion_multiplier,
|
|
&process_compute_budget_instructions(sanitized_message.program_instructions_iter())
|
|
.unwrap_or_default()
|
|
.into(),
|
|
false,
|
|
);
|
|
assert!(expected_normal_fee < expected_prioritized_fee);
|
|
|
|
bank_client
|
|
.send_and_confirm_message(&[&mint_keypair], message)
|
|
.unwrap();
|
|
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
|
|
assert_eq!(pre_balance - post_balance, expected_prioritized_fee);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_get_minimum_delegation() {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
let bank = Bank::new_for_tests(&genesis_config);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank.clone());
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_get_minimum_delegation",
|
|
);
|
|
|
|
let account_metas = vec![AccountMeta::new_readonly(stake::program::id(), false)];
|
|
let instruction = Instruction::new_with_bytes(program_id, &[], account_metas);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_program_sbf_inner_instruction_alignment_checks() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(50);
|
|
let (bank, bank_forks) = Bank::new_with_bank_forks_for_tests(&genesis_config);
|
|
let noop = create_program(&bank, &bpf_loader_deprecated::id(), "solana_sbf_rust_noop");
|
|
let inner_instruction_alignment_check = create_program(
|
|
&bank,
|
|
&bpf_loader_deprecated::id(),
|
|
"solana_sbf_rust_inner_instruction_alignment_check",
|
|
);
|
|
|
|
// invoke unaligned program, which will call aligned program twice,
|
|
// unaligned should be allowed once invoke completes
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
bank_client
|
|
.advance_slot(1, bank_forks.as_ref(), &Pubkey::default())
|
|
.expect("Failed to advance the slot");
|
|
let mut instruction = Instruction::new_with_bytes(
|
|
inner_instruction_alignment_check,
|
|
&[0],
|
|
vec![
|
|
AccountMeta::new_readonly(noop, false),
|
|
AccountMeta::new_readonly(mint_keypair.pubkey(), false),
|
|
],
|
|
);
|
|
|
|
instruction.data[0] += 1;
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction.clone());
|
|
assert!(result.is_ok(), "{result:?}");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_cpi_account_ownership_writability() {
|
|
solana_logger::setup();
|
|
|
|
for direct_mapping in [false, true] {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let mut feature_set = FeatureSet::all_enabled();
|
|
if !direct_mapping {
|
|
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
|
|
}
|
|
|
|
bank.feature_set = Arc::new(feature_set);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let invoke_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let invoked_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoked",
|
|
);
|
|
|
|
let (bank, realloc_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_realloc",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(invoked_program_id, false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
];
|
|
|
|
for (account_size, byte_index) in [
|
|
(0, 0), // first realloc byte
|
|
(0, MAX_PERMITTED_DATA_INCREASE as u8), // last realloc byte
|
|
(2, 0), // first data byte
|
|
(2, 1), // last data byte
|
|
(2, 3), // first realloc byte
|
|
(2, 2 + MAX_PERMITTED_DATA_INCREASE as u8), // last realloc byte
|
|
] {
|
|
for instruction_id in [
|
|
TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLEE,
|
|
TEST_FORBID_WRITE_AFTER_OWNERSHIP_CHANGE_IN_CALLER,
|
|
] {
|
|
bank.register_unique_recent_blockhash_for_test();
|
|
let account = AccountSharedData::new(42, account_size, &invoke_program_id);
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&[instruction_id, byte_index, 42, 42],
|
|
account_metas.clone(),
|
|
);
|
|
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
|
|
if (byte_index as usize) < account_size || direct_mapping {
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(
|
|
0,
|
|
InstructionError::ExternalAccountDataModified
|
|
)
|
|
);
|
|
} else {
|
|
// without direct mapping, changes to the realloc padding
|
|
// outside the account length are ignored
|
|
assert!(result.is_ok(), "{result:?}");
|
|
}
|
|
}
|
|
}
|
|
// Test that the CPI code that updates `ref_to_len_in_vm` fails if we
|
|
// make it write to an invalid location. This is the first variant which
|
|
// correctly triggers ExternalAccountDataModified when direct mapping is
|
|
// disabled. When direct mapping is enabled this tests fails early
|
|
// because we move the account data pointer.
|
|
// TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE is able to make more
|
|
// progress when direct mapping is on.
|
|
let account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let instruction_data = vec![
|
|
TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE_MOVING_DATA_POINTER,
|
|
42,
|
|
42,
|
|
42,
|
|
];
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
if direct_mapping {
|
|
// We move the data pointer, direct mapping doesn't allow it
|
|
// anymore so it errors out earlier. See
|
|
// test_cpi_invalid_account_info_pointers.
|
|
TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete)
|
|
} else {
|
|
// We managed to make CPI write into the account data, but the
|
|
// usual checks still apply and we get an error.
|
|
TransactionError::InstructionError(0, InstructionError::ExternalAccountDataModified)
|
|
}
|
|
);
|
|
|
|
// We're going to try and make CPI write ref_to_len_in_vm into a 2nd
|
|
// account, so we add an extra one here.
|
|
let account2_keypair = Keypair::new();
|
|
let mut account_metas = account_metas.clone();
|
|
account_metas.push(AccountMeta::new(account2_keypair.pubkey(), false));
|
|
|
|
for target_account in [1, account_metas.len() as u8 - 1] {
|
|
// Similar to the test above where we try to make CPI write into account
|
|
// data. This variant is for when direct mapping is enabled.
|
|
let account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
bank.store_account(&account2_keypair.pubkey(), &account);
|
|
let instruction_data = vec![
|
|
TEST_FORBID_LEN_UPDATE_AFTER_OWNERSHIP_CHANGE,
|
|
target_account,
|
|
42,
|
|
42,
|
|
];
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
|
|
let (result, _, logs) = process_transaction_and_record_inner(&bank, tx);
|
|
if direct_mapping {
|
|
assert_eq!(
|
|
result.unwrap_err(),
|
|
TransactionError::InstructionError(
|
|
0,
|
|
InstructionError::ProgramFailedToComplete
|
|
)
|
|
);
|
|
// We haven't moved the data pointer, but ref_to_len_vm _is_ in
|
|
// the account data vm range and that's not allowed either.
|
|
assert!(
|
|
logs.iter().any(|log| log.contains("Invalid pointer")),
|
|
"{logs:?}"
|
|
);
|
|
} else {
|
|
// we expect this to succeed as after updating `ref_to_len_in_vm`,
|
|
// CPI will sync the actual account data between the callee and the
|
|
// caller, _always_ writing over the location pointed by
|
|
// `ref_to_len_in_vm`. To verify this, we check that the account
|
|
// data is in fact all zeroes like it is in the callee.
|
|
result.unwrap();
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), vec![0; 40]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_cpi_account_data_updates() {
|
|
solana_logger::setup();
|
|
|
|
for direct_mapping in [false, true] {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let mut feature_set = FeatureSet::all_enabled();
|
|
if !direct_mapping {
|
|
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
|
|
}
|
|
|
|
bank.feature_set = Arc::new(feature_set);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let invoke_program_id = load_upgradeable_program_wrapper(
|
|
&bank_client,
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let (bank, realloc_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_realloc",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(realloc_program_id, false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
];
|
|
|
|
// This tests the case where a caller extends an account beyond the original
|
|
// data length. The callee should see the extended data (asserted in the
|
|
// callee program, not here).
|
|
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS];
|
|
instruction_data.extend_from_slice(b"bar");
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
// "bar" here was copied from the realloc region
|
|
assert_eq!(account.data(), b"foobar");
|
|
|
|
// This tests the case where a callee extends an account beyond the original
|
|
// data length. The caller should see the extended data where the realloc
|
|
// region contains the new data. In this test the callee owns the account,
|
|
// the caller can't write but the CPI glue still updates correctly.
|
|
let mut account = AccountSharedData::new(42, 0, &realloc_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_GROWS];
|
|
instruction_data.extend_from_slice(b"bar");
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
result.unwrap();
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
// "bar" here was copied from the realloc region
|
|
assert_eq!(account.data(), b"foobar");
|
|
|
|
// This tests the case where a callee shrinks an account, the caller data
|
|
// slice must be truncated accordingly and post_len..original_data_len must
|
|
// be zeroed (zeroing is checked in the invoked program not here). Same as
|
|
// above, the callee owns the account but the changes are still reflected in
|
|
// the caller even if things are readonly from the caller's POV.
|
|
let mut account = AccountSharedData::new(42, 0, &realloc_program_id);
|
|
account.set_data(b"foobar".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data =
|
|
vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN];
|
|
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), b"foob");
|
|
|
|
// This tests the case where the program extends an account, then calls
|
|
// itself and in the inner call it shrinks the account to a size that is
|
|
// still larger than the original size. The account data must be set to the
|
|
// correct value in the caller frame, and the realloc region must be zeroed
|
|
// (again tested in the invoked program).
|
|
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
|
|
// realloc to "foobazbad" then shrink to "foobazb"
|
|
instruction_data.extend_from_slice(7usize.to_le_bytes().as_ref());
|
|
instruction_data.extend_from_slice(b"bazbad");
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), b"foobazb");
|
|
|
|
// Similar to the test above, but this time the nested invocation shrinks to
|
|
// _below_ the original data length. Both the spare capacity in the account
|
|
// data _end_ the realloc region must be zeroed.
|
|
let mut account = AccountSharedData::new(42, 0, &invoke_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS_CALLEE_SHRINKS];
|
|
// realloc to "foobazbad" then shrink to "f"
|
|
instruction_data.extend_from_slice(1usize.to_le_bytes().as_ref());
|
|
instruction_data.extend_from_slice(b"bazbad");
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), b"f");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_cpi_deprecated_loader_realloc() {
|
|
solana_logger::setup();
|
|
|
|
for direct_mapping in [false, true] {
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let mut feature_set = FeatureSet::all_enabled();
|
|
if !direct_mapping {
|
|
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
|
|
}
|
|
|
|
bank.feature_set = Arc::new(feature_set);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
|
|
let deprecated_program_id = create_program(
|
|
&bank,
|
|
&bpf_loader_deprecated::id(),
|
|
"solana_sbf_rust_deprecated_loader",
|
|
);
|
|
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(deprecated_program_id, false),
|
|
AccountMeta::new_readonly(deprecated_program_id, false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
];
|
|
|
|
// If a bpf_loader_deprecated program extends an account, the callee
|
|
// accidentally sees the extended data when direct mapping is off, but
|
|
// direct mapping fixes the issue
|
|
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLER_GROWS];
|
|
instruction_data.extend_from_slice(b"bar");
|
|
let instruction = Instruction::new_with_bytes(
|
|
deprecated_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
// when direct mapping is off, the realloc will accidentally clobber
|
|
// whatever comes after the data slice (owner, executable, rent epoch
|
|
// etc). When direct mapping is on, you get an InvalidRealloc error.
|
|
if direct_mapping {
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::InvalidRealloc)
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ModifiedProgramId)
|
|
);
|
|
}
|
|
|
|
// check that if a bpf_loader_deprecated program extends an account, the
|
|
// extended data is ignored
|
|
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
|
|
account.set_data(b"foo".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_GROWS];
|
|
instruction_data.extend_from_slice(b"bar");
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), b"foo");
|
|
|
|
// check that if a bpf_loader_deprecated program truncates an account,
|
|
// the caller doesn't see the truncation
|
|
let mut account = AccountSharedData::new(42, 0, &deprecated_program_id);
|
|
account.set_data(b"foobar".to_vec());
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data =
|
|
vec![TEST_CPI_ACCOUNT_UPDATE_CALLEE_SHRINKS_SMALLER_THAN_ORIGINAL_LEN];
|
|
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let account = bank.get_account(&account_keypair.pubkey()).unwrap();
|
|
assert_eq!(account.data(), b"foobar");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_cpi_change_account_data_memory_allocation() {
|
|
use solana_program_runtime::{declare_process_instruction, loaded_programs::LoadedProgram};
|
|
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
|
|
declare_process_instruction!(MockBuiltin, 42, |invoke_context| {
|
|
let transaction_context = &invoke_context.transaction_context;
|
|
let instruction_context = transaction_context.get_current_instruction_context()?;
|
|
let instruction_data = instruction_context.get_instruction_data();
|
|
|
|
let index_in_transaction =
|
|
instruction_context.get_index_of_instruction_account_in_transaction(0)?;
|
|
|
|
let mut account = transaction_context
|
|
.accounts()
|
|
.get(index_in_transaction)
|
|
.unwrap()
|
|
.borrow_mut();
|
|
|
|
// Test changing the account data both in place and by changing the
|
|
// underlying vector. CPI will have to detect the vector change and
|
|
// update the corresponding memory region. In all cases CPI will have
|
|
// to zero the spare bytes correctly.
|
|
match instruction_data[0] {
|
|
0xFE => account.set_data(instruction_data.to_vec()),
|
|
0xFD => account.set_data_from_slice(instruction_data),
|
|
0xFC => {
|
|
// Exercise the update_caller_account capacity check where account len != capacity.
|
|
let mut data = instruction_data.to_vec();
|
|
data.reserve_exact(1);
|
|
account.set_data(data)
|
|
}
|
|
_ => panic!(),
|
|
}
|
|
|
|
Ok(())
|
|
});
|
|
|
|
let builtin_program_id = Pubkey::new_unique();
|
|
bank.add_builtin(
|
|
builtin_program_id,
|
|
"test_cpi_change_account_data_memory_allocation_builtin".to_string(),
|
|
LoadedProgram::new_builtin(0, 42, MockBuiltin::vm),
|
|
);
|
|
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(builtin_program_id, false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
];
|
|
|
|
let mut account = AccountSharedData::new(42, 20, &builtin_program_id);
|
|
account.set_data(vec![0xFF; 20]);
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let mut instruction_data = vec![TEST_CPI_CHANGE_ACCOUNT_DATA_MEMORY_ALLOCATION];
|
|
instruction_data.extend_from_slice(builtin_program_id.as_ref());
|
|
let instruction =
|
|
Instruction::new_with_bytes(invoke_program_id, &instruction_data, account_metas.clone());
|
|
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_cpi_invalid_account_info_pointers() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
let bank = Bank::new_for_tests(&genesis_config);
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let c_invoke_program_id =
|
|
load_upgradeable_program_wrapper(&bank_client, &mint_keypair, &authority_keypair, "invoke");
|
|
|
|
let (bank, invoke_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
AccountMeta::new_readonly(c_invoke_program_id, false),
|
|
];
|
|
|
|
for invoke_program_id in [invoke_program_id, c_invoke_program_id] {
|
|
for ix in [
|
|
TEST_CPI_INVALID_KEY_POINTER,
|
|
TEST_CPI_INVALID_LAMPORTS_POINTER,
|
|
TEST_CPI_INVALID_OWNER_POINTER,
|
|
TEST_CPI_INVALID_DATA_POINTER,
|
|
] {
|
|
let account = AccountSharedData::new(42, 5, &invoke_program_id);
|
|
bank.store_account(&account_keypair.pubkey(), &account);
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&[ix, 42, 42, 42],
|
|
account_metas.clone(),
|
|
);
|
|
|
|
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
|
let tx = Transaction::new(&[&mint_keypair], message.clone(), bank.last_blockhash());
|
|
let (result, _, logs) = process_transaction_and_record_inner(&bank, tx);
|
|
assert!(result.is_err(), "{result:?}");
|
|
assert!(
|
|
logs.iter().any(|log| log.contains("Invalid pointer")),
|
|
"{logs:?}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(feature = "sbf_rust")]
|
|
fn test_deny_executable_write() {
|
|
solana_logger::setup();
|
|
|
|
let GenesisConfigInfo {
|
|
genesis_config,
|
|
mint_keypair,
|
|
..
|
|
} = create_genesis_config(100_123_456_789);
|
|
|
|
for direct_mapping in [false, true] {
|
|
let mut bank = Bank::new_for_tests(&genesis_config);
|
|
let feature_set = Arc::make_mut(&mut bank.feature_set);
|
|
// by default test banks have all features enabled, so we only need to
|
|
// disable when needed
|
|
if !direct_mapping {
|
|
feature_set.deactivate(&feature_set::bpf_account_data_direct_mapping::id());
|
|
}
|
|
let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
|
|
let mut bank_client = BankClient::new_shared(bank);
|
|
let authority_keypair = Keypair::new();
|
|
|
|
let (_bank, invoke_program_id) = load_upgradeable_program_and_advance_slot(
|
|
&mut bank_client,
|
|
bank_forks.as_ref(),
|
|
&mint_keypair,
|
|
&authority_keypair,
|
|
"solana_sbf_rust_invoke",
|
|
);
|
|
|
|
let account_keypair = Keypair::new();
|
|
let mint_pubkey = mint_keypair.pubkey();
|
|
let account_metas = vec![
|
|
AccountMeta::new(mint_pubkey, true),
|
|
AccountMeta::new(account_keypair.pubkey(), false),
|
|
AccountMeta::new_readonly(invoke_program_id, false),
|
|
];
|
|
|
|
let mut instruction_data = vec![TEST_WRITE_ACCOUNT, 2];
|
|
instruction_data.extend_from_slice(4usize.to_le_bytes().as_ref());
|
|
instruction_data.push(42);
|
|
let instruction = Instruction::new_with_bytes(
|
|
invoke_program_id,
|
|
&instruction_data,
|
|
account_metas.clone(),
|
|
);
|
|
let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction);
|
|
assert_eq!(
|
|
result.unwrap_err().unwrap(),
|
|
TransactionError::InstructionError(0, InstructionError::ExecutableDataModified)
|
|
);
|
|
}
|
|
}
|