solana/programs/loader-v3/src/lib.rs

1477 lines
53 KiB
Rust

use {
rand::Rng,
solana_measure::measure::Measure,
solana_program_runtime::{
compute_budget::ComputeBudget,
ic_logger_msg,
invoke_context::InvokeContext,
loaded_programs::{LoadProgramMetrics, LoadedProgram, LoadedProgramType},
log_collector::LogCollector,
stable_log,
},
solana_rbpf::{
aligned_memory::AlignedMemory,
ebpf,
memory_region::{MemoryMapping, MemoryRegion},
verifier::RequisiteVerifier,
vm::{
BuiltInProgram, Config, ContextObject, EbpfVm, ProgramResult, VerifiedExecutable,
PROGRAM_ENVIRONMENT_KEY_SHIFT,
},
},
solana_sdk::{
entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{self, FeatureSet},
instruction::InstructionError,
loader_v3::{self, LoaderV3State, DEPLOYMENT_COOLDOWN_IN_SLOTS},
loader_v3_instruction::LoaderV3Instruction,
program_utils::limited_deserialize,
pubkey::Pubkey,
saturating_add_assign,
transaction_context::{BorrowedAccount, InstructionContext},
},
std::{
cell::RefCell,
rc::Rc,
sync::{atomic::Ordering, Arc},
},
};
fn get_state(data: &[u8]) -> Result<&LoaderV3State, InstructionError> {
unsafe {
let data = data
.get(0..LoaderV3State::program_data_offset())
.ok_or(InstructionError::AccountDataTooSmall)?
.try_into()
.unwrap();
Ok(std::mem::transmute::<
&[u8; LoaderV3State::program_data_offset()],
&LoaderV3State,
>(data))
}
}
fn get_state_mut(data: &mut [u8]) -> Result<&mut LoaderV3State, InstructionError> {
unsafe {
let data = data
.get_mut(0..LoaderV3State::program_data_offset())
.ok_or(InstructionError::AccountDataTooSmall)?
.try_into()
.unwrap();
Ok(std::mem::transmute::<
&mut [u8; LoaderV3State::program_data_offset()],
&mut LoaderV3State,
>(data))
}
}
pub fn load_program_from_account(
_feature_set: &FeatureSet,
compute_budget: &ComputeBudget,
log_collector: Option<Rc<RefCell<LogCollector>>>,
program: &BorrowedAccount,
use_jit: bool,
debugging_features: bool,
) -> Result<(Arc<LoadedProgram>, LoadProgramMetrics), InstructionError> {
let mut load_program_metrics = LoadProgramMetrics {
program_id: program.get_key().to_string(),
..LoadProgramMetrics::default()
};
let config = Config {
max_call_depth: compute_budget.max_call_depth,
stack_frame_size: compute_budget.stack_frame_size,
enable_stack_frame_gaps: false,
instruction_meter_checkpoint_distance: 10000,
enable_instruction_meter: true,
enable_instruction_tracing: debugging_features,
enable_symbol_and_section_labels: debugging_features,
reject_broken_elfs: true,
noop_instruction_rate: 256,
sanitize_user_provided_values: true,
runtime_environment_key: rand::thread_rng()
.gen::<i32>()
.checked_shr(PROGRAM_ENVIRONMENT_KEY_SHIFT)
.unwrap_or(0),
external_internal_function_hash_collision: true,
reject_callx_r10: true,
dynamic_stack_frames: true,
enable_sdiv: true,
optimize_rodata: true,
static_syscalls: true,
enable_elf_vaddr: true,
reject_rodata_stack_overlap: true,
new_elf_parser: true,
aligned_memory_mapping: true,
// Warning, do not use `Config::default()` so that configuration here is explicit.
};
let loader = BuiltInProgram::new_loader(config);
let state = get_state(program.get_data())?;
let programdata = program
.get_data()
.get(LoaderV3State::program_data_offset()..)
.ok_or(InstructionError::AccountDataTooSmall)?;
let loaded_program = LoadedProgram::new(
&loader_v3::id(),
Arc::new(loader),
state.slot,
state.slot.saturating_add(1),
programdata,
program.get_data().len(),
use_jit,
&mut load_program_metrics,
)
.map_err(|err| {
ic_logger_msg!(log_collector, "{}", err);
InstructionError::InvalidAccountData
})?;
Ok((Arc::new(loaded_program), load_program_metrics))
}
fn calculate_heap_cost(heap_size: u64, heap_cost: u64) -> u64 {
const KIBIBYTE: u64 = 1024;
const PAGE_SIZE_KB: u64 = 32;
heap_size
.saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1))
.saturating_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE))
.saturating_sub(1)
.saturating_mul(heap_cost)
}
/// Create the SBF virtual machine
pub fn create_vm<'a, 'b>(
invoke_context: &'a mut InvokeContext<'b>,
program: &'a VerifiedExecutable<RequisiteVerifier, InvokeContext<'b>>,
) -> Result<EbpfVm<'a, RequisiteVerifier, InvokeContext<'b>>, Box<dyn std::error::Error>> {
let config = program.get_executable().get_config();
let compute_budget = invoke_context.get_compute_budget();
let heap_size = compute_budget.heap_size.unwrap_or(HEAP_LENGTH);
invoke_context.consume_checked(calculate_heap_cost(
heap_size as u64,
compute_budget.heap_cost,
))?;
let mut stack = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled(config.stack_size());
let mut heap = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled(
compute_budget.heap_size.unwrap_or(HEAP_LENGTH),
);
let stack_len = stack.len();
let regions: Vec<MemoryRegion> = vec![
program.get_executable().get_ro_region(),
MemoryRegion::new_writable_gapped(stack.as_slice_mut(), ebpf::MM_STACK_START, 0),
MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
];
let log_collector = invoke_context.get_log_collector();
let memory_mapping = MemoryMapping::new(regions, config).map_err(|err| {
ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", err);
Box::new(InstructionError::ProgramEnvironmentSetupFailure)
})?;
Ok(EbpfVm::new(
program,
invoke_context,
memory_mapping,
stack_len,
))
}
fn execute(
invoke_context: &mut InvokeContext,
program: &VerifiedExecutable<RequisiteVerifier, InvokeContext<'static>>,
) -> Result<(), Box<dyn std::error::Error>> {
let log_collector = invoke_context.get_log_collector();
let stack_height = invoke_context.get_stack_height();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let program_id = *instruction_context.get_last_program_key(transaction_context)?;
#[cfg(any(target_os = "windows", not(target_arch = "x86_64")))]
let use_jit = false;
#[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
let use_jit = program.get_executable().get_compiled_program().is_some();
let compute_meter_prev = invoke_context.get_remaining();
let mut create_vm_time = Measure::start("create_vm");
let mut vm = create_vm(
invoke_context,
// We dropped the lifetime tracking in the Executor by setting it to 'static,
// thus we need to reintroduce the correct lifetime of InvokeContext here again.
unsafe { std::mem::transmute(program) },
)?;
create_vm_time.stop();
let mut execute_time = Measure::start("execute");
stable_log::program_invoke(&log_collector, &program_id, stack_height);
let (compute_units_consumed, result) = vm.execute_program(!use_jit);
drop(vm);
ic_logger_msg!(
log_collector,
"Program {} consumed {} of {} compute units",
&program_id,
compute_units_consumed,
compute_meter_prev
);
execute_time.stop();
let timings = &mut invoke_context.timings;
timings.create_vm_us = timings.create_vm_us.saturating_add(create_vm_time.as_us());
timings.execute_us = timings.execute_us.saturating_add(execute_time.as_us());
match result {
ProgramResult::Ok(status) if status != SUCCESS => {
let error: InstructionError = status.into();
Err(Box::new(error) as Box<dyn std::error::Error>)
}
ProgramResult::Err(error) => Err(error),
_ => Ok(()),
}
}
fn check_program_account(
log_collector: &Option<Rc<RefCell<LogCollector>>>,
instruction_context: &InstructionContext,
program: &BorrowedAccount,
authority_address: &Pubkey,
) -> Result<LoaderV3State, InstructionError> {
if !loader_v3::check_id(program.get_owner()) {
ic_logger_msg!(log_collector, "Program not owned by loader");
return Err(InstructionError::InvalidAccountOwner);
}
if program.get_data().is_empty() {
ic_logger_msg!(log_collector, "Program is uninitialized");
return Err(InstructionError::InvalidAccountData);
}
let state = get_state(program.get_data())?;
if !program.is_writable() {
ic_logger_msg!(log_collector, "Program is not writeable");
return Err(InstructionError::InvalidArgument);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
if state.authority_address.is_none() {
ic_logger_msg!(log_collector, "Program is finalized");
return Err(InstructionError::Immutable);
}
if state.authority_address != Some(*authority_address) {
ic_logger_msg!(log_collector, "Incorrect authority provided");
return Err(InstructionError::IncorrectAuthority);
}
Ok(*state)
}
pub fn process_instruction_write(
invoke_context: &mut InvokeContext,
offset: u32,
bytes: Vec<u8>,
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(1)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
let mut payer = instruction_context
.try_borrow_instruction_account(transaction_context, 2)
.ok();
let is_initialization = offset == 0 && program.get_data().is_empty();
if is_initialization {
if !loader_v3::check_id(program.get_owner()) {
ic_logger_msg!(log_collector, "Program not owned by loader");
return Err(InstructionError::InvalidAccountOwner);
}
if !program.is_writable() {
ic_logger_msg!(log_collector, "Program is not writeable");
return Err(InstructionError::InvalidArgument);
}
if !instruction_context.is_instruction_account_signer(1)? {
ic_logger_msg!(log_collector, "Authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
} else {
let state = check_program_account(
&log_collector,
instruction_context,
&program,
authority_address,
)?;
if state.is_deployed {
ic_logger_msg!(log_collector, "Program is not retracted");
return Err(InstructionError::InvalidArgument);
}
}
if payer.is_some() && !instruction_context.is_instruction_account_signer(2)? {
ic_logger_msg!(log_collector, "Payer did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
if payer.is_some() && !instruction_context.is_instruction_account_writable(2)? {
ic_logger_msg!(log_collector, "Payer is not writeable");
return Err(InstructionError::InvalidArgument);
}
let program_size = program
.get_data()
.len()
.saturating_sub(LoaderV3State::program_data_offset());
if offset as usize > program_size {
ic_logger_msg!(log_collector, "Write out of bounds");
return Err(InstructionError::AccountDataTooSmall);
}
let end_offset = (offset as usize).saturating_add(bytes.len());
let rent = invoke_context.get_sysvar_cache().get_rent()?;
let required_lamports =
rent.minimum_balance(LoaderV3State::program_data_offset().saturating_add(end_offset));
let transfer_lamports = required_lamports.saturating_sub(program.get_lamports());
if transfer_lamports > 0 {
payer = payer.filter(|payer| payer.get_lamports() >= transfer_lamports);
if payer.is_none() {
ic_logger_msg!(
log_collector,
"Insufficient lamports, {} are required",
required_lamports
);
return Err(InstructionError::InsufficientFunds);
}
}
if end_offset > program_size {
program.set_data_length(LoaderV3State::program_data_offset().saturating_add(end_offset))?;
}
if let Some(mut payer) = payer {
payer.checked_sub_lamports(transfer_lamports)?;
program.checked_add_lamports(transfer_lamports)?;
}
if is_initialization {
let state = get_state_mut(program.get_data_mut()?)?;
state.slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
state.is_deployed = false;
state.authority_address = Some(*authority_address);
}
program
.get_data_mut()?
.get_mut(
LoaderV3State::program_data_offset().saturating_add(offset as usize)
..LoaderV3State::program_data_offset().saturating_add(end_offset),
)
.ok_or(InstructionError::AccountDataTooSmall)?
.copy_from_slice(&bytes);
Ok(())
}
pub fn process_instruction_truncate(
invoke_context: &mut InvokeContext,
offset: u32,
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(1)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
let mut recipient =
instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
let state = check_program_account(
&log_collector,
instruction_context,
&program,
authority_address,
)?;
if state.is_deployed {
ic_logger_msg!(log_collector, "Program is not retracted");
return Err(InstructionError::InvalidArgument);
}
let program_size = program
.get_data()
.len()
.saturating_sub(LoaderV3State::program_data_offset());
if offset as usize > program_size {
ic_logger_msg!(log_collector, "Truncate out of bounds");
return Err(InstructionError::AccountDataTooSmall);
}
let required_lamports = if offset == 0 {
program.set_data_length(0)?;
0
} else {
program.set_data_length(
LoaderV3State::program_data_offset().saturating_add(offset as usize),
)?;
let rent = invoke_context.get_sysvar_cache().get_rent()?;
rent.minimum_balance(program.get_data().len())
};
let transfer_lamports = program.get_lamports().saturating_sub(required_lamports);
program.checked_sub_lamports(transfer_lamports)?;
recipient.checked_add_lamports(transfer_lamports)?;
Ok(())
}
pub fn process_instruction_deploy(
invoke_context: &mut InvokeContext,
use_jit: bool,
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(1)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
let source_program = instruction_context
.try_borrow_instruction_account(transaction_context, 2)
.ok();
let state = check_program_account(
&log_collector,
instruction_context,
&program,
authority_address,
)?;
let current_slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
if state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot {
ic_logger_msg!(
log_collector,
"Program was deployed recently, cooldown still in effect"
);
return Err(InstructionError::InvalidArgument);
}
if state.is_deployed {
ic_logger_msg!(log_collector, "Destination program is not retracted");
return Err(InstructionError::InvalidArgument);
}
let buffer = if let Some(ref source_program) = source_program {
let source_state = check_program_account(
&log_collector,
instruction_context,
source_program,
authority_address,
)?;
if source_state.is_deployed {
ic_logger_msg!(log_collector, "Source program is not retracted");
return Err(InstructionError::InvalidArgument);
}
source_program
} else {
&program
};
let (_executor, load_program_metrics) = load_program_from_account(
&invoke_context.feature_set,
invoke_context.get_compute_budget(),
invoke_context.get_log_collector(),
buffer,
use_jit,
false,
)?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
if let Some(mut source_program) = source_program {
let rent = invoke_context.get_sysvar_cache().get_rent()?;
let required_lamports = rent.minimum_balance(program.get_data().len());
let transfer_lamports = program.get_lamports().saturating_sub(required_lamports);
program.set_data_from_slice(source_program.get_data())?;
source_program.set_data_length(0)?;
source_program.checked_sub_lamports(transfer_lamports)?;
program.checked_add_lamports(transfer_lamports)?;
}
let state = get_state_mut(program.get_data_mut()?)?;
state.slot = current_slot;
state.is_deployed = true;
Ok(())
}
pub fn process_instruction_retract(
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(1)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
let state = check_program_account(
&log_collector,
instruction_context,
&program,
authority_address,
)?;
let current_slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
if state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot {
ic_logger_msg!(
log_collector,
"Program was deployed recently, cooldown still in effect"
);
return Err(InstructionError::InvalidArgument);
}
if !state.is_deployed {
ic_logger_msg!(log_collector, "Program is not deployed");
return Err(InstructionError::InvalidArgument);
}
let state = get_state_mut(program.get_data_mut()?)?;
state.is_deployed = false;
Ok(())
}
pub fn process_instruction_transfer_authority(
invoke_context: &mut InvokeContext,
) -> Result<(), InstructionError> {
let log_collector = invoke_context.get_log_collector();
let transaction_context = &invoke_context.transaction_context;
let instruction_context = transaction_context.get_current_instruction_context()?;
let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
let authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(1)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
let new_authority_address = instruction_context
.get_index_of_instruction_account_in_transaction(2)
.and_then(|index| transaction_context.get_key_of_account_at_index(index))
.ok()
.cloned();
let _state = check_program_account(
&log_collector,
instruction_context,
&program,
authority_address,
)?;
if new_authority_address.is_some() && !instruction_context.is_instruction_account_signer(2)? {
ic_logger_msg!(log_collector, "New authority did not sign");
return Err(InstructionError::MissingRequiredSignature);
}
let state = get_state_mut(program.get_data_mut()?)?;
state.authority_address = new_authority_address;
Ok(())
}
pub fn process_instruction(
invoke_context: &mut InvokeContext,
) -> Result<(), Box<dyn std::error::Error>> {
let use_jit = true;
let log_collector = invoke_context.get_log_collector();
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 program_id = instruction_context.get_last_program_key(transaction_context)?;
if loader_v3::check_id(program_id) {
if invoke_context
.feature_set
.is_active(&feature_set::native_programs_consume_cu::id())
{
invoke_context.consume_checked(2000)?;
}
match limited_deserialize(instruction_data)? {
LoaderV3Instruction::Write { offset, bytes } => {
process_instruction_write(invoke_context, offset, bytes)
}
LoaderV3Instruction::Truncate { offset } => {
process_instruction_truncate(invoke_context, offset)
}
LoaderV3Instruction::Deploy => process_instruction_deploy(invoke_context, use_jit),
LoaderV3Instruction::Retract => process_instruction_retract(invoke_context),
LoaderV3Instruction::TransferAuthority => {
process_instruction_transfer_authority(invoke_context)
}
}
.map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
} else {
let program = instruction_context.try_borrow_last_program_account(transaction_context)?;
if !loader_v3::check_id(program.get_owner()) {
ic_logger_msg!(log_collector, "Program not owned by loader");
return Err(Box::new(InstructionError::InvalidAccountOwner));
}
if program.get_data().is_empty() {
ic_logger_msg!(log_collector, "Program is uninitialized");
return Err(Box::new(InstructionError::InvalidAccountData));
}
let state = get_state(program.get_data())?;
if !state.is_deployed {
ic_logger_msg!(log_collector, "Program is not deployed");
return Err(Box::new(InstructionError::InvalidArgument));
}
let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
let (loaded_program, load_program_metrics) = load_program_from_account(
&invoke_context.feature_set,
invoke_context.get_compute_budget(),
invoke_context.get_log_collector(),
&program,
use_jit,
false,
)?;
load_program_metrics.submit_datapoint(&mut invoke_context.timings);
get_or_create_executor_time.stop();
saturating_add_assign!(
invoke_context.timings.get_or_create_executor_us,
get_or_create_executor_time.as_us()
);
drop(program);
loaded_program.usage_counter.fetch_add(1, Ordering::Relaxed);
match &loaded_program.program {
LoadedProgramType::FailedVerification
| LoadedProgramType::Closed
| LoadedProgramType::DelayVisibility => {
Err(Box::new(InstructionError::InvalidAccountData))
}
LoadedProgramType::Typed(executable) => execute(invoke_context, executable),
_ => Err(Box::new(InstructionError::IncorrectProgramId)),
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
solana_program_runtime::invoke_context::mock_process_instruction,
solana_sdk::{
account::{
create_account_shared_data_for_test, AccountSharedData, ReadableAccount,
WritableAccount,
},
account_utils::StateMut,
instruction::AccountMeta,
slot_history::Slot,
sysvar::{clock, rent},
transaction_context::IndexOfAccount,
},
std::{fs::File, io::Read, path::Path},
};
fn process_instruction(
program_indices: Vec<IndexOfAccount>,
instruction_data: &[u8],
transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
instruction_accounts: &[(IndexOfAccount, bool, bool)],
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
let instruction_accounts = instruction_accounts
.iter()
.map(
|(index_in_transaction, is_signer, is_writable)| AccountMeta {
pubkey: transaction_accounts[*index_in_transaction as usize].0,
is_signer: *is_signer,
is_writable: *is_writable,
},
)
.collect::<Vec<_>>();
mock_process_instruction(
&loader_v3::id(),
program_indices,
instruction_data,
transaction_accounts,
instruction_accounts,
expected_result,
super::process_instruction,
|_invoke_context| {},
)
}
fn load_program_account_from_elf(
is_deployed: bool,
authority_address: Option<Pubkey>,
path: &str,
) -> AccountSharedData {
let path = Path::new("test_elfs/out/").join(path).with_extension("so");
let mut file = File::open(path).expect("file open failed");
let mut elf_bytes = Vec::new();
file.read_to_end(&mut elf_bytes).unwrap();
let rent = rent::Rent::default();
let account_size =
loader_v3::LoaderV3State::program_data_offset().saturating_add(elf_bytes.len());
let mut program_account = AccountSharedData::new(
rent.minimum_balance(account_size),
account_size,
&loader_v3::id(),
);
program_account
.set_state(&loader_v3::LoaderV3State {
slot: 0,
is_deployed,
authority_address,
})
.unwrap();
program_account.data_mut()[loader_v3::LoaderV3State::program_data_offset()..]
.copy_from_slice(&elf_bytes);
program_account
}
fn clock(slot: Slot) -> AccountSharedData {
let clock = clock::Clock {
slot,
..clock::Clock::default()
};
create_account_shared_data_for_test(&clock)
}
fn test_loader_instruction_general_errors(instruction: LoaderV3Instruction) {
let instruction = bincode::serialize(&instruction).unwrap();
let authority_address = Pubkey::new_unique();
let transaction_accounts = vec![
(
Pubkey::new_unique(),
load_program_account_from_elf(true, Some(authority_address), "noop"),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(false, None, "noop"),
),
(
clock::id(),
create_account_shared_data_for_test(&clock::Clock::default()),
),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Error: Missing program account
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[],
Err(InstructionError::NotEnoughAccountKeys),
);
// Error: Missing authority account
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[(0, false, true)],
Err(InstructionError::NotEnoughAccountKeys),
);
// Error: Program not owned by loader
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[(1, false, true), (1, true, false), (2, true, true)],
Err(InstructionError::InvalidAccountOwner),
);
// Error: Program is not writeable
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[(0, false, false), (1, true, false), (2, true, true)],
Err(InstructionError::InvalidArgument),
);
// Error: Authority did not sign
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[(0, false, true), (1, false, false), (2, true, true)],
Err(InstructionError::MissingRequiredSignature),
);
// Error: Program is finalized
process_instruction(
vec![],
&instruction,
transaction_accounts.clone(),
&[(2, false, true), (1, true, false), (0, true, true)],
Err(InstructionError::Immutable),
);
// Error: Incorrect authority provided
process_instruction(
vec![],
&instruction,
transaction_accounts,
&[(0, false, true), (2, true, false), (2, true, true)],
Err(InstructionError::IncorrectAuthority),
);
}
#[test]
fn test_loader_instruction_write() {
let authority_address = Pubkey::new_unique();
let mut transaction_accounts = vec![
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(10000000, 0, &loader_v3::id()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(true, Some(authority_address), "noop"),
),
(
clock::id(),
create_account_shared_data_for_test(&clock::Clock::default()),
),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Initialize account by first write
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 0,
bytes: vec![0, 1, 2, 3],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, true, true)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
loader_v3::LoaderV3State::program_data_offset().saturating_add(4),
);
assert_eq!(accounts[0].lamports(), 1252800);
assert_eq!(
accounts[2].lamports(),
transaction_accounts[2]
.1
.lamports()
.saturating_sub(accounts[0].lamports()),
);
// Error: Program is not writeable
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 0,
bytes: vec![0, 1, 2, 3],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, false), (1, true, false), (2, true, true)],
Err(InstructionError::InvalidArgument),
);
// Error: Authority did not sign
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 0,
bytes: vec![0, 1, 2, 3],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, false, false), (2, true, true)],
Err(InstructionError::MissingRequiredSignature),
);
// Extend account by writing at the end
transaction_accounts[0].1 = accounts[0].clone();
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 4,
bytes: vec![4, 5, 6, 7],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, true, true)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
loader_v3::LoaderV3State::program_data_offset().saturating_add(8),
);
assert_eq!(
accounts[0].lamports(),
transaction_accounts[0].1.lamports().saturating_add(27840),
);
assert_eq!(
accounts[2].lamports(),
transaction_accounts[2].1.lamports().saturating_sub(
accounts[0]
.lamports()
.saturating_sub(transaction_accounts[0].1.lamports()),
),
);
// Overwrite existing data (no payer required)
transaction_accounts[0].1 = accounts[0].clone();
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 2,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
loader_v3::LoaderV3State::program_data_offset().saturating_add(8),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Empty write
transaction_accounts[0].1 = accounts[0].clone();
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 2,
bytes: Vec::new(),
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
loader_v3::LoaderV3State::program_data_offset().saturating_add(8),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Error: Program is not retracted
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 8,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(3, false, true), (1, true, false), (2, true, true)],
Err(InstructionError::InvalidArgument),
);
// Error: Payer did not sign
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 8,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, false, true)],
Err(InstructionError::MissingRequiredSignature),
);
// Error: Payer is not writeable
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 8,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, true, false)],
Err(InstructionError::InvalidArgument),
);
// Error: Write out of bounds
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 9,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, true, true)],
Err(InstructionError::AccountDataTooSmall),
);
// Error: Insufficient funds (Bankrupt payer account)
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 8,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (1, true, true)],
Err(InstructionError::InsufficientFunds),
);
// Error: Insufficient funds (No payer account)
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Write {
offset: 8,
bytes: vec![8, 8, 8, 8],
})
.unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Err(InstructionError::InsufficientFunds),
);
test_loader_instruction_general_errors(LoaderV3Instruction::Write {
offset: 0,
bytes: Vec::new(),
});
}
#[test]
fn test_loader_instruction_truncate() {
let authority_address = Pubkey::new_unique();
let transaction_accounts = vec![
(
Pubkey::new_unique(),
load_program_account_from_elf(false, Some(authority_address), "noop"),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(true, Some(authority_address), "noop"),
),
(
clock::id(),
create_account_shared_data_for_test(&clock::Clock::default()),
),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Cut the end off
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Truncate { offset: 4 }).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, false, true)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
loader_v3::LoaderV3State::program_data_offset().saturating_add(4),
);
assert_eq!(accounts[0].lamports(), 1252800);
assert_eq!(
accounts[2].lamports(),
transaction_accounts[0]
.1
.lamports()
.saturating_sub(accounts[0].lamports()),
);
// Close program account
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Truncate { offset: 0 }).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, false, true)],
Ok(()),
);
assert_eq!(accounts[0].data().len(), 0);
assert_eq!(
accounts[2].lamports(),
transaction_accounts[0]
.1
.lamports()
.saturating_sub(accounts[0].lamports()),
);
// Error: Program is uninitialized
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Truncate { offset: 0 }).unwrap(),
transaction_accounts.clone(),
&[(2, false, true), (1, true, false), (0, false, true)],
Err(InstructionError::InvalidAccountData),
);
// Error: Program is not retracted
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Truncate { offset: 0 }).unwrap(),
transaction_accounts.clone(),
&[(3, false, true), (1, true, false), (2, false, true)],
Err(InstructionError::InvalidArgument),
);
// Error: Truncate out of bounds
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Truncate { offset: 10000 }).unwrap(),
transaction_accounts,
&[(0, false, true), (1, true, false), (2, false, true)],
Err(InstructionError::AccountDataTooSmall),
);
test_loader_instruction_general_errors(LoaderV3Instruction::Truncate { offset: 0 });
}
#[test]
fn test_loader_instruction_deploy() {
let authority_address = Pubkey::new_unique();
let mut transaction_accounts = vec![
(
Pubkey::new_unique(),
load_program_account_from_elf(false, Some(authority_address), "rodata"),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(false, Some(authority_address), "noop"),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(false, Some(authority_address), "invalid"),
),
(clock::id(), clock(1000)),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Deploy from its own data
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
transaction_accounts[0].1 = accounts[0].clone();
transaction_accounts[5].1 = clock(2000);
assert_eq!(
accounts[0].data().len(),
transaction_accounts[0].1.data().len(),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Error: Source program is not writable
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, false, false)],
Err(InstructionError::InvalidArgument),
);
// Error: Source program is not retracted
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(2, false, true), (1, true, false), (0, false, true)],
Err(InstructionError::InvalidArgument),
);
// Redeploy: Retract, then replace data by other source
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Retract).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
transaction_accounts[0].1 = accounts[0].clone();
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, false, true)],
Ok(()),
);
transaction_accounts[0].1 = accounts[0].clone();
assert_eq!(
accounts[0].data().len(),
transaction_accounts[2].1.data().len(),
);
assert_eq!(accounts[2].data().len(), 0,);
assert_eq!(
accounts[2].lamports(),
transaction_accounts[2].1.lamports().saturating_sub(
accounts[0]
.lamports()
.saturating_sub(transaction_accounts[0].1.lamports())
),
);
// Error: Program was deployed recently, cooldown still in effect
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Err(InstructionError::InvalidArgument),
);
transaction_accounts[5].1 = clock(3000);
// Error: Program is uninitialized
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(3, false, true), (1, true, false)],
Err(InstructionError::InvalidAccountData),
);
// Error: Program fails verification
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(4, false, true), (1, true, false)],
Err(InstructionError::InvalidAccountData),
);
// Error: Program is deployed already
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Deploy).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Err(InstructionError::InvalidArgument),
);
test_loader_instruction_general_errors(LoaderV3Instruction::Deploy);
}
#[test]
fn test_loader_instruction_retract() {
let authority_address = Pubkey::new_unique();
let mut transaction_accounts = vec![
(
Pubkey::new_unique(),
load_program_account_from_elf(true, Some(authority_address), "rodata"),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(false, Some(authority_address), "rodata"),
),
(clock::id(), clock(1000)),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Retract program
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Retract).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
transaction_accounts[0].1.data().len(),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Error: Program is uninitialized
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Retract).unwrap(),
transaction_accounts.clone(),
&[(2, false, true), (1, true, false)],
Err(InstructionError::InvalidAccountData),
);
// Error: Program is not deployed
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Retract).unwrap(),
transaction_accounts.clone(),
&[(3, false, true), (1, true, false)],
Err(InstructionError::InvalidArgument),
);
// Error: Program was deployed recently, cooldown still in effect
transaction_accounts[4].1 = clock(0);
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::Retract).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Err(InstructionError::InvalidArgument),
);
test_loader_instruction_general_errors(LoaderV3Instruction::Retract);
}
#[test]
fn test_loader_instruction_transfer_authority() {
let authority_address = Pubkey::new_unique();
let transaction_accounts = vec![
(
Pubkey::new_unique(),
load_program_account_from_elf(true, Some(authority_address), "rodata"),
),
(
authority_address,
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &Pubkey::new_unique()),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
clock::id(),
create_account_shared_data_for_test(&clock::Clock::default()),
),
(
rent::id(),
create_account_shared_data_for_test(&rent::Rent::default()),
),
];
// Transfer authority
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false), (2, true, false)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
transaction_accounts[0].1.data().len(),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Finalize program
let accounts = process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(),
transaction_accounts.clone(),
&[(0, false, true), (1, true, false)],
Ok(()),
);
assert_eq!(
accounts[0].data().len(),
transaction_accounts[0].1.data().len(),
);
assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
// Error: Program is uninitialized
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(),
transaction_accounts.clone(),
&[(3, false, true), (1, true, false), (2, true, false)],
Err(InstructionError::InvalidAccountData),
);
// Error: New authority did not sign
process_instruction(
vec![],
&bincode::serialize(&LoaderV3Instruction::TransferAuthority).unwrap(),
transaction_accounts,
&[(0, false, true), (1, true, false), (2, false, false)],
Err(InstructionError::MissingRequiredSignature),
);
test_loader_instruction_general_errors(LoaderV3Instruction::TransferAuthority);
}
#[test]
fn test_execute_program() {
let program_address = Pubkey::new_unique();
let transaction_accounts = vec![
(
program_address,
load_program_account_from_elf(true, None, "rodata"),
),
(
Pubkey::new_unique(),
AccountSharedData::new(10000000, 32, &program_address),
),
(
Pubkey::new_unique(),
AccountSharedData::new(0, 0, &loader_v3::id()),
),
(
Pubkey::new_unique(),
load_program_account_from_elf(false, None, "rodata"),
),
(
program_address,
load_program_account_from_elf(true, None, "invalid"),
),
];
// Execute program
process_instruction(
vec![0],
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::Custom(42)),
);
// Error: Program not owned by loader
process_instruction(
vec![1],
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::InvalidAccountOwner),
);
// Error: Program is uninitialized
process_instruction(
vec![2],
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::InvalidAccountData),
);
// Error: Program is not deployed
process_instruction(
vec![3],
&[0, 1, 2, 3],
transaction_accounts.clone(),
&[(1, false, true)],
Err(InstructionError::InvalidArgument),
);
// Error: Program fails verification
process_instruction(
vec![4],
&[0, 1, 2, 3],
transaction_accounts,
&[(1, false, true)],
Err(InstructionError::InvalidAccountData),
);
}
}