#![deny(clippy::arithmetic_side_effects)]
#![deny(clippy::indexing_slicing)]
pub mod serialization;
pub mod syscalls;
use {
solana_compute_budget::compute_budget::MAX_INSTRUCTION_STACK_DEPTH,
solana_feature_set::{
bpf_account_data_direct_mapping, enable_bpf_loader_set_authority_checked_ix,
},
solana_log_collector::{ic_logger_msg, ic_msg, LogCollector},
solana_measure::measure::Measure,
solana_program_runtime::{
invoke_context::{BpfAllocator, InvokeContext, SerializedAccountMetadata, SyscallContext},
loaded_programs::{
LoadProgramMetrics, ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType,
DELAY_VISIBILITY_SLOT_OFFSET,
},
mem_pool::VmMemoryPool,
stable_log,
sysvar_cache::get_sysvar_with_account_check,
},
solana_rbpf::{
declare_builtin_function,
ebpf::{self, MM_HEAP_START},
elf::Executable,
error::{EbpfError, ProgramResult},
memory_region::{AccessType, MemoryCowCallback, MemoryMapping, MemoryRegion},
program::BuiltinProgram,
verifier::RequisiteVerifier,
vm::{ContextObject, EbpfVm},
},
solana_sdk::{
account::WritableAccount,
bpf_loader, bpf_loader_deprecated,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::Slot,
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
instruction::{AccountMeta, InstructionError},
loader_upgradeable_instruction::UpgradeableLoaderInstruction,
loader_v4, native_loader,
program_utils::limited_deserialize,
pubkey::Pubkey,
saturating_add_assign,
system_instruction::{self, MAX_PERMITTED_DATA_LENGTH},
transaction_context::{IndexOfAccount, InstructionContext, TransactionContext},
},
solana_type_overrides::sync::{atomic::Ordering, Arc},
std::{cell::RefCell, mem, rc::Rc},
syscalls::{create_program_runtime_environment_v1, morph_into_deployment_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;
thread_local! {
pub static MEMORY_POOL: RefCell<VmMemoryPool> = RefCell::new(VmMemoryPool::new());
}
#[allow(clippy::too_many_arguments)]
pub fn load_program_from_bytes(
log_collector: Option<Rc<RefCell<LogCollector>>>,
load_program_metrics: &mut LoadProgramMetrics,
programdata: &[u8],
loader_key: &Pubkey,
account_size: usize,
deployment_slot: Slot,
program_runtime_environment: Arc<BuiltinProgram<InvokeContext<'static>>>,
reloading: bool,
) -> Result<ProgramCacheEntry, InstructionError> {
let effective_slot = deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET);
let loaded_program = if reloading {
unsafe {
ProgramCacheEntry::reload(
loader_key,
program_runtime_environment,
deployment_slot,
effective_slot,
programdata,
account_size,
load_program_metrics,
)
}
} else {
ProgramCacheEntry::new(
loader_key,
program_runtime_environment,
deployment_slot,
effective_slot,
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 deployment_slot: Slot = $slot;
let environments = $invoke_context.get_environments_for_slot(
deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET)
).map_err(|e| {
ic_msg!($invoke_context, "Failed to get runtime environment: {}", e);
InstructionError::ProgramEnvironmentSetupFailure
})?;
let deployment_program_runtime_environment = morph_into_deployment_environment_v1(
environments.program_runtime_v1.clone(),
).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 mut load_elf_time = Measure::start("load_elf_time");
let executable = Executable::<InvokeContext>::load(
$new_programdata,
Arc::new(deployment_program_runtime_environment),
).map_err(|err| {
ic_logger_msg!($invoke_context.get_log_collector(), "{}", err);
InstructionError::InvalidAccountData
})?;
load_elf_time.stop();
load_program_metrics.load_elf_us = load_elf_time.as_us();
let mut verify_code_time = Measure::start("verify_code_time");
executable.verify::<RequisiteVerifier>().map_err(|err| {
ic_logger_msg!($invoke_context.get_log_collector(), "{}", err);
InstructionError::InvalidAccountData
})?;
verify_code_time.stop();
load_program_metrics.verify_code_us = verify_code_time.as_us();
let executor = load_program_from_bytes(
$invoke_context.get_log_collector(),
&mut load_program_metrics,
$new_programdata,
$loader_key,
$account_size,
$slot,
environments.program_runtime_v1.clone(),
true,
)?;
if let Some(old_entry) = $invoke_context.program_cache_for_tx_batch.find(&$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.program_cache_for_tx_batch.store_modified_entry($program_id, Arc::new(executor));
}};
}
pub fn direct_deploy_program(
invoke_context: &mut InvokeContext,
program_id: &Pubkey,
loader_key: &Pubkey,
account_size: usize,
elf: &[u8],
slot: Slot,
) -> Result<(), InstructionError> {
deploy_program!(
invoke_context,
*program_id,
loader_key,
account_size,
slot,
{},
elf,
);
Ok(())
}
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)
|| loader_v4::check_id(id)
}
pub fn calculate_heap_cost(heap_size: u32, heap_cost: u64) -> u64 {
const KIBIBYTE: u64 = 1024;
const PAGE_SIZE_KB: u64 = 32;
let mut rounded_heap_size = u64::from(heap_size);
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)
}
pub fn create_vm<'a, 'b>(
program: &'a Executable<InvokeContext<'b>>,
regions: Vec<MemoryRegion>,
accounts_metadata: Vec<SerializedAccountMetadata>,
invoke_context: &'a mut InvokeContext<'b>,
stack: &mut [u8],
heap: &mut [u8],
) -> Result<EbpfVm<'a, InvokeContext<'b>>, Box<dyn std::error::Error>> {
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| {
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() {
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_loader().clone(),
program.get_sbpf_version(),
invoke_context,
memory_mapping,
stack_size,
))
}
#[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;
let heap_cost_result = invoke_context.consume_checked($crate::calculate_heap_cost(
heap_size,
invoke_context.get_compute_budget().heap_cost,
));
let $vm = heap_cost_result.and_then(|_| {
let (mut stack, mut heap) = $crate::MEMORY_POOL
.with_borrow_mut(|pool| (pool.get_stack(stack_size), pool.get_heap(heap_size)));
let vm = $crate::create_vm(
$program,
$regions,
$accounts_metadata,
$invoke_context,
stack
.as_slice_mut()
.get_mut(..stack_size)
.expect("invalid stack size"),
heap.as_slice_mut()
.get_mut(..heap_size as usize)
.expect("invalid heap size"),
);
vm.map(|vm| (vm, stack, heap))
});
};
}
#[macro_export]
macro_rules! mock_create_vm {
($vm:ident, $additional_regions:expr, $accounts_metadata:expr, $invoke_context:expr $(,)?) => {
let loader = solana_type_overrides::sync::Arc::new(BuiltinProgram::new_mock());
let function_registry = solana_rbpf::program::FunctionRegistry::default();
let executable = solana_rbpf::elf::Executable::<InvokeContext>::from_text_bytes(
&[0x95, 0, 0, 0, 0, 0, 0, 0],
loader,
SBPFVersion::V2,
function_registry,
)
.unwrap();
executable
.verify::<solana_rbpf::verifier::RequisiteVerifier>()
.unwrap();
$crate::create_vm!(
$vm,
&executable,
$additional_regions,
$accounts_metadata,
$invoke_context,
);
let $vm = $vm.map(|(vm, _, _)| vm);
};
}
fn create_memory_mapping<'a, 'b, C: ContextObject>(
executable: &'a Executable<C>,
stack: &'b mut [u8],
heap: &'b mut [u8],
additional_regions: Vec<MemoryRegion>,
cow_cb: Option<MemoryCowCallback>,
) -> Result<MemoryMapping<'a>, Box<dyn std::error::Error>> {
let config = executable.get_config();
let sbpf_version = executable.get_sbpf_version();
let regions: Vec<MemoryRegion> = vec![
executable.get_ro_region(),
MemoryRegion::new_writable_gapped(
stack,
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, 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)?
})
}
declare_builtin_function!(
Entrypoint,
fn rust(
invoke_context: &mut InvokeContext,
_arg0: u64,
_arg1: u64,
_arg2: u64,
_arg3: u64,
_arg4: u64,
_memory_mapping: &mut MemoryMapping,
) -> Result<u64, Box<dyn std::error::Error>> {
process_instruction_inner(invoke_context)
}
);
pub fn process_instruction_inner(
invoke_context: &mut InvokeContext,
) -> Result<u64, Box<dyn std::error::Error>> {
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_account =
instruction_context.try_borrow_last_program_account(transaction_context)?;
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) {
invoke_context.consume_checked(UPGRADEABLE_LOADER_COMPUTE_UNITS)?;
process_loader_upgradeable_instruction(invoke_context)
} else if bpf_loader::check_id(program_id) {
invoke_context.consume_checked(DEFAULT_LOADER_COMPUTE_UNITS)?;
ic_logger_msg!(
log_collector,
"BPF loader management instructions are no longer supported",
);
Err(InstructionError::UnsupportedProgramId)
} else if bpf_loader_deprecated::check_id(program_id) {
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<dyn std::error::Error>);
}
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
.program_cache_for_tx_batch
.find(program_account.get_key())
.ok_or_else(|| {
ic_logger_msg!(log_collector, "Program is not cached");
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 {
ProgramCacheEntryType::FailedVerification(_)
| ProgramCacheEntryType::Closed
| ProgramCacheEntryType::DelayVisibility => {
ic_logger_msg!(log_collector, "Program is not deployed");
Err(Box::new(InstructionError::InvalidAccountData) as Box<dyn std::error::Error>)
}
ProgramCacheEntryType::Loaded(executable) => execute(executable, invoke_context),
_ => Err(Box::new(InstructionError::IncorrectProgramId) as Box<dyn std::error::Error>),
}
.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); }
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)?,
)?);
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);
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);
}
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);
}
{
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,
);
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::<Result<Vec<Pubkey>, solana_sdk::pubkey::PubkeyError>>()?;
invoke_context.native_invoke(instruction.into(), signers.as_slice())?;
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()?;
{
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);
buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?;
}
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)?,
)?);
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);
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);
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 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);
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()?;
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);
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)?;
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
.get_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()?;
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);
}
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()?;
invoke_context
.program_cache_for_tx_batch
.store_modified_entry(
program_key,
Arc::new(ProgramCacheEntry::new_tombstone(
clock.slot,
ProgramCacheEntryOwner::LoaderV3,
ProgramCacheEntryType::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 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)]
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)
};
drop(programdata_account);
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<Pubkey>,
transaction_context: &TransactionContext,
instruction_context: &InstructionContext,
log_collector: &Option<Rc<RefCell<LogCollector>>>,
) -> 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(())
}
pub fn execute<'a, 'b: 'a>(
executable: &'a Executable<InvokeContext<'static>>,
invoke_context: &'a mut InvokeContext<'b>,
) -> Result<(), Box<dyn std::error::Error>> {
let executable = unsafe {
mem::transmute::<&'a Executable<InvokeContext<'static>>, &'a Executable<InvokeContext<'b>>>(
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
.get_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,
!direct_mapping,
)?;
serialize_time.stop();
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::<Vec<_>>();
let mut create_vm_time = Measure::start("create_vm");
let execution_result = {
let compute_meter_prev = invoke_context.get_remaining();
create_vm!(vm, executable, regions, accounts_metadata, invoke_context);
let (mut vm, stack, heap) = 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();
vm.context_object_pointer.execute_time = Some(Measure::start("execute"));
let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit);
MEMORY_POOL.with_borrow_mut(|memory_pool| {
memory_pool.put_stack(stack);
memory_pool.put_heap(heap);
debug_assert!(memory_pool.stack_len() <= MAX_INSTRUCTION_STACK_DEPTH);
debug_assert!(memory_pool.heap_len() <= MAX_INSTRUCTION_STACK_DEPTH);
});
drop(vm);
if let Some(execute_time) = invoke_context.execute_time.as_mut() {
execute_time.stop();
saturating_add_assign!(invoke_context.timings.execute_us, execute_time.as_us());
}
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 = status.into();
Err(Box::new(error) as Box<dyn std::error::Error>)
}
ProgramResult::Err(mut error) => {
if direct_mapping {
if let EbpfError::AccessViolation(
AccessType::Store,
address,
_size,
_section_name,
) = error
{
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 = EbpfError::SyscallError(Box::new(if account.is_executable() {
InstructionError::ExecutableDataModified
} else if account.is_writable() {
InstructionError::ExternalAccountDataModified
} else {
InstructionError::ReadonlyDataModified
}));
}
}
}
Err(if let EbpfError::SyscallError(err) = error {
err
} else {
error.into()
})
}
_ => Ok(()),
}
};
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<dyn std::error::Error>)
});
deserialize_time.stop();
saturating_add_assign!(invoke_context.timings.serialize_us, serialize_time.as_us());
saturating_add_assign!(invoke_context.timings.create_vm_us, create_vm_time.as_us());
saturating_add_assign!(
invoke_context.timings.deserialize_us,
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, loader_v4::LoaderV4State},
};
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.get_feature_set(),
invoke_context.get_compute_budget(),
false, false, );
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 programdata_data_offset = if loader_v4::check_id(owner) {
LoaderV4State::program_data_offset()
} else {
0
};
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(
None,
&mut load_program_metrics,
account
.data()
.get(programdata_data_offset.min(account.data().len())..)
.unwrap(),
owner,
account.data().len(),
0,
program_runtime_environment.clone(),
false,
) {
invoke_context
.program_cache_for_tx_batch
.set_slot_for_tests(DELAY_VISIBILITY_SLOT_OFFSET);
invoke_context
.program_cache_for_tx_batch
.store_modified_entry(*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_sdk::{
account::{
create_account_shared_data_for_test as create_account_for_test, AccountSharedData,
ReadableAccount, WritableAccount,
},
account_utils::StateMut,
clock::Clock,
epoch_schedule::EpochSchedule,
instruction::{AccountMeta, InstructionError},
pubkey::Pubkey,
rent::Rent,
system_program, sysvar,
},
std::{fs::File, io::Read, ops::Range, sync::atomic::AtomicU64},
};
fn process_instruction(
loader_id: &Pubkey,
program_indices: &[IndexOfAccount],
instruction_data: &[u8],
transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
instruction_accounts: Vec<AccountMeta>,
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
mock_process_instruction(
loader_id,
program_indices.to_vec(),
instruction_data,
transaction_accounts,
instruction_accounts,
expected_result,
Entrypoint::vm,
|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]
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,
};
process_instruction(
&loader_id,
&[],
&[],
Vec::new(),
Vec::new(),
Err(InstructionError::UnsupportedProgramId),
);
process_instruction(
&loader_id,
&[0],
&[],
vec![(program_id, program_account.clone())],
Vec::new(),
Ok(()),
);
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account),
],
vec![parameter_meta.clone(), parameter_meta],
Ok(()),
);
mock_process_instruction(
&loader_id,
vec![0],
&[],
vec![(program_id, program_account.clone())],
Vec::new(),
Err(InstructionError::ProgramFailedToComplete),
Entrypoint::vm,
|invoke_context| {
invoke_context.mock_set_remaining(0);
test_utils::load_all_invoked_programs(invoke_context);
},
|_invoke_context| {},
);
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,
};
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
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,
};
process_instruction(
&loader_id,
&[0],
&[],
vec![
(program_id, program_account.clone()),
(parameter_id, parameter_account.clone()),
],
vec![parameter_meta.clone()],
Ok(()),
);
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,
},
];
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)
}
);
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,
},
];
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),
);
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]
);
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]
);
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),
);
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),
);
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),
);
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),
);
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<AccountMeta>) {
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<AccountMeta>,
expected_result: Result<(), InstructionError>,
) -> Vec<AccountSharedData> {
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,
Entrypoint::vm,
|_invoke_context| {},
|_invoke_context| {},
)
}
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);
}
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),
);
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),
);
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),
);
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),
);
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.first().unwrap().clone();
process_instruction(
transaction_accounts,
instruction_accounts,
Err(InstructionError::AccountBorrowFailed),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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,
};
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),
}
);
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,
}
);
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),
);
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),
);
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),
);
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,
};
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),
}
);
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(()),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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),
);
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,
};
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),
}
);
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),
}
);
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),
);
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),
);
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), authority_meta.clone()],
Err(InstructionError::IncorrectAuthority),
);
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),
);
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,
};
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),
}
);
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![
buffer_meta.clone(),
authority_meta.clone(),
authority_meta.clone(),
],
Ok(()),
);
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), new_authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
process_instruction(
&loader_id,
&[],
&instruction,
transaction_accounts.clone(),
vec![buffer_meta.clone(), authority_meta.clone()],
Err(InstructionError::NotEnoughAccountKeys),
);
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),
);
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),
);
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),
);
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),
);
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,
};
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()
);
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),
);
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()
);
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()
);
programdata_account = accounts.first().unwrap().clone();
program_account = accounts.get(3).unwrap().clone();
process_instruction(
&loader_id,
&[1],
&[],
vec![
(programdata_address, programdata_account.clone()),
(program_address, program_account.clone()),
],
Vec::new(),
Err(InstructionError::InvalidAccountData),
);
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),
);
}
fn fuzz<F>(
bytes: &[u8],
outer_iters: usize,
inner_iters: usize,
offset: Range<usize>,
value: Range<u8>,
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();
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();
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;
assert_eq!(0, calculate_heap_cost(31 * 1024, heap_cost));
assert_eq!(0, calculate_heap_cost(32 * 1024, heap_cost));
assert_eq!(heap_cost, calculate_heap_cost(33 * 1024, heap_cost));
assert_eq!(heap_cost, calculate_heap_cost(64 * 1024, heap_cost));
}
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![(
sysvar::epoch_schedule::id(),
create_account_for_test(&EpochSchedule::default()),
)];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let program_id = Pubkey::new_unique();
let env = Arc::new(BuiltinProgram::new_mock());
let program = ProgramCacheEntry {
program: ProgramCacheEntryType::Unloaded(env),
account_owner: ProgramCacheEntryOwner::LoaderV2,
account_size: 0,
deployment_slot: 0,
effective_slot: 0,
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
latest_access_slot: AtomicU64::new(0),
};
invoke_context
.program_cache_for_tx_batch
.replenish(program_id, Arc::new(program));
assert_matches!(
deploy_test_program(&mut invoke_context, program_id,),
Ok(())
);
let updated_program = invoke_context
.program_cache_for_tx_batch
.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![(
sysvar::epoch_schedule::id(),
create_account_for_test(&EpochSchedule::default()),
)];
with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
let program_id = Pubkey::new_unique();
let env = Arc::new(BuiltinProgram::new_mock());
let program = ProgramCacheEntry {
program: ProgramCacheEntryType::Unloaded(env),
account_owner: ProgramCacheEntryOwner::LoaderV2,
account_size: 0,
deployment_slot: 0,
effective_slot: 0,
tx_usage_counter: AtomicU64::new(100),
ix_usage_counter: AtomicU64::new(100),
latest_access_slot: AtomicU64::new(0),
};
invoke_context
.program_cache_for_tx_batch
.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
.program_cache_for_tx_batch
.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);
}
}