#![deny(clippy::arithmetic_side_effects)] #![deny(clippy::indexing_slicing)] pub mod serialization; pub mod syscalls; use { solana_measure::measure::Measure, solana_program_runtime::{ ic_logger_msg, ic_msg, invoke_context::{BpfAllocator, InvokeContext, SerializedAccountMetadata, SyscallContext}, loaded_programs::{ LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET, }, log_collector::LogCollector, stable_log, sysvar_cache::get_sysvar_with_account_check, }, solana_rbpf::{ aligned_memory::AlignedMemory, ebpf::{self, HOST_ALIGN, MM_HEAP_START}, elf::Executable, error::EbpfError, memory_region::{AccessType, MemoryCowCallback, MemoryMapping, MemoryRegion}, verifier::RequisiteVerifier, vm::{BuiltinProgram, ContextObject, EbpfVm, ProgramResult}, }, solana_sdk::{ account::WritableAccount, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::Slot, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, feature_set::{ bpf_account_data_direct_mapping, cap_accounts_data_allocations_per_transaction, cap_bpf_program_instruction_accounts, delay_visibility_of_program_deployment, enable_bpf_loader_extend_program_ix, enable_bpf_loader_set_authority_checked_ix, enable_program_redeployment_cooldown, limit_max_instruction_trace_length, native_programs_consume_cu, remove_bpf_loader_incorrect_program_id, FeatureSet, }, instruction::{AccountMeta, InstructionError}, loader_instruction::LoaderInstruction, loader_upgradeable_instruction::UpgradeableLoaderInstruction, native_loader, program_error::{ MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED, MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED, }, program_utils::limited_deserialize, pubkey::Pubkey, saturating_add_assign, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, transaction_context::{ BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext, }, }, std::{ cell::RefCell, mem, rc::Rc, sync::{atomic::Ordering, Arc}, }, syscalls::create_program_runtime_environment_v1, }; pub const DEFAULT_LOADER_COMPUTE_UNITS: u64 = 570; pub const DEPRECATED_LOADER_COMPUTE_UNITS: u64 = 1_140; pub const UPGRADEABLE_LOADER_COMPUTE_UNITS: u64 = 2_370; #[allow(clippy::too_many_arguments)] pub fn load_program_from_bytes( feature_set: &FeatureSet, log_collector: Option>>, load_program_metrics: &mut LoadProgramMetrics, programdata: &[u8], loader_key: &Pubkey, account_size: usize, deployment_slot: Slot, program_runtime_environment: Arc>>, ) -> Result { let effective_slot = if feature_set.is_active(&delay_visibility_of_program_deployment::id()) { deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET) } else { deployment_slot }; let loaded_program = LoadedProgram::new( loader_key, program_runtime_environment, deployment_slot, effective_slot, None, programdata, account_size, load_program_metrics, ) .map_err(|err| { ic_logger_msg!(log_collector, "{}", err); InstructionError::InvalidAccountData })?; Ok(loaded_program) } macro_rules! deploy_program { ($invoke_context:expr, $program_id:expr, $loader_key:expr, $account_size:expr, $slot:expr, $drop:expr, $new_programdata:expr $(,)?) => {{ let mut load_program_metrics = LoadProgramMetrics::default(); let mut register_syscalls_time = Measure::start("register_syscalls_time"); let program_runtime_environment = create_program_runtime_environment_v1( &$invoke_context.feature_set, $invoke_context.get_compute_budget(), true, /* deployment */ false, /* debugging_features */ ).map_err(|e| { ic_msg!($invoke_context, "Failed to register syscalls: {}", e); InstructionError::ProgramEnvironmentSetupFailure })?; register_syscalls_time.stop(); load_program_metrics.register_syscalls_us = register_syscalls_time.as_us(); let executor = load_program_from_bytes( &$invoke_context.feature_set, $invoke_context.get_log_collector(), &mut load_program_metrics, $new_programdata, $loader_key, $account_size, $slot, Arc::new(program_runtime_environment), )?; if let Some(old_entry) = $invoke_context.find_program_in_cache(&$program_id) { executor.tx_usage_counter.store( old_entry.tx_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed ); executor.ix_usage_counter.store( old_entry.ix_usage_counter.load(Ordering::Relaxed), Ordering::Relaxed ); } $drop load_program_metrics.program_id = $program_id.to_string(); load_program_metrics.submit_datapoint(&mut $invoke_context.timings); $invoke_context.programs_modified_by_tx.replenish($program_id, Arc::new(executor)); }}; } fn write_program_data( program_data_offset: usize, bytes: &[u8], invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { 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 data = program.get_data_mut()?; let write_offset = program_data_offset.saturating_add(bytes.len()); if data.len() < write_offset { ic_msg!( invoke_context, "Write overflow: {} < {}", data.len(), write_offset, ); return Err(InstructionError::AccountDataTooSmall); } data.get_mut(program_data_offset..write_offset) .ok_or(InstructionError::AccountDataTooSmall)? .copy_from_slice(bytes); Ok(()) } pub fn check_loader_id(id: &Pubkey) -> bool { bpf_loader::check_id(id) || bpf_loader_deprecated::check_id(id) || bpf_loader_upgradeable::check_id(id) } /// Only used in macro, do not use directly! pub fn calculate_heap_cost(heap_size: u64, heap_cost: u64, enable_rounding_fix: bool) -> u64 { const KIBIBYTE: u64 = 1024; const PAGE_SIZE_KB: u64 = 32; let mut rounded_heap_size = heap_size; if enable_rounding_fix { rounded_heap_size = rounded_heap_size .saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1)); } rounded_heap_size .checked_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE)) .expect("PAGE_SIZE_KB * KIBIBYTE > 0") .saturating_sub(1) .saturating_mul(heap_cost) } /// Only used in macro, do not use directly! pub fn create_vm<'a, 'b>( program: &'a Executable>, regions: Vec, accounts_metadata: Vec, invoke_context: &'a mut InvokeContext<'b>, stack: &mut AlignedMemory, heap: &mut AlignedMemory, ) -> Result>, Box> { let stack_size = stack.len(); let heap_size = heap.len(); let accounts = Rc::clone(invoke_context.transaction_context.accounts()); let memory_mapping = create_memory_mapping( program, stack, heap, regions, Some(Box::new(move |index_in_transaction| { // The two calls below can't really fail. If they fail because of a bug, // whatever is writing will trigger an EbpfError::AccessViolation like // if the region was readonly, and the transaction will fail gracefully. let mut account = accounts .try_borrow_mut(index_in_transaction as IndexOfAccount) .map_err(|_| ())?; accounts .touch(index_in_transaction as IndexOfAccount) .map_err(|_| ())?; if account.is_shared() { // See BorrowedAccount::make_data_mut() as to why we reserve extra // MAX_PERMITTED_DATA_INCREASE bytes here. account.reserve(MAX_PERMITTED_DATA_INCREASE); } Ok(account.data_as_mut_slice().as_mut_ptr() as u64) })), )?; invoke_context.set_syscall_context(SyscallContext { allocator: BpfAllocator::new(heap_size as u64), accounts_metadata, trace_log: Vec::new(), })?; Ok(EbpfVm::new( program.get_config(), program.get_sbpf_version(), invoke_context, memory_mapping, stack_size, )) } /// Create the SBF virtual machine #[macro_export] macro_rules! create_vm { ($vm:ident, $program:expr, $regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => { let invoke_context = &*$invoke_context; let stack_size = $program.get_config().stack_size(); let heap_size = invoke_context .get_compute_budget() .heap_size .unwrap_or(solana_sdk::entrypoint::HEAP_LENGTH); let round_up_heap_size = invoke_context .feature_set .is_active(&solana_sdk::feature_set::round_up_heap_size::id()); let mut heap_cost_result = invoke_context.consume_checked($crate::calculate_heap_cost( heap_size as u64, invoke_context.get_compute_budget().heap_cost, round_up_heap_size, )); if !round_up_heap_size { heap_cost_result = Ok(()); } let mut allocations = None; let $vm = heap_cost_result.and_then(|_| { let mut stack = solana_rbpf::aligned_memory::AlignedMemory::< { solana_rbpf::ebpf::HOST_ALIGN }, >::zero_filled(stack_size); let mut heap = solana_rbpf::aligned_memory::AlignedMemory::< { solana_rbpf::ebpf::HOST_ALIGN }, >::zero_filled(heap_size); let vm = $crate::create_vm( $program, $regions, $accounts_metadata, $invoke_context, &mut stack, &mut heap, ); allocations = Some((stack, heap)); vm }); }; } #[macro_export] macro_rules! mock_create_vm { ($vm:ident, $additional_regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => { let loader = std::sync::Arc::new(BuiltinProgram::new_loader( solana_rbpf::vm::Config::default(), )); let function_registry = solana_rbpf::vm::FunctionRegistry::default(); let executable = solana_rbpf::elf::Executable::< solana_rbpf::verifier::TautologyVerifier, InvokeContext, >::from_text_bytes( &[0x95, 0, 0, 0, 0, 0, 0, 0], loader, SBPFVersion::V2, function_registry, ) .unwrap(); let verified_executable = solana_rbpf::elf::Executable::verified(executable).unwrap(); $crate::create_vm!( $vm, &verified_executable, $additional_regions, $accounts_metadata, $invoke_context, ); }; } fn create_memory_mapping<'a, 'b, C: ContextObject>( executable: &'a Executable, stack: &'b mut AlignedMemory<{ HOST_ALIGN }>, heap: &'b mut AlignedMemory<{ HOST_ALIGN }>, additional_regions: Vec, cow_cb: Option, ) -> Result, Box> { let config = executable.get_config(); let sbpf_version = executable.get_sbpf_version(); let regions: Vec = vec![ executable.get_ro_region(), MemoryRegion::new_writable_gapped( stack.as_slice_mut(), ebpf::MM_STACK_START, if !sbpf_version.dynamic_stack_frames() && config.enable_stack_frame_gaps { config.stack_frame_size as u64 } else { 0 }, ), MemoryRegion::new_writable(heap.as_slice_mut(), MM_HEAP_START), ] .into_iter() .chain(additional_regions) .collect(); Ok(if let Some(cow_cb) = cow_cb { MemoryMapping::new_with_cow(regions, cow_cb, config, sbpf_version)? } else { MemoryMapping::new(regions, config, sbpf_version)? }) } pub fn process_instruction( invoke_context: &mut InvokeContext, _arg0: u64, _arg1: u64, _arg2: u64, _arg3: u64, _arg4: u64, _memory_mapping: &mut MemoryMapping, result: &mut ProgramResult, ) { *result = process_instruction_inner(invoke_context).into(); } fn process_instruction_inner( invoke_context: &mut InvokeContext, ) -> Result> { let log_collector = invoke_context.get_log_collector(); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; if !invoke_context .feature_set .is_active(&remove_bpf_loader_incorrect_program_id::id()) { fn get_index_in_transaction( instruction_context: &InstructionContext, index_in_instruction: IndexOfAccount, ) -> Result { if index_in_instruction < instruction_context.get_number_of_program_accounts() { instruction_context .get_index_of_program_account_in_transaction(index_in_instruction) } else { instruction_context.get_index_of_instruction_account_in_transaction( index_in_instruction .saturating_sub(instruction_context.get_number_of_program_accounts()), ) } } fn try_borrow_account<'a>( transaction_context: &'a TransactionContext, instruction_context: &'a InstructionContext, index_in_instruction: IndexOfAccount, ) -> Result, InstructionError> { if index_in_instruction < instruction_context.get_number_of_program_accounts() { instruction_context .try_borrow_program_account(transaction_context, index_in_instruction) } else { instruction_context.try_borrow_instruction_account( transaction_context, index_in_instruction .saturating_sub(instruction_context.get_number_of_program_accounts()), ) } } let first_instruction_account = { let borrowed_root_account = instruction_context.try_borrow_program_account(transaction_context, 0)?; let owner_id = borrowed_root_account.get_owner(); if native_loader::check_id(owner_id) { 1 } else { 0 } }; let first_account_key = transaction_context.get_key_of_account_at_index( get_index_in_transaction(instruction_context, first_instruction_account)?, )?; let second_account_key = get_index_in_transaction( instruction_context, first_instruction_account.saturating_add(1), ) .and_then(|index_in_transaction| { transaction_context.get_key_of_account_at_index(index_in_transaction) }); let program_id = instruction_context.get_last_program_key(transaction_context)?; let program_account_index = if first_account_key == program_id { first_instruction_account } else if second_account_key .map(|key| key == program_id) .unwrap_or(false) { first_instruction_account.saturating_add(1) } else { let first_account = try_borrow_account( transaction_context, instruction_context, first_instruction_account, )?; if first_account.is_executable() { ic_logger_msg!(log_collector, "BPF loader is executable"); return Err(Box::new(InstructionError::IncorrectProgramId)); } first_instruction_account }; let program = try_borrow_account( transaction_context, instruction_context, program_account_index, )?; if program.is_executable() && !check_loader_id(program.get_owner()) { ic_logger_msg!( log_collector, "Executable account not owned by the BPF loader" ); return Err(Box::new(InstructionError::IncorrectProgramId)); } } let program_account = instruction_context.try_borrow_last_program_account(transaction_context)?; // Consume compute units if feature `native_programs_consume_cu` is activated let native_programs_consume_cu = invoke_context .feature_set .is_active(&native_programs_consume_cu::id()); // Program Management Instruction if native_loader::check_id(program_account.get_owner()) { drop(program_account); let program_id = instruction_context.get_last_program_key(transaction_context)?; return if bpf_loader_upgradeable::check_id(program_id) { if native_programs_consume_cu { invoke_context.consume_checked(UPGRADEABLE_LOADER_COMPUTE_UNITS)?; } process_loader_upgradeable_instruction(invoke_context) } else if bpf_loader::check_id(program_id) { if native_programs_consume_cu { invoke_context.consume_checked(DEFAULT_LOADER_COMPUTE_UNITS)?; } process_loader_instruction(invoke_context) } else if bpf_loader_deprecated::check_id(program_id) { if native_programs_consume_cu { invoke_context.consume_checked(DEPRECATED_LOADER_COMPUTE_UNITS)?; } ic_logger_msg!(log_collector, "Deprecated loader is no longer supported"); Err(InstructionError::UnsupportedProgramId) } else { ic_logger_msg!(log_collector, "Invalid BPF loader id"); Err(InstructionError::IncorrectProgramId) } .map(|_| 0) .map_err(|error| Box::new(error) as Box); } // Program Invocation if !program_account.is_executable() { ic_logger_msg!(log_collector, "Program is not executable"); return Err(Box::new(InstructionError::IncorrectProgramId)); } let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time"); let executor = invoke_context .find_program_in_cache(program_account.get_key()) .ok_or(InstructionError::InvalidAccountData)?; if executor.is_tombstone() { return Err(Box::new(InstructionError::InvalidAccountData)); } drop(program_account); get_or_create_executor_time.stop(); saturating_add_assign!( invoke_context.timings.get_or_create_executor_us, get_or_create_executor_time.as_us() ); executor.ix_usage_counter.fetch_add(1, Ordering::Relaxed); match &executor.program { LoadedProgramType::FailedVerification(_) | LoadedProgramType::Closed | LoadedProgramType::DelayVisibility => { Err(Box::new(InstructionError::InvalidAccountData) as Box) } LoadedProgramType::LegacyV0(executable) => execute(executable, invoke_context), LoadedProgramType::LegacyV1(executable) => execute(executable, invoke_context), _ => Err(Box::new(InstructionError::IncorrectProgramId) as Box), } .map(|_| 0) } fn process_loader_upgradeable_instruction( 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 instruction_data = instruction_context.get_instruction_data(); let program_id = instruction_context.get_last_program_key(transaction_context)?; match limited_deserialize(instruction_data)? { UpgradeableLoaderInstruction::InitializeBuffer => { instruction_context.check_number_of_instruction_accounts(2)?; let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; if UpgradeableLoaderState::Uninitialized != buffer.get_state()? { ic_logger_msg!(log_collector, "Buffer account already initialized"); return Err(InstructionError::AccountAlreadyInitialized); } let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?); buffer.set_state(&UpgradeableLoaderState::Buffer { authority_address: authority_key, })?; } UpgradeableLoaderInstruction::Write { offset, bytes } => { instruction_context.check_number_of_instruction_accounts(2)?; let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { if authority_address.is_none() { ic_logger_msg!(log_collector, "Buffer is immutable"); return Err(InstructionError::Immutable); // TODO better error code } let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?); if authority_address != authority_key { ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Buffer authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidAccountData); } drop(buffer); write_program_data( UpgradeableLoaderState::size_of_buffer_metadata().saturating_add(offset as usize), &bytes, invoke_context, )?; } UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => { instruction_context.check_number_of_instruction_accounts(4)?; let payer_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(0)?, )?; let programdata_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?; let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?; instruction_context.check_number_of_instruction_accounts(8)?; let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(7)?, )?); // Verify Program account let program = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; if UpgradeableLoaderState::Uninitialized != program.get_state()? { ic_logger_msg!(log_collector, "Program account already initialized"); return Err(InstructionError::AccountAlreadyInitialized); } if program.get_data().len() < UpgradeableLoaderState::size_of_program() { ic_logger_msg!(log_collector, "Program account too small"); return Err(InstructionError::AccountDataTooSmall); } if program.get_lamports() < rent.minimum_balance(program.get_data().len()) { ic_logger_msg!(log_collector, "Program account not rent-exempt"); return Err(InstructionError::ExecutableAccountNotRentExempt); } let new_program_id = *program.get_key(); drop(program); // Verify Buffer account let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { if authority_address != authority_key { ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(7)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidArgument); } let buffer_key = *buffer.get_key(); let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata(); let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset); let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); let programdata_len = UpgradeableLoaderState::size_of_programdata(max_data_len); if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata() || buffer_data_len == 0 { ic_logger_msg!(log_collector, "Buffer account too small"); return Err(InstructionError::InvalidAccountData); } drop(buffer); if max_data_len < buffer_data_len { ic_logger_msg!( log_collector, "Max data length is too small to hold Buffer data" ); return Err(InstructionError::AccountDataTooSmall); } if programdata_len > MAX_PERMITTED_DATA_LENGTH as usize { ic_logger_msg!(log_collector, "Max data length is too large"); return Err(InstructionError::InvalidArgument); } // Create ProgramData account let (derived_address, bump_seed) = Pubkey::find_program_address(&[new_program_id.as_ref()], program_id); if derived_address != programdata_key { ic_logger_msg!(log_collector, "ProgramData address is not derived"); return Err(InstructionError::InvalidArgument); } // Drain the Buffer account to payer before paying for programdata account { let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; let mut payer = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; payer.checked_add_lamports(buffer.get_lamports())?; buffer.set_lamports(0)?; } let owner_id = *program_id; let mut instruction = system_instruction::create_account( &payer_key, &programdata_key, 1.max(rent.minimum_balance(programdata_len)), programdata_len as u64, program_id, ); // pass an extra account to avoid the overly strict UnbalancedInstruction error instruction .accounts .push(AccountMeta::new(buffer_key, false)); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let caller_program_id = instruction_context.get_last_program_key(transaction_context)?; let signers = [[new_program_id.as_ref(), &[bump_seed]]] .iter() .map(|seeds| Pubkey::create_program_address(seeds, caller_program_id)) .collect::, solana_sdk::pubkey::PubkeyError>>()?; invoke_context.native_invoke(instruction.into(), signers.as_slice())?; // Load and verify the program bits let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; deploy_program!( invoke_context, new_program_id, &owner_id, UpgradeableLoaderState::size_of_program().saturating_add(programdata_len), clock.slot, { drop(buffer); }, buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; // Update the ProgramData account and record the program bits { let mut programdata = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; programdata.set_state(&UpgradeableLoaderState::ProgramData { slot: clock.slot, upgrade_authority_address: authority_key, })?; let dst_slice = programdata .get_data_mut()? .get_mut( programdata_data_offset ..programdata_data_offset.saturating_add(buffer_data_len), ) .ok_or(InstructionError::AccountDataTooSmall)?; let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; let src_slice = buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?; dst_slice.copy_from_slice(src_slice); if invoke_context .feature_set .is_active(&enable_program_redeployment_cooldown::id()) { buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?; } } // Update the Program account let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; program.set_state(&UpgradeableLoaderState::Program { programdata_address: programdata_key, })?; program.set_executable(true)?; drop(program); ic_logger_msg!(log_collector, "Deployed program {:?}", new_program_id); } UpgradeableLoaderInstruction::Upgrade => { instruction_context.check_number_of_instruction_accounts(3)?; let programdata_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(0)?, )?; let rent = get_sysvar_with_account_check::rent(invoke_context, instruction_context, 4)?; let clock = get_sysvar_with_account_check::clock(invoke_context, instruction_context, 5)?; instruction_context.check_number_of_instruction_accounts(7)?; let authority_key = Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(6)?, )?); // Verify Program account let program = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; if !program.is_executable() { ic_logger_msg!(log_collector, "Program account not executable"); return Err(InstructionError::AccountNotExecutable); } if !program.is_writable() { ic_logger_msg!(log_collector, "Program account not writeable"); return Err(InstructionError::InvalidArgument); } if program.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::IncorrectProgramId); } if let UpgradeableLoaderState::Program { programdata_address, } = program.get_state()? { if programdata_address != programdata_key { ic_logger_msg!(log_collector, "Program and ProgramData account mismatch"); return Err(InstructionError::InvalidArgument); } } else { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidAccountData); } let new_program_id = *program.get_key(); drop(program); // Verify Buffer account let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? { if authority_address != authority_key { ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(6)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid Buffer account"); return Err(InstructionError::InvalidArgument); } let buffer_lamports = buffer.get_lamports(); let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata(); let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset); if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata() || buffer_data_len == 0 { ic_logger_msg!(log_collector, "Buffer account too small"); return Err(InstructionError::InvalidAccountData); } drop(buffer); // Verify ProgramData account let programdata = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); let programdata_balance_required = 1.max(rent.minimum_balance(programdata.get_data().len())); if programdata.get_data().len() < UpgradeableLoaderState::size_of_programdata(buffer_data_len) { ic_logger_msg!(log_collector, "ProgramData account not large enough"); return Err(InstructionError::AccountDataTooSmall); } if programdata.get_lamports().saturating_add(buffer_lamports) < programdata_balance_required { ic_logger_msg!( log_collector, "Buffer account balance too low to fund upgrade" ); return Err(InstructionError::InsufficientFunds); } if let UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } = programdata.get_state()? { if invoke_context .feature_set .is_active(&enable_program_redeployment_cooldown::id()) && clock.slot == slot { ic_logger_msg!(log_collector, "Program was deployed in this block already"); return Err(InstructionError::InvalidArgument); } if upgrade_authority_address.is_none() { ic_logger_msg!(log_collector, "Program not upgradeable"); return Err(InstructionError::Immutable); } if upgrade_authority_address != authority_key { ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(6)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } } else { ic_logger_msg!(log_collector, "Invalid ProgramData account"); return Err(InstructionError::InvalidAccountData); }; let programdata_len = programdata.get_data().len(); drop(programdata); // Load and verify the program bits let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; deploy_program!( invoke_context, new_program_id, program_id, UpgradeableLoaderState::size_of_program().saturating_add(programdata_len), clock.slot, { drop(buffer); }, buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; // Update the ProgramData account, record the upgraded data, and zero // the rest let mut programdata = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; { programdata.set_state(&UpgradeableLoaderState::ProgramData { slot: clock.slot, upgrade_authority_address: authority_key, })?; let dst_slice = programdata .get_data_mut()? .get_mut( programdata_data_offset ..programdata_data_offset.saturating_add(buffer_data_len), ) .ok_or(InstructionError::AccountDataTooSmall)?; let buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; let src_slice = buffer .get_data() .get(buffer_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?; dst_slice.copy_from_slice(src_slice); } programdata .get_data_mut()? .get_mut(programdata_data_offset.saturating_add(buffer_data_len)..) .ok_or(InstructionError::AccountDataTooSmall)? .fill(0); // Fund ProgramData to rent-exemption, spill the rest let mut buffer = instruction_context.try_borrow_instruction_account(transaction_context, 2)?; let mut spill = instruction_context.try_borrow_instruction_account(transaction_context, 3)?; spill.checked_add_lamports( programdata .get_lamports() .saturating_add(buffer_lamports) .saturating_sub(programdata_balance_required), )?; buffer.set_lamports(0)?; programdata.set_lamports(programdata_balance_required)?; if invoke_context .feature_set .is_active(&enable_program_redeployment_cooldown::id()) { buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?; } ic_logger_msg!(log_collector, "Upgraded program {:?}", new_program_id); } UpgradeableLoaderInstruction::SetAuthority => { instruction_context.check_number_of_instruction_accounts(2)?; let mut account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let present_authority_key = transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?; let new_authority = instruction_context .get_index_of_instruction_account_in_transaction(2) .and_then(|index_in_transaction| { transaction_context.get_key_of_account_at_index(index_in_transaction) }) .ok(); match account.get_state()? { UpgradeableLoaderState::Buffer { authority_address } => { if new_authority.is_none() { ic_logger_msg!(log_collector, "Buffer authority is not optional"); return Err(InstructionError::IncorrectAuthority); } if authority_address.is_none() { ic_logger_msg!(log_collector, "Buffer is immutable"); return Err(InstructionError::Immutable); } if authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Buffer authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::Buffer { authority_address: new_authority.cloned(), })?; } UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } => { if upgrade_authority_address.is_none() { ic_logger_msg!(log_collector, "Program not upgradeable"); return Err(InstructionError::Immutable); } if upgrade_authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: new_authority.cloned(), })?; } _ => { ic_logger_msg!(log_collector, "Account does not support authorities"); return Err(InstructionError::InvalidArgument); } } ic_logger_msg!(log_collector, "New authority {:?}", new_authority); } UpgradeableLoaderInstruction::SetAuthorityChecked => { if !invoke_context .feature_set .is_active(&enable_bpf_loader_set_authority_checked_ix::id()) { return Err(InstructionError::InvalidInstructionData); } instruction_context.check_number_of_instruction_accounts(3)?; let mut account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let present_authority_key = transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(1)?, )?; let new_authority_key = transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(2)?, )?; match account.get_state()? { UpgradeableLoaderState::Buffer { authority_address } => { if authority_address.is_none() { ic_logger_msg!(log_collector, "Buffer is immutable"); return Err(InstructionError::Immutable); } if authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect buffer authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Buffer authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } if !instruction_context.is_instruction_account_signer(2)? { ic_logger_msg!(log_collector, "New authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(*new_authority_key), })?; } UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } => { if upgrade_authority_address.is_none() { ic_logger_msg!(log_collector, "Program not upgradeable"); return Err(InstructionError::Immutable); } if upgrade_authority_address != Some(*present_authority_key) { ic_logger_msg!(log_collector, "Incorrect upgrade authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(1)? { ic_logger_msg!(log_collector, "Upgrade authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } if !instruction_context.is_instruction_account_signer(2)? { ic_logger_msg!(log_collector, "New authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } account.set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: Some(*new_authority_key), })?; } _ => { ic_logger_msg!(log_collector, "Account does not support authorities"); return Err(InstructionError::InvalidArgument); } } ic_logger_msg!(log_collector, "New authority {:?}", new_authority_key); } UpgradeableLoaderInstruction::Close => { instruction_context.check_number_of_instruction_accounts(2)?; if instruction_context.get_index_of_instruction_account_in_transaction(0)? == instruction_context.get_index_of_instruction_account_in_transaction(1)? { ic_logger_msg!( log_collector, "Recipient is the same as the account being closed" ); return Err(InstructionError::InvalidArgument); } let mut close_account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let close_key = *close_account.get_key(); let close_account_state = close_account.get_state()?; if invoke_context .feature_set .is_active(&enable_program_redeployment_cooldown::id()) { close_account.set_data_length(UpgradeableLoaderState::size_of_uninitialized())?; } match close_account_state { UpgradeableLoaderState::Uninitialized => { let mut recipient_account = instruction_context .try_borrow_instruction_account(transaction_context, 1)?; recipient_account.checked_add_lamports(close_account.get_lamports())?; close_account.set_lamports(0)?; ic_logger_msg!(log_collector, "Closed Uninitialized {}", close_key); } UpgradeableLoaderState::Buffer { authority_address } => { instruction_context.check_number_of_instruction_accounts(3)?; drop(close_account); common_close_account( &authority_address, transaction_context, instruction_context, &log_collector, )?; ic_logger_msg!(log_collector, "Closed Buffer {}", close_key); } UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: authority_address, } => { instruction_context.check_number_of_instruction_accounts(4)?; drop(close_account); let program_account = instruction_context .try_borrow_instruction_account(transaction_context, 3)?; let program_key = *program_account.get_key(); if !program_account.is_writable() { ic_logger_msg!(log_collector, "Program account is not writable"); return Err(InstructionError::InvalidArgument); } if program_account.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::IncorrectProgramId); } if invoke_context .feature_set .is_active(&enable_program_redeployment_cooldown::id()) { let clock = invoke_context.get_sysvar_cache().get_clock()?; if clock.slot == slot { ic_logger_msg!( log_collector, "Program was deployed in this block already" ); return Err(InstructionError::InvalidArgument); } } match program_account.get_state()? { UpgradeableLoaderState::Program { programdata_address, } => { if programdata_address != close_key { ic_logger_msg!( log_collector, "ProgramData account does not match ProgramData account" ); return Err(InstructionError::InvalidArgument); } drop(program_account); common_close_account( &authority_address, transaction_context, instruction_context, &log_collector, )?; let clock = invoke_context.get_sysvar_cache().get_clock()?; if invoke_context .feature_set .is_active(&delay_visibility_of_program_deployment::id()) { invoke_context.programs_modified_by_tx.replenish( program_key, Arc::new(LoadedProgram::new_tombstone( clock.slot, LoadedProgramType::Closed, )), ); } else { invoke_context .programs_updated_only_for_global_cache .replenish( program_key, Arc::new(LoadedProgram::new_tombstone( clock.slot, LoadedProgramType::Closed, )), ); } } _ => { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidArgument); } } ic_logger_msg!(log_collector, "Closed Program {}", program_key); } _ => { ic_logger_msg!(log_collector, "Account does not support closing"); return Err(InstructionError::InvalidArgument); } } } UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => { if !invoke_context .feature_set .is_active(&enable_bpf_loader_extend_program_ix::ID) { return Err(InstructionError::InvalidInstructionData); } if additional_bytes == 0 { ic_logger_msg!(log_collector, "Additional bytes must be greater than 0"); return Err(InstructionError::InvalidInstructionData); } const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0; const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1; #[allow(dead_code)] // System program is only required when a CPI is performed const OPTIONAL_SYSTEM_PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 2; const OPTIONAL_PAYER_ACCOUNT_INDEX: IndexOfAccount = 3; let programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; let programdata_key = *programdata_account.get_key(); if program_id != programdata_account.get_owner() { ic_logger_msg!(log_collector, "ProgramData owner is invalid"); return Err(InstructionError::InvalidAccountOwner); } if !programdata_account.is_writable() { ic_logger_msg!(log_collector, "ProgramData is not writable"); return Err(InstructionError::InvalidArgument); } let program_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_ACCOUNT_INDEX)?; if !program_account.is_writable() { ic_logger_msg!(log_collector, "Program account is not writable"); return Err(InstructionError::InvalidArgument); } if program_account.get_owner() != program_id { ic_logger_msg!(log_collector, "Program account not owned by loader"); return Err(InstructionError::InvalidAccountOwner); } let program_key = *program_account.get_key(); match program_account.get_state()? { UpgradeableLoaderState::Program { programdata_address, } => { if programdata_address != programdata_key { ic_logger_msg!( log_collector, "Program account does not match ProgramData account" ); return Err(InstructionError::InvalidArgument); } } _ => { ic_logger_msg!(log_collector, "Invalid Program account"); return Err(InstructionError::InvalidAccountData); } } drop(program_account); let old_len = programdata_account.get_data().len(); let new_len = old_len.saturating_add(additional_bytes as usize); if new_len > MAX_PERMITTED_DATA_LENGTH as usize { ic_logger_msg!( log_collector, "Extended ProgramData length of {} bytes exceeds max account data length of {} bytes", new_len, MAX_PERMITTED_DATA_LENGTH ); return Err(InstructionError::InvalidRealloc); } let clock_slot = invoke_context .get_sysvar_cache() .get_clock() .map(|clock| clock.slot)?; let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address, } = programdata_account.get_state()? { if clock_slot == slot { ic_logger_msg!(log_collector, "Program was extended in this block already"); return Err(InstructionError::InvalidArgument); } if upgrade_authority_address.is_none() { ic_logger_msg!( log_collector, "Cannot extend ProgramData accounts that are not upgradeable" ); return Err(InstructionError::Immutable); } upgrade_authority_address } else { ic_logger_msg!(log_collector, "ProgramData state is invalid"); return Err(InstructionError::InvalidAccountData); }; let required_payment = { let balance = programdata_account.get_lamports(); let rent = invoke_context.get_sysvar_cache().get_rent()?; let min_balance = rent.minimum_balance(new_len).max(1); min_balance.saturating_sub(balance) }; // Borrowed accounts need to be dropped before native_invoke drop(programdata_account); // Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context let program_id = *program_id; if required_payment > 0 { let payer_key = *transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction( OPTIONAL_PAYER_ACCOUNT_INDEX, )?, )?; invoke_context.native_invoke( system_instruction::transfer(&payer_key, &programdata_key, required_payment) .into(), &[], )?; } let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let mut programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; programdata_account.set_data_length(new_len)?; let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata(); deploy_program!( invoke_context, program_key, &program_id, UpgradeableLoaderState::size_of_program().saturating_add(new_len), clock_slot, { drop(programdata_account); }, programdata_account .get_data() .get(programdata_data_offset..) .ok_or(InstructionError::AccountDataTooSmall)?, ); let mut programdata_account = instruction_context .try_borrow_instruction_account(transaction_context, PROGRAM_DATA_ACCOUNT_INDEX)?; programdata_account.set_state(&UpgradeableLoaderState::ProgramData { slot: clock_slot, upgrade_authority_address, })?; ic_logger_msg!( log_collector, "Extended ProgramData account by {} bytes", additional_bytes ); } } Ok(()) } fn common_close_account( authority_address: &Option, transaction_context: &TransactionContext, instruction_context: &InstructionContext, log_collector: &Option>>, ) -> Result<(), InstructionError> { if authority_address.is_none() { ic_logger_msg!(log_collector, "Account is immutable"); return Err(InstructionError::Immutable); } if *authority_address != Some(*transaction_context.get_key_of_account_at_index( instruction_context.get_index_of_instruction_account_in_transaction(2)?, )?) { ic_logger_msg!(log_collector, "Incorrect authority provided"); return Err(InstructionError::IncorrectAuthority); } if !instruction_context.is_instruction_account_signer(2)? { ic_logger_msg!(log_collector, "Authority did not sign"); return Err(InstructionError::MissingRequiredSignature); } let mut close_account = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; let mut recipient_account = instruction_context.try_borrow_instruction_account(transaction_context, 1)?; recipient_account.checked_add_lamports(close_account.get_lamports())?; close_account.set_lamports(0)?; close_account.set_state(&UpgradeableLoaderState::Uninitialized)?; Ok(()) } fn process_loader_instruction(invoke_context: &mut InvokeContext) -> Result<(), InstructionError> { 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)?; let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?; if program.get_owner() != program_id { ic_msg!( invoke_context, "Executable account not owned by the BPF loader" ); return Err(InstructionError::IncorrectProgramId); } let is_program_signer = program.is_signer(); match limited_deserialize(instruction_data)? { LoaderInstruction::Write { offset, bytes } => { if !is_program_signer { ic_msg!(invoke_context, "Program account did not sign"); return Err(InstructionError::MissingRequiredSignature); } drop(program); write_program_data(offset as usize, &bytes, invoke_context)?; } LoaderInstruction::Finalize => { if !is_program_signer { ic_msg!(invoke_context, "key[0] did not sign the transaction"); return Err(InstructionError::MissingRequiredSignature); } deploy_program!( invoke_context, *program.get_key(), program.get_owner(), program.get_data().len(), invoke_context.programs_loaded_for_tx_batch.slot(), {}, program.get_data(), ); program.set_executable(true)?; ic_msg!(invoke_context, "Finalized account {:?}", program.get_key()); } } Ok(()) } fn execute<'a, 'b: 'a>( executable: &'a Executable>, invoke_context: &'a mut InvokeContext<'b>, ) -> Result<(), Box> { // 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. let executable = unsafe { mem::transmute::<_, &'a Executable>>(executable) }; 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 (program_id, is_loader_deprecated) = { let program_account = instruction_context.try_borrow_last_program_account(transaction_context)?; ( *program_account.get_key(), *program_account.get_owner() == bpf_loader_deprecated::id(), ) }; #[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 = executable.get_compiled_program().is_some(); let direct_mapping = invoke_context .feature_set .is_active(&bpf_account_data_direct_mapping::id()); let mut serialize_time = Measure::start("serialize"); let (parameter_bytes, regions, accounts_metadata) = serialization::serialize_parameters( invoke_context.transaction_context, instruction_context, invoke_context .feature_set .is_active(&cap_bpf_program_instruction_accounts::ID), !direct_mapping, )?; serialize_time.stop(); // save the account addresses so in case we hit an AccessViolation error we // can map to a more specific error let account_region_addrs = accounts_metadata .iter() .map(|m| { let vm_end = m .vm_data_addr .saturating_add(m.original_data_len as u64) .saturating_add(if !is_loader_deprecated { MAX_PERMITTED_DATA_INCREASE as u64 } else { 0 }); m.vm_data_addr..vm_end }) .collect::>(); let mut create_vm_time = Measure::start("create_vm"); let mut execute_time; let execution_result = { let compute_meter_prev = invoke_context.get_remaining(); create_vm!(vm, executable, regions, accounts_metadata, invoke_context,); let mut vm = match vm { Ok(info) => info, Err(e) => { ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", e); return Err(Box::new(InstructionError::ProgramEnvironmentSetupFailure)); } }; create_vm_time.stop(); execute_time = Measure::start("execute"); let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit); drop(vm); ic_logger_msg!( log_collector, "Program {} consumed {} of {} compute units", &program_id, compute_units_consumed, compute_meter_prev ); let (_returned_from_program_id, return_data) = invoke_context.transaction_context.get_return_data(); if !return_data.is_empty() { stable_log::program_return(&log_collector, &program_id, return_data); } match result { ProgramResult::Ok(status) if status != SUCCESS => { let error: InstructionError = if (status == MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED && !invoke_context .feature_set .is_active(&cap_accounts_data_allocations_per_transaction::id())) || (status == MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED && !invoke_context .feature_set .is_active(&limit_max_instruction_trace_length::id())) { // Until the cap_accounts_data_allocations_per_transaction feature is // enabled, map the `MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED` error to `InvalidError`. // Until the limit_max_instruction_trace_length feature is // enabled, map the `MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED` error to `InvalidError`. InstructionError::InvalidError } else { status.into() }; Err(Box::new(error) as Box) } ProgramResult::Err(mut error) => { if direct_mapping { if let Some(EbpfError::AccessViolation( _pc, AccessType::Store, address, _size, _section_name, )) = error.downcast_ref() { // If direct_mapping is enabled and a program tries to write to a readonly // region we'll get a memory access violation. Map it to a more specific // error so it's easier for developers to see what happened. if let Some((instruction_account_index, _)) = account_region_addrs .iter() .enumerate() .find(|(_, vm_region)| vm_region.contains(address)) { let transaction_context = &invoke_context.transaction_context; let instruction_context = transaction_context.get_current_instruction_context()?; let account = instruction_context.try_borrow_instruction_account( transaction_context, instruction_account_index as IndexOfAccount, )?; error = Box::new(if account.is_executable() { InstructionError::ExecutableDataModified } else if account.is_writable() { InstructionError::ExternalAccountDataModified } else { InstructionError::ReadonlyDataModified }) } } } Err(error) } _ => Ok(()), } }; execute_time.stop(); fn deserialize_parameters( invoke_context: &mut InvokeContext, parameter_bytes: &[u8], copy_account_data: bool, ) -> Result<(), InstructionError> { serialization::deserialize_parameters( invoke_context.transaction_context, invoke_context .transaction_context .get_current_instruction_context()?, copy_account_data, parameter_bytes, &invoke_context.get_syscall_context()?.accounts_metadata, ) } let mut deserialize_time = Measure::start("deserialize"); let execute_or_deserialize_result = execution_result.and_then(|_| { deserialize_parameters(invoke_context, parameter_bytes.as_slice(), !direct_mapping) .map_err(|error| Box::new(error) as Box) }); deserialize_time.stop(); // Update the timings let timings = &mut invoke_context.timings; timings.serialize_us = timings.serialize_us.saturating_add(serialize_time.as_us()); 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()); timings.deserialize_us = timings .deserialize_us .saturating_add(deserialize_time.as_us()); execute_or_deserialize_result } pub mod test_utils { use { super::*, solana_program_runtime::loaded_programs::DELAY_VISIBILITY_SLOT_OFFSET, solana_sdk::account::ReadableAccount, }; pub fn load_all_invoked_programs(invoke_context: &mut InvokeContext) { let mut load_program_metrics = LoadProgramMetrics::default(); let program_runtime_environment = create_program_runtime_environment_v1( &invoke_context.feature_set, invoke_context.get_compute_budget(), false, /* deployment */ false, /* debugging_features */ ); let program_runtime_environment = Arc::new(program_runtime_environment.unwrap()); let num_accounts = invoke_context.transaction_context.get_number_of_accounts(); for index in 0..num_accounts { let account = invoke_context .transaction_context .get_account_at_index(index) .expect("Failed to get the account") .borrow(); let owner = account.owner(); if check_loader_id(owner) { let pubkey = invoke_context .transaction_context .get_key_of_account_at_index(index) .expect("Failed to get account key"); if let Ok(loaded_program) = load_program_from_bytes( &FeatureSet::all_enabled(), None, &mut load_program_metrics, account.data(), owner, account.data().len(), 0, program_runtime_environment.clone(), ) { invoke_context .programs_modified_by_tx .set_slot_for_tests(DELAY_VISIBILITY_SLOT_OFFSET); invoke_context .programs_modified_by_tx .replenish(*pubkey, Arc::new(loaded_program)); } } } } } #[cfg(test)] mod tests { use { super::*, assert_matches::assert_matches, rand::Rng, solana_program_runtime::{ invoke_context::mock_process_instruction, with_mock_invoke_context, }, solana_rbpf::{ elf::SBPFVersion, verifier::Verifier, vm::{Config, ContextObject, FunctionRegistry}, }, solana_sdk::{ account::{ create_account_shared_data_for_test as create_account_for_test, AccountSharedData, ReadableAccount, WritableAccount, }, account_utils::StateMut, clock::Clock, instruction::{AccountMeta, InstructionError}, pubkey::Pubkey, rent::Rent, system_program, sysvar, }, std::{fs::File, io::Read, ops::Range, sync::atomic::AtomicU64}, }; struct TestContextObject { remaining: u64, } impl ContextObject for TestContextObject { fn trace(&mut self, _state: [u64; 12]) {} fn consume(&mut self, amount: u64) { self.remaining = self.remaining.saturating_sub(amount); } fn get_remaining(&self) -> u64 { self.remaining } } fn process_instruction( loader_id: &Pubkey, program_indices: &[IndexOfAccount], instruction_data: &[u8], transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, expected_result: Result<(), InstructionError>, ) -> Vec { mock_process_instruction( loader_id, program_indices.to_vec(), instruction_data, transaction_accounts, instruction_accounts, expected_result, super::process_instruction, |invoke_context| { test_utils::load_all_invoked_programs(invoke_context); }, |_invoke_context| {}, ) } fn load_program_account_from_elf(loader_id: &Pubkey, path: &str) -> AccountSharedData { let mut file = File::open(path).expect("file open failed"); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); let rent = Rent::default(); let mut program_account = AccountSharedData::new(rent.minimum_balance(elf.len()), 0, loader_id); program_account.set_data(elf); program_account.set_executable(true); program_account } #[test] #[should_panic(expected = "LDDWCannotBeLast")] fn test_bpf_loader_check_load_dw() { let prog = &[ 0x18, 0x00, 0x00, 0x00, 0x88, 0x77, 0x66, 0x55, // first half of lddw ]; RequisiteVerifier::verify( prog, &Config::default(), &SBPFVersion::V2, &FunctionRegistry::default(), ) .unwrap(); } #[test] fn test_bpf_loader_write() { let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); let mut program_account = AccountSharedData::new(1, 0, &loader_id); let instruction_data = bincode::serialize(&LoaderInstruction::Write { offset: 3, bytes: vec![1, 2, 3], }) .unwrap(); // Case: No program account process_instruction( &loader_id, &[], &instruction_data, Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); // Case: Not signed process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account.clone())], vec![AccountMeta { pubkey: program_id, is_signer: false, is_writable: true, }], Err(InstructionError::MissingRequiredSignature), ); // Case: Write bytes to an offset program_account.set_data(vec![0; 6]); let accounts = process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account.clone())], vec![AccountMeta { pubkey: program_id, is_signer: true, is_writable: true, }], Ok(()), ); assert_eq!(&vec![0, 0, 0, 1, 2, 3], accounts.first().unwrap().data()); // Case: Overflow program_account.set_data(vec![0; 5]); process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account)], vec![AccountMeta { pubkey: program_id, is_signer: true, is_writable: true, }], Err(InstructionError::AccountDataTooSmall), ); } #[test] fn test_bpf_loader_finalize() { let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); let mut program_account = load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so"); program_account.set_executable(false); let instruction_data = bincode::serialize(&LoaderInstruction::Finalize).unwrap(); // Case: No program account process_instruction( &loader_id, &[], &instruction_data, Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); // Case: Not signed process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account.clone())], vec![AccountMeta { pubkey: program_id, is_signer: false, is_writable: true, }], Err(InstructionError::MissingRequiredSignature), ); // Case: Finalize let accounts = process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account.clone())], vec![AccountMeta { pubkey: program_id, is_signer: true, is_writable: true, }], Ok(()), ); assert!(accounts.first().unwrap().executable()); // Case: Finalize bad ELF *program_account.data_as_mut_slice().get_mut(0).unwrap() = 0; process_instruction( &loader_id, &[], &instruction_data, vec![(program_id, program_account)], vec![AccountMeta { pubkey: program_id, is_signer: true, is_writable: true, }], Err(InstructionError::InvalidAccountData), ); } #[test] fn test_bpf_loader_invoke_main() { let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); let mut program_account = load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so"); let parameter_id = Pubkey::new_unique(); let parameter_account = AccountSharedData::new(1, 0, &loader_id); let parameter_meta = AccountMeta { pubkey: parameter_id, is_signer: false, is_writable: false, }; // Case: No program account process_instruction( &loader_id, &[], &[], Vec::new(), Vec::new(), Err(InstructionError::NotEnoughAccountKeys), ); // Case: Only a program account process_instruction( &loader_id, &[0], &[], vec![(program_id, program_account.clone())], Vec::new(), Ok(()), ); // Case: With program and parameter account process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account.clone()), (parameter_id, parameter_account.clone()), ], vec![parameter_meta.clone()], Ok(()), ); // Case: With duplicate accounts process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account.clone()), (parameter_id, parameter_account), ], vec![parameter_meta.clone(), parameter_meta], Ok(()), ); // Case: limited budget mock_process_instruction( &loader_id, vec![0], &[], vec![(program_id, program_account.clone())], Vec::new(), Err(InstructionError::ProgramFailedToComplete), super::process_instruction, |invoke_context| { invoke_context.mock_set_remaining(0); test_utils::load_all_invoked_programs(invoke_context); }, |_invoke_context| {}, ); // Case: Account not a program program_account.set_executable(false); process_instruction( &loader_id, &[0], &[], vec![(program_id, program_account)], Vec::new(), Err(InstructionError::IncorrectProgramId), ); } #[test] fn test_bpf_loader_serialize_unaligned() { let loader_id = bpf_loader_deprecated::id(); let program_id = Pubkey::new_unique(); let program_account = load_program_account_from_elf(&loader_id, "test_elfs/out/noop_unaligned.so"); let parameter_id = Pubkey::new_unique(); let parameter_account = AccountSharedData::new(1, 0, &loader_id); let parameter_meta = AccountMeta { pubkey: parameter_id, is_signer: false, is_writable: false, }; // Case: With program and parameter account process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account.clone()), (parameter_id, parameter_account.clone()), ], vec![parameter_meta.clone()], Ok(()), ); // Case: With duplicate accounts process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account), (parameter_id, parameter_account), ], vec![parameter_meta.clone(), parameter_meta], Ok(()), ); } #[test] fn test_bpf_loader_serialize_aligned() { let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); let program_account = load_program_account_from_elf(&loader_id, "test_elfs/out/noop_aligned.so"); let parameter_id = Pubkey::new_unique(); let parameter_account = AccountSharedData::new(1, 0, &loader_id); let parameter_meta = AccountMeta { pubkey: parameter_id, is_signer: false, is_writable: false, }; // Case: With program and parameter account process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account.clone()), (parameter_id, parameter_account.clone()), ], vec![parameter_meta.clone()], Ok(()), ); // Case: With duplicate accounts process_instruction( &loader_id, &[0], &[], vec![ (program_id, program_account), (parameter_id, parameter_account), ], vec![parameter_meta.clone(), parameter_meta], Ok(()), ); } #[test] fn test_bpf_loader_upgradeable_initialize_buffer() { let loader_id = bpf_loader_upgradeable::id(); let buffer_address = Pubkey::new_unique(); let buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id); let authority_address = Pubkey::new_unique(); let authority_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id); let instruction_data = bincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap(); let instruction_accounts = vec![ AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: authority_address, is_signer: false, is_writable: false, }, ]; // Case: Success let accounts = process_instruction( &loader_id, &[], &instruction_data, vec![ (buffer_address, buffer_account), (authority_address, authority_account), ], instruction_accounts.clone(), Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(authority_address) } ); // Case: Already initialized let accounts = process_instruction( &loader_id, &[], &instruction_data, vec![ (buffer_address, accounts.first().unwrap().clone()), (authority_address, accounts.get(1).unwrap().clone()), ], instruction_accounts, Err(InstructionError::AccountAlreadyInitialized), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(authority_address) } ); } #[test] fn test_bpf_loader_upgradeable_write() { let loader_id = bpf_loader_upgradeable::id(); let buffer_address = Pubkey::new_unique(); let mut buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id); let instruction_accounts = vec![ AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: buffer_address, is_signer: true, is_writable: false, }, ]; // Case: Not initialized let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![42; 9], }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts.clone(), Err(InstructionError::InvalidAccountData), ); // Case: Write entire buffer let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![42; 9], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); let accounts = process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts.clone(), Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address) } ); assert_eq!( &accounts .first() .unwrap() .data() .get(UpgradeableLoaderState::size_of_buffer_metadata()..) .unwrap(), &[42; 9] ); // Case: Write portion of the buffer let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 3, bytes: vec![42; 6], }) .unwrap(); let mut buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(9), &loader_id); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); let accounts = process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts.clone(), Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address) } ); assert_eq!( &accounts .first() .unwrap() .data() .get(UpgradeableLoaderState::size_of_buffer_metadata()..) .unwrap(), &[0, 0, 0, 42, 42, 42, 42, 42, 42] ); // Case: overflow size let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![42; 10], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts.clone(), Err(InstructionError::AccountDataTooSmall), ); // Case: overflow offset let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 1, bytes: vec![42; 9], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts.clone(), Err(InstructionError::AccountDataTooSmall), ); // Case: Not signed let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 0, bytes: vec![42; 9], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], vec![ AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::MissingRequiredSignature), ); // Case: wrong authority let authority_address = Pubkey::new_unique(); let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 1, bytes: vec![42; 9], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(buffer_address), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![ (buffer_address, buffer_account.clone()), (authority_address, buffer_account.clone()), ], vec![ AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: authority_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::IncorrectAuthority), ); // Case: None authority let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { offset: 1, bytes: vec![42; 9], }) .unwrap(); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: None, }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![(buffer_address, buffer_account.clone())], instruction_accounts, Err(InstructionError::Immutable), ); } fn truncate_data(account: &mut AccountSharedData, len: usize) { let mut data = account.data().to_vec(); data.truncate(len); account.set_data(data); } #[test] fn test_bpf_loader_upgradeable_upgrade() { let mut file = File::open("test_elfs/out/noop_aligned.so").expect("file open failed"); let mut elf_orig = Vec::new(); file.read_to_end(&mut elf_orig).unwrap(); let mut file = File::open("test_elfs/out/noop_unaligned.so").expect("file open failed"); let mut elf_new = Vec::new(); file.read_to_end(&mut elf_new).unwrap(); assert_ne!(elf_orig.len(), elf_new.len()); const SLOT: u64 = 42; let buffer_address = Pubkey::new_unique(); let upgrade_authority_address = Pubkey::new_unique(); fn get_accounts( buffer_address: &Pubkey, buffer_authority: &Pubkey, upgrade_authority_address: &Pubkey, elf_orig: &[u8], elf_new: &[u8], ) -> (Vec<(Pubkey, AccountSharedData)>, Vec) { let loader_id = bpf_loader_upgradeable::id(); let program_address = Pubkey::new_unique(); let spill_address = Pubkey::new_unique(); let rent = Rent::default(); let min_program_balance = 1.max(rent.minimum_balance(UpgradeableLoaderState::size_of_program())); let min_programdata_balance = 1.max(rent.minimum_balance( UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())), )); let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &loader_id); let mut buffer_account = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_buffer(elf_new.len()), &bpf_loader_upgradeable::id(), ); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(*buffer_authority), }) .unwrap(); buffer_account .data_as_mut_slice() .get_mut(UpgradeableLoaderState::size_of_buffer_metadata()..) .unwrap() .copy_from_slice(elf_new); let mut programdata_account = AccountSharedData::new( min_programdata_balance, UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())), &bpf_loader_upgradeable::id(), ); programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot: SLOT, upgrade_authority_address: Some(*upgrade_authority_address), }) .unwrap(); let mut program_account = AccountSharedData::new( min_program_balance, UpgradeableLoaderState::size_of_program(), &bpf_loader_upgradeable::id(), ); program_account.set_executable(true); program_account .set_state(&UpgradeableLoaderState::Program { programdata_address, }) .unwrap(); let spill_account = AccountSharedData::new(0, 0, &Pubkey::new_unique()); let rent_account = create_account_for_test(&rent); let clock_account = create_account_for_test(&Clock { slot: SLOT.saturating_add(1), ..Clock::default() }); let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let transaction_accounts = vec![ (programdata_address, programdata_account), (program_address, program_account), (*buffer_address, buffer_account), (spill_address, spill_account), (sysvar::rent::id(), rent_account), (sysvar::clock::id(), clock_account), (*upgrade_authority_address, upgrade_authority_account), ]; let instruction_accounts = vec![ AccountMeta { pubkey: programdata_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: program_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: *buffer_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: spill_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: sysvar::rent::id(), is_signer: false, is_writable: false, }, AccountMeta { pubkey: sysvar::clock::id(), is_signer: false, is_writable: false, }, AccountMeta { pubkey: *upgrade_authority_address, is_signer: true, is_writable: false, }, ]; (transaction_accounts, instruction_accounts) } fn process_instruction( transaction_accounts: Vec<(Pubkey, AccountSharedData)>, instruction_accounts: Vec, expected_result: Result<(), InstructionError>, ) -> Vec { let instruction_data = bincode::serialize(&UpgradeableLoaderInstruction::Upgrade).unwrap(); mock_process_instruction( &bpf_loader_upgradeable::id(), Vec::new(), &instruction_data, transaction_accounts, instruction_accounts, expected_result, super::process_instruction, |_invoke_context| {}, |_invoke_context| {}, ) } // Case: Success let (transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); let accounts = process_instruction(transaction_accounts, instruction_accounts, Ok(())); let min_programdata_balance = Rent::default().minimum_balance( UpgradeableLoaderState::size_of_programdata(elf_orig.len().max(elf_new.len())), ); assert_eq!( min_programdata_balance, accounts.first().unwrap().lamports() ); assert_eq!(0, accounts.get(2).unwrap().lamports()); assert_eq!(1, accounts.get(3).unwrap().lamports()); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::ProgramData { slot: SLOT.saturating_add(1), upgrade_authority_address: Some(upgrade_authority_address) } ); for (i, byte) in accounts .first() .unwrap() .data() .get( UpgradeableLoaderState::size_of_programdata_metadata() ..UpgradeableLoaderState::size_of_programdata(elf_new.len()), ) .unwrap() .iter() .enumerate() { assert_eq!(*elf_new.get(i).unwrap(), *byte); } // Case: not upgradable let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::ProgramData { slot: SLOT, upgrade_authority_address: None, }) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::Immutable), ); // Case: wrong authority let (mut transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); let invalid_upgrade_authority_address = Pubkey::new_unique(); transaction_accounts.get_mut(6).unwrap().0 = invalid_upgrade_authority_address; instruction_accounts.get_mut(6).unwrap().pubkey = invalid_upgrade_authority_address; process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::IncorrectAuthority), ); // Case: authority did not sign let (transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); instruction_accounts.get_mut(6).unwrap().is_signer = false; process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::MissingRequiredSignature), ); // Case: Buffer account and spill account alias let (transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); *instruction_accounts.get_mut(3).unwrap() = instruction_accounts.get(2).unwrap().clone(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::AccountBorrowFailed), ); // Case: Programdata account and spill account alias let (transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); *instruction_accounts.get_mut(3).unwrap() = instruction_accounts.get(0).unwrap().clone(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::AccountBorrowFailed), ); // Case: Program account not executable let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(1) .unwrap() .1 .set_executable(false); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::AccountNotExecutable), ); // Case: Program account now owned by loader let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(1) .unwrap() .1 .set_owner(Pubkey::new_unique()); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::IncorrectProgramId), ); // Case: Program account not writable let (transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); instruction_accounts.get_mut(1).unwrap().is_writable = false; process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::InvalidArgument), ); // Case: Program account not initialized let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(1) .unwrap() .1 .set_state(&UpgradeableLoaderState::Uninitialized) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::InvalidAccountData), ); // Case: Program ProgramData account mismatch let (mut transaction_accounts, mut instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); let invalid_programdata_address = Pubkey::new_unique(); transaction_accounts.get_mut(0).unwrap().0 = invalid_programdata_address; instruction_accounts.get_mut(0).unwrap().pubkey = invalid_programdata_address; process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::InvalidArgument), ); // Case: Buffer account not initialized let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(2) .unwrap() .1 .set_state(&UpgradeableLoaderState::Uninitialized) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::InvalidArgument), ); // Case: Buffer account too big let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts.get_mut(2).unwrap().1 = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_buffer( elf_orig.len().max(elf_new.len()).saturating_add(1), ), &bpf_loader_upgradeable::id(), ); transaction_accounts .get_mut(2) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(upgrade_authority_address), }) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::AccountDataTooSmall), ); // Case: Buffer account too small let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &upgrade_authority_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(2) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(upgrade_authority_address), }) .unwrap(); truncate_data(&mut transaction_accounts.get_mut(2).unwrap().1, 5); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::InvalidAccountData), ); // Case: Mismatched buffer and program authority let (transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &buffer_address, &upgrade_authority_address, &elf_orig, &elf_new, ); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::IncorrectAuthority), ); // Case: No buffer authority let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &buffer_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(2) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: None, }) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::IncorrectAuthority), ); // Case: No buffer and program authority let (mut transaction_accounts, instruction_accounts) = get_accounts( &buffer_address, &buffer_address, &upgrade_authority_address, &elf_orig, &elf_new, ); transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::ProgramData { slot: SLOT, upgrade_authority_address: None, }) .unwrap(); transaction_accounts .get_mut(2) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: None, }) .unwrap(); process_instruction( transaction_accounts, instruction_accounts, Err(InstructionError::IncorrectAuthority), ); } #[test] fn test_bpf_loader_upgradeable_set_upgrade_authority() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(); let loader_id = bpf_loader_upgradeable::id(); let slot = 0; let upgrade_authority_address = Pubkey::new_unique(); let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let new_upgrade_authority_address = Pubkey::new_unique(); let new_upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address( &[program_address.as_ref()], &bpf_loader_upgradeable::id(), ); let mut programdata_account = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_programdata(0), &bpf_loader_upgradeable::id(), ); programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: Some(upgrade_authority_address), }) .unwrap(); let programdata_meta = AccountMeta { pubkey: programdata_address, is_signer: false, is_writable: true, }; let upgrade_authority_meta = AccountMeta { pubkey: upgrade_authority_address, is_signer: true, is_writable: false, }; let new_upgrade_authority_meta = AccountMeta { pubkey: new_upgrade_authority_address, is_signer: false, is_writable: false, }; // Case: Set to new authority let accounts = process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![ programdata_meta.clone(), upgrade_authority_meta.clone(), new_upgrade_authority_meta.clone(), ], Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: Some(new_upgrade_authority_address), } ); // Case: Not upgradeable let accounts = process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ], vec![programdata_meta.clone(), upgrade_authority_meta.clone()], Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: None, } ); // Case: Authority did not sign process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ], vec![ programdata_meta.clone(), AccountMeta { pubkey: upgrade_authority_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::MissingRequiredSignature), ); // Case: wrong authority let invalid_upgrade_authority_address = Pubkey::new_unique(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), ( invalid_upgrade_authority_address, upgrade_authority_account.clone(), ), (new_upgrade_authority_address, new_upgrade_authority_account), ], vec![ programdata_meta.clone(), AccountMeta { pubkey: invalid_upgrade_authority_address, is_signer: true, is_writable: false, }, new_upgrade_authority_meta, ], Err(InstructionError::IncorrectAuthority), ); // Case: No authority programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: None, }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ], vec![programdata_meta.clone(), upgrade_authority_meta.clone()], Err(InstructionError::Immutable), ); // Case: Not a ProgramData account programdata_account .set_state(&UpgradeableLoaderState::Program { programdata_address: Pubkey::new_unique(), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account), ], vec![programdata_meta, upgrade_authority_meta], Err(InstructionError::InvalidArgument), ); } #[test] fn test_bpf_loader_upgradeable_set_upgrade_authority_checked() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap(); let loader_id = bpf_loader_upgradeable::id(); let slot = 0; let upgrade_authority_address = Pubkey::new_unique(); let upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let new_upgrade_authority_address = Pubkey::new_unique(); let new_upgrade_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let program_address = Pubkey::new_unique(); let (programdata_address, _) = Pubkey::find_program_address( &[program_address.as_ref()], &bpf_loader_upgradeable::id(), ); let mut programdata_account = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_programdata(0), &bpf_loader_upgradeable::id(), ); programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: Some(upgrade_authority_address), }) .unwrap(); let programdata_meta = AccountMeta { pubkey: programdata_address, is_signer: false, is_writable: true, }; let upgrade_authority_meta = AccountMeta { pubkey: upgrade_authority_address, is_signer: true, is_writable: false, }; let new_upgrade_authority_meta = AccountMeta { pubkey: new_upgrade_authority_address, is_signer: true, is_writable: false, }; // Case: Set to new authority let accounts = process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![ programdata_meta.clone(), upgrade_authority_meta.clone(), new_upgrade_authority_meta.clone(), ], Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: Some(new_upgrade_authority_address), } ); // Case: set to same authority process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ], vec![ programdata_meta.clone(), upgrade_authority_meta.clone(), upgrade_authority_meta.clone(), ], Ok(()), ); // Case: present authority not in instruction process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![programdata_meta.clone(), new_upgrade_authority_meta.clone()], Err(InstructionError::NotEnoughAccountKeys), ); // Case: new authority not in instruction process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![programdata_meta.clone(), upgrade_authority_meta.clone()], Err(InstructionError::NotEnoughAccountKeys), ); // Case: present authority did not sign process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![ programdata_meta.clone(), AccountMeta { pubkey: upgrade_authority_address, is_signer: false, is_writable: false, }, new_upgrade_authority_meta.clone(), ], Err(InstructionError::MissingRequiredSignature), ); // Case: New authority did not sign process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ( new_upgrade_authority_address, new_upgrade_authority_account.clone(), ), ], vec![ programdata_meta.clone(), upgrade_authority_meta.clone(), AccountMeta { pubkey: new_upgrade_authority_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::MissingRequiredSignature), ); // Case: wrong present authority let invalid_upgrade_authority_address = Pubkey::new_unique(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), ( invalid_upgrade_authority_address, upgrade_authority_account.clone(), ), (new_upgrade_authority_address, new_upgrade_authority_account), ], vec![ programdata_meta.clone(), AccountMeta { pubkey: invalid_upgrade_authority_address, is_signer: true, is_writable: false, }, new_upgrade_authority_meta.clone(), ], Err(InstructionError::IncorrectAuthority), ); // Case: programdata is immutable programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot, upgrade_authority_address: None, }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account.clone()), ], vec![ programdata_meta.clone(), upgrade_authority_meta.clone(), new_upgrade_authority_meta.clone(), ], Err(InstructionError::Immutable), ); // Case: Not a ProgramData account programdata_account .set_state(&UpgradeableLoaderState::Program { programdata_address: Pubkey::new_unique(), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (upgrade_authority_address, upgrade_authority_account), ], vec![ programdata_meta, upgrade_authority_meta, new_upgrade_authority_meta, ], Err(InstructionError::InvalidArgument), ); } #[test] fn test_bpf_loader_upgradeable_set_buffer_authority() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(); let loader_id = bpf_loader_upgradeable::id(); let invalid_authority_address = Pubkey::new_unique(); let authority_address = Pubkey::new_unique(); let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let new_authority_address = Pubkey::new_unique(); let new_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let buffer_address = Pubkey::new_unique(); let mut buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), }) .unwrap(); let mut transaction_accounts = vec![ (buffer_address, buffer_account.clone()), (authority_address, authority_account.clone()), (new_authority_address, new_authority_account.clone()), ]; let buffer_meta = AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: true, }; let authority_meta = AccountMeta { pubkey: authority_address, is_signer: true, is_writable: false, }; let new_authority_meta = AccountMeta { pubkey: new_authority_address, is_signer: false, is_writable: false, }; // Case: New authority required let accounts = process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta.clone(), authority_meta.clone()], Err(InstructionError::IncorrectAuthority), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), } ); // Case: Set to new authority buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), }) .unwrap(); let accounts = process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), new_authority_meta.clone(), ], Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(new_authority_address), } ); // Case: Authority did not sign process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), AccountMeta { pubkey: authority_address, is_signer: false, is_writable: false, }, new_authority_meta.clone(), ], Err(InstructionError::MissingRequiredSignature), ); // Case: wrong authority process_instruction( &loader_id, &[], &instruction, vec![ (buffer_address, buffer_account.clone()), (invalid_authority_address, authority_account), (new_authority_address, new_authority_account), ], vec![ buffer_meta.clone(), AccountMeta { pubkey: invalid_authority_address, is_signer: true, is_writable: false, }, new_authority_meta.clone(), ], Err(InstructionError::IncorrectAuthority), ); // Case: No authority process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta.clone(), authority_meta.clone()], Err(InstructionError::IncorrectAuthority), ); // Case: Set to no authority transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: None, }) .unwrap(); process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), new_authority_meta.clone(), ], Err(InstructionError::Immutable), ); // Case: Not a Buffer account transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::Program { programdata_address: Pubkey::new_unique(), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta, authority_meta, new_authority_meta], Err(InstructionError::InvalidArgument), ); } #[test] fn test_bpf_loader_upgradeable_set_buffer_authority_checked() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthorityChecked).unwrap(); let loader_id = bpf_loader_upgradeable::id(); let invalid_authority_address = Pubkey::new_unique(); let authority_address = Pubkey::new_unique(); let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let new_authority_address = Pubkey::new_unique(); let new_authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let buffer_address = Pubkey::new_unique(); let mut buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(0), &loader_id); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), }) .unwrap(); let mut transaction_accounts = vec![ (buffer_address, buffer_account.clone()), (authority_address, authority_account.clone()), (new_authority_address, new_authority_account.clone()), ]; let buffer_meta = AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: true, }; let authority_meta = AccountMeta { pubkey: authority_address, is_signer: true, is_writable: false, }; let new_authority_meta = AccountMeta { pubkey: new_authority_address, is_signer: true, is_writable: false, }; // Case: Set to new authority buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), }) .unwrap(); let accounts = process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), new_authority_meta.clone(), ], Ok(()), ); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!( state, UpgradeableLoaderState::Buffer { authority_address: Some(new_authority_address), } ); // Case: set to same authority process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), authority_meta.clone(), ], Ok(()), ); // Case: Missing current authority process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta.clone(), new_authority_meta.clone()], Err(InstructionError::NotEnoughAccountKeys), ); // Case: Missing new authority process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta.clone(), authority_meta.clone()], Err(InstructionError::NotEnoughAccountKeys), ); // Case: wrong present authority process_instruction( &loader_id, &[], &instruction, vec![ (buffer_address, buffer_account.clone()), (invalid_authority_address, authority_account), (new_authority_address, new_authority_account), ], vec![ buffer_meta.clone(), AccountMeta { pubkey: invalid_authority_address, is_signer: true, is_writable: false, }, new_authority_meta.clone(), ], Err(InstructionError::IncorrectAuthority), ); // Case: present authority did not sign process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), AccountMeta { pubkey: authority_address, is_signer: false, is_writable: false, }, new_authority_meta.clone(), ], Err(InstructionError::MissingRequiredSignature), ); // Case: new authority did not sign process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), AccountMeta { pubkey: new_authority_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::MissingRequiredSignature), ); // Case: Not a Buffer account transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::Program { programdata_address: Pubkey::new_unique(), }) .unwrap(); process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![ buffer_meta.clone(), authority_meta.clone(), new_authority_meta.clone(), ], Err(InstructionError::InvalidArgument), ); // Case: Buffer is immutable transaction_accounts .get_mut(0) .unwrap() .1 .set_state(&UpgradeableLoaderState::Buffer { authority_address: None, }) .unwrap(); process_instruction( &loader_id, &[], &instruction, transaction_accounts.clone(), vec![buffer_meta, authority_meta, new_authority_meta], Err(InstructionError::Immutable), ); } #[test] fn test_bpf_loader_upgradeable_close() { let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Close).unwrap(); let loader_id = bpf_loader_upgradeable::id(); let invalid_authority_address = Pubkey::new_unique(); let authority_address = Pubkey::new_unique(); let authority_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let recipient_address = Pubkey::new_unique(); let recipient_account = AccountSharedData::new(1, 0, &Pubkey::new_unique()); let buffer_address = Pubkey::new_unique(); let mut buffer_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_buffer(128), &loader_id); buffer_account .set_state(&UpgradeableLoaderState::Buffer { authority_address: Some(authority_address), }) .unwrap(); let uninitialized_address = Pubkey::new_unique(); let mut uninitialized_account = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_programdata(0), &loader_id, ); uninitialized_account .set_state(&UpgradeableLoaderState::Uninitialized) .unwrap(); let programdata_address = Pubkey::new_unique(); let mut programdata_account = AccountSharedData::new( 1, UpgradeableLoaderState::size_of_programdata(128), &loader_id, ); programdata_account .set_state(&UpgradeableLoaderState::ProgramData { slot: 0, upgrade_authority_address: Some(authority_address), }) .unwrap(); let program_address = Pubkey::new_unique(); let mut program_account = AccountSharedData::new(1, UpgradeableLoaderState::size_of_program(), &loader_id); program_account.set_executable(true); program_account .set_state(&UpgradeableLoaderState::Program { programdata_address, }) .unwrap(); let clock_account = create_account_for_test(&Clock { slot: 1, ..Clock::default() }); let transaction_accounts = vec![ (buffer_address, buffer_account.clone()), (recipient_address, recipient_account.clone()), (authority_address, authority_account.clone()), ]; let buffer_meta = AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: true, }; let recipient_meta = AccountMeta { pubkey: recipient_address, is_signer: false, is_writable: true, }; let authority_meta = AccountMeta { pubkey: authority_address, is_signer: true, is_writable: false, }; // Case: close a buffer account let accounts = process_instruction( &loader_id, &[], &instruction, transaction_accounts, vec![ buffer_meta.clone(), recipient_meta.clone(), authority_meta.clone(), ], Ok(()), ); assert_eq!(0, accounts.first().unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports()); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!( UpgradeableLoaderState::size_of_uninitialized(), accounts.first().unwrap().data().len() ); // Case: close with wrong authority process_instruction( &loader_id, &[], &instruction, vec![ (buffer_address, buffer_account.clone()), (recipient_address, recipient_account.clone()), (invalid_authority_address, authority_account.clone()), ], vec![ buffer_meta, recipient_meta.clone(), AccountMeta { pubkey: invalid_authority_address, is_signer: true, is_writable: false, }, ], Err(InstructionError::IncorrectAuthority), ); // Case: close an uninitialized account let accounts = process_instruction( &loader_id, &[], &instruction, vec![ (uninitialized_address, uninitialized_account.clone()), (recipient_address, recipient_account.clone()), (invalid_authority_address, authority_account.clone()), ], vec![ AccountMeta { pubkey: uninitialized_address, is_signer: false, is_writable: true, }, recipient_meta.clone(), authority_meta.clone(), ], Ok(()), ); assert_eq!(0, accounts.first().unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports()); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!( UpgradeableLoaderState::size_of_uninitialized(), accounts.first().unwrap().data().len() ); // Case: close a program account let accounts = process_instruction( &loader_id, &[], &instruction, vec![ (programdata_address, programdata_account.clone()), (recipient_address, recipient_account.clone()), (authority_address, authority_account.clone()), (program_address, program_account.clone()), (sysvar::clock::id(), clock_account.clone()), ], vec![ AccountMeta { pubkey: programdata_address, is_signer: false, is_writable: true, }, recipient_meta, authority_meta, AccountMeta { pubkey: program_address, is_signer: false, is_writable: true, }, ], Ok(()), ); assert_eq!(0, accounts.first().unwrap().lamports()); assert_eq!(2, accounts.get(1).unwrap().lamports()); let state: UpgradeableLoaderState = accounts.first().unwrap().state().unwrap(); assert_eq!(state, UpgradeableLoaderState::Uninitialized); assert_eq!( UpgradeableLoaderState::size_of_uninitialized(), accounts.first().unwrap().data().len() ); // Try to invoke closed account programdata_account = accounts.first().unwrap().clone(); program_account = accounts.get(3).unwrap().clone(); process_instruction( &loader_id, &[0, 1], &[], vec![ (programdata_address, programdata_account.clone()), (program_address, program_account.clone()), ], Vec::new(), Err(InstructionError::InvalidAccountData), ); // Case: Reopen should fail process_instruction( &loader_id, &[], &bincode::serialize(&UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len: 0, }) .unwrap(), vec![ (recipient_address, recipient_account), (programdata_address, programdata_account), (program_address, program_account), (buffer_address, buffer_account), ( sysvar::rent::id(), create_account_for_test(&Rent::default()), ), (sysvar::clock::id(), clock_account), ( system_program::id(), AccountSharedData::new(0, 0, &system_program::id()), ), (authority_address, authority_account), ], vec![ AccountMeta { pubkey: recipient_address, is_signer: true, is_writable: true, }, AccountMeta { pubkey: programdata_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: program_address, is_signer: false, is_writable: true, }, AccountMeta { pubkey: buffer_address, is_signer: false, is_writable: false, }, AccountMeta { pubkey: sysvar::rent::id(), is_signer: false, is_writable: false, }, AccountMeta { pubkey: sysvar::clock::id(), is_signer: false, is_writable: false, }, AccountMeta { pubkey: system_program::id(), is_signer: false, is_writable: false, }, AccountMeta { pubkey: authority_address, is_signer: false, is_writable: false, }, ], Err(InstructionError::AccountAlreadyInitialized), ); } /// fuzzing utility function fn fuzz( bytes: &[u8], outer_iters: usize, inner_iters: usize, offset: Range, value: Range, work: F, ) where F: Fn(&mut [u8]), { let mut rng = rand::thread_rng(); for _ in 0..outer_iters { let mut mangled_bytes = bytes.to_vec(); for _ in 0..inner_iters { let offset = rng.gen_range(offset.start..offset.end); let value = rng.gen_range(value.start..value.end); *mangled_bytes.get_mut(offset).unwrap() = value; work(&mut mangled_bytes); } } } #[test] #[ignore] fn test_fuzz() { let loader_id = bpf_loader::id(); let program_id = Pubkey::new_unique(); // Create program account let mut file = File::open("test_elfs/out/noop_aligned.so").expect("file open failed"); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); // Mangle the whole file fuzz( &elf, 1_000_000_000, 100, 0..elf.len(), 0..255, |bytes: &mut [u8]| { let mut program_account = AccountSharedData::new(1, 0, &loader_id); program_account.set_data(bytes.to_vec()); program_account.set_executable(true); process_instruction( &loader_id, &[], &[], vec![(program_id, program_account)], Vec::new(), Ok(()), ); }, ); } #[test] fn test_calculate_heap_cost() { let heap_cost = 8_u64; // heap allocations are in 32K block, `heap_cost` of CU is consumed per additional 32k // when `enable_heap_size_round_up` not enabled: { // assert less than 32K heap should cost zero unit assert_eq!(0, calculate_heap_cost(31_u64 * 1024, heap_cost, false)); // assert exact 32K heap should be cost zero unit assert_eq!(0, calculate_heap_cost(32_u64 * 1024, heap_cost, false)); // assert slightly more than 32K heap is mistakenly cost zero unit assert_eq!(0, calculate_heap_cost(33_u64 * 1024, heap_cost, false)); // assert exact 64K heap should cost 1 * heap_cost assert_eq!( heap_cost, calculate_heap_cost(64_u64 * 1024, heap_cost, false) ); } // when `enable_heap_size_round_up` is enabled: { // assert less than 32K heap should cost zero unit assert_eq!(0, calculate_heap_cost(31_u64 * 1024, heap_cost, true)); // assert exact 32K heap should be cost zero unit assert_eq!(0, calculate_heap_cost(32_u64 * 1024, heap_cost, true)); // assert slightly more than 32K heap should cost 1 * heap_cost assert_eq!( heap_cost, calculate_heap_cost(33_u64 * 1024, heap_cost, true) ); // assert exact 64K heap should cost 1 * heap_cost assert_eq!( heap_cost, calculate_heap_cost(64_u64 * 1024, heap_cost, true) ); } } fn deploy_test_program( invoke_context: &mut InvokeContext, program_id: Pubkey, ) -> Result<(), InstructionError> { let mut file = File::open("test_elfs/out/noop_unaligned.so").expect("file open failed"); let mut elf = Vec::new(); file.read_to_end(&mut elf).unwrap(); deploy_program!( invoke_context, program_id, &bpf_loader_upgradeable::id(), elf.len(), 2, {}, &elf ); Ok(()) } #[test] fn test_program_usage_count_on_upgrade() { let transaction_accounts = vec![]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); let env = Arc::new(BuiltinProgram::new_loader(Config::default())); let program = LoadedProgram { program: LoadedProgramType::Unloaded(env), account_size: 0, deployment_slot: 0, effective_slot: 0, maybe_expiration_slot: None, tx_usage_counter: AtomicU64::new(100), ix_usage_counter: AtomicU64::new(100), }; invoke_context .programs_modified_by_tx .replenish(program_id, Arc::new(program)); assert_matches!( deploy_test_program(&mut invoke_context, program_id,), Ok(()) ); let updated_program = invoke_context .programs_modified_by_tx .find(&program_id) .expect("Didn't find upgraded program in the cache"); assert_eq!(updated_program.deployment_slot, 2); assert_eq!( updated_program.tx_usage_counter.load(Ordering::Relaxed), 100 ); assert_eq!( updated_program.ix_usage_counter.load(Ordering::Relaxed), 100 ); } #[test] fn test_program_usage_count_on_non_upgrade() { let transaction_accounts = vec![]; with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts); let program_id = Pubkey::new_unique(); let env = Arc::new(BuiltinProgram::new_loader(Config::default())); let program = LoadedProgram { program: LoadedProgramType::Unloaded(env), account_size: 0, deployment_slot: 0, effective_slot: 0, maybe_expiration_slot: None, tx_usage_counter: AtomicU64::new(100), ix_usage_counter: AtomicU64::new(100), }; invoke_context .programs_modified_by_tx .replenish(program_id, Arc::new(program)); let program_id2 = Pubkey::new_unique(); assert_matches!( deploy_test_program(&mut invoke_context, program_id2), Ok(()) ); let program2 = invoke_context .programs_modified_by_tx .find(&program_id2) .expect("Didn't find upgraded program in the cache"); assert_eq!(program2.deployment_slot, 2); assert_eq!(program2.tx_usage_counter.load(Ordering::Relaxed), 0); assert_eq!(program2.ix_usage_counter.load(Ordering::Relaxed), 0); } }