miraland_loader_v4_program/
lib.rs

1use {
2    miraland_measure::measure::Measure,
3    miraland_program_runtime::{
4        compute_budget::ComputeBudget,
5        ic_logger_msg,
6        invoke_context::InvokeContext,
7        loaded_programs::{
8            LoadProgramMetrics, LoadedProgram, LoadedProgramType, DELAY_VISIBILITY_SLOT_OFFSET,
9        },
10        log_collector::LogCollector,
11        stable_log,
12    },
13    solana_rbpf::{
14        aligned_memory::AlignedMemory,
15        declare_builtin_function, ebpf,
16        elf::Executable,
17        error::ProgramResult,
18        memory_region::{MemoryMapping, MemoryRegion},
19        program::{BuiltinProgram, FunctionRegistry},
20        vm::{Config, ContextObject, EbpfVm},
21    },
22    miraland_sdk::{
23        entrypoint::SUCCESS,
24        instruction::InstructionError,
25        loader_v4::{self, LoaderV4State, LoaderV4Status, DEPLOYMENT_COOLDOWN_IN_SLOTS},
26        loader_v4_instruction::LoaderV4Instruction,
27        program_utils::limited_deserialize,
28        pubkey::Pubkey,
29        saturating_add_assign,
30        transaction_context::{BorrowedAccount, InstructionContext},
31    },
32    std::{
33        cell::RefCell,
34        rc::Rc,
35        sync::{atomic::Ordering, Arc},
36    },
37};
38
39pub const DEFAULT_COMPUTE_UNITS: u64 = 2_000;
40
41pub fn get_state(data: &[u8]) -> Result<&LoaderV4State, InstructionError> {
42    unsafe {
43        let data = data
44            .get(0..LoaderV4State::program_data_offset())
45            .ok_or(InstructionError::AccountDataTooSmall)?
46            .try_into()
47            .unwrap();
48        Ok(std::mem::transmute::<
49            &[u8; LoaderV4State::program_data_offset()],
50            &LoaderV4State,
51        >(data))
52    }
53}
54
55fn get_state_mut(data: &mut [u8]) -> Result<&mut LoaderV4State, InstructionError> {
56    unsafe {
57        let data = data
58            .get_mut(0..LoaderV4State::program_data_offset())
59            .ok_or(InstructionError::AccountDataTooSmall)?
60            .try_into()
61            .unwrap();
62        Ok(std::mem::transmute::<
63            &mut [u8; LoaderV4State::program_data_offset()],
64            &mut LoaderV4State,
65        >(data))
66    }
67}
68
69pub fn create_program_runtime_environment_v2<'a>(
70    compute_budget: &ComputeBudget,
71    debugging_features: bool,
72) -> BuiltinProgram<InvokeContext<'a>> {
73    let config = Config {
74        max_call_depth: compute_budget.max_call_depth,
75        stack_frame_size: compute_budget.stack_frame_size,
76        enable_address_translation: true, // To be deactivated once we have BTF inference and verification
77        enable_stack_frame_gaps: false,
78        instruction_meter_checkpoint_distance: 10000,
79        enable_instruction_meter: true,
80        enable_instruction_tracing: debugging_features,
81        enable_symbol_and_section_labels: debugging_features,
82        reject_broken_elfs: true,
83        noop_instruction_rate: 256,
84        sanitize_user_provided_values: true,
85        external_internal_function_hash_collision: true,
86        reject_callx_r10: true,
87        enable_sbpf_v1: false,
88        enable_sbpf_v2: true,
89        optimize_rodata: true,
90        new_elf_parser: true,
91        aligned_memory_mapping: true,
92        // Warning, do not use `Config::default()` so that configuration here is explicit.
93    };
94    BuiltinProgram::new_loader(config, FunctionRegistry::default())
95}
96
97fn calculate_heap_cost(heap_size: u32, heap_cost: u64) -> u64 {
98    const KIBIBYTE: u64 = 1024;
99    const PAGE_SIZE_KB: u64 = 32;
100    u64::from(heap_size)
101        .saturating_add(PAGE_SIZE_KB.saturating_mul(KIBIBYTE).saturating_sub(1))
102        .checked_div(PAGE_SIZE_KB.saturating_mul(KIBIBYTE))
103        .expect("PAGE_SIZE_KB * KIBIBYTE > 0")
104        .saturating_sub(1)
105        .saturating_mul(heap_cost)
106}
107
108/// Create the SBF virtual machine
109pub fn create_vm<'a, 'b>(
110    invoke_context: &'a mut InvokeContext<'b>,
111    program: &'a Executable<InvokeContext<'b>>,
112) -> Result<EbpfVm<'a, InvokeContext<'b>>, Box<dyn std::error::Error>> {
113    let config = program.get_config();
114    let sbpf_version = program.get_sbpf_version();
115    let compute_budget = invoke_context.get_compute_budget();
116    let heap_size = compute_budget.heap_size;
117    invoke_context.consume_checked(calculate_heap_cost(heap_size, compute_budget.heap_cost))?;
118    let mut stack = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled(config.stack_size());
119    let mut heap = AlignedMemory::<{ ebpf::HOST_ALIGN }>::zero_filled(
120        usize::try_from(compute_budget.heap_size).unwrap(),
121    );
122    let stack_len = stack.len();
123    let regions: Vec<MemoryRegion> = vec![
124        program.get_ro_region(),
125        MemoryRegion::new_writable_gapped(stack.as_slice_mut(), ebpf::MM_STACK_START, 0),
126        MemoryRegion::new_writable(heap.as_slice_mut(), ebpf::MM_HEAP_START),
127    ];
128    let log_collector = invoke_context.get_log_collector();
129    let memory_mapping = MemoryMapping::new(regions, config, sbpf_version).map_err(|err| {
130        ic_logger_msg!(log_collector, "Failed to create SBF VM: {}", err);
131        Box::new(InstructionError::ProgramEnvironmentSetupFailure)
132    })?;
133    Ok(EbpfVm::new(
134        program.get_loader().clone(),
135        sbpf_version,
136        invoke_context,
137        memory_mapping,
138        stack_len,
139    ))
140}
141
142fn execute<'a, 'b: 'a>(
143    invoke_context: &'a mut InvokeContext<'b>,
144    executable: &'a Executable<InvokeContext<'static>>,
145) -> Result<(), Box<dyn std::error::Error>> {
146    // We dropped the lifetime tracking in the Executor by setting it to 'static,
147    // thus we need to reintroduce the correct lifetime of InvokeContext here again.
148    let executable =
149        unsafe { std::mem::transmute::<_, &'a Executable<InvokeContext<'b>>>(executable) };
150    let log_collector = invoke_context.get_log_collector();
151    let stack_height = invoke_context.get_stack_height();
152    let transaction_context = &invoke_context.transaction_context;
153    let instruction_context = transaction_context.get_current_instruction_context()?;
154    let program_id = *instruction_context.get_last_program_key(transaction_context)?;
155    #[cfg(any(target_os = "windows", not(target_arch = "x86_64")))]
156    let use_jit = false;
157    #[cfg(all(not(target_os = "windows"), target_arch = "x86_64"))]
158    let use_jit = executable.get_compiled_program().is_some();
159
160    let compute_meter_prev = invoke_context.get_remaining();
161    let mut create_vm_time = Measure::start("create_vm");
162    let mut vm = create_vm(invoke_context, executable)?;
163    create_vm_time.stop();
164
165    let mut execute_time = Measure::start("execute");
166    stable_log::program_invoke(&log_collector, &program_id, stack_height);
167    let (compute_units_consumed, result) = vm.execute_program(executable, !use_jit);
168    drop(vm);
169    ic_logger_msg!(
170        log_collector,
171        "Program {} consumed {} of {} compute units",
172        &program_id,
173        compute_units_consumed,
174        compute_meter_prev
175    );
176    execute_time.stop();
177
178    let timings = &mut invoke_context.timings;
179    timings.create_vm_us = timings.create_vm_us.saturating_add(create_vm_time.as_us());
180    timings.execute_us = timings.execute_us.saturating_add(execute_time.as_us());
181
182    match result {
183        ProgramResult::Ok(status) if status != SUCCESS => {
184            let error: InstructionError = status.into();
185            Err(error.into())
186        }
187        ProgramResult::Err(error) => Err(error.into()),
188        _ => Ok(()),
189    }
190}
191
192fn check_program_account(
193    log_collector: &Option<Rc<RefCell<LogCollector>>>,
194    instruction_context: &InstructionContext,
195    program: &BorrowedAccount,
196    authority_address: &Pubkey,
197) -> Result<LoaderV4State, InstructionError> {
198    if !loader_v4::check_id(program.get_owner()) {
199        ic_logger_msg!(log_collector, "Program not owned by loader");
200        return Err(InstructionError::InvalidAccountOwner);
201    }
202    if program.get_data().is_empty() {
203        ic_logger_msg!(log_collector, "Program is uninitialized");
204        return Err(InstructionError::InvalidAccountData);
205    }
206    let state = get_state(program.get_data())?;
207    if !program.is_writable() {
208        ic_logger_msg!(log_collector, "Program is not writeable");
209        return Err(InstructionError::InvalidArgument);
210    }
211    if !instruction_context.is_instruction_account_signer(1)? {
212        ic_logger_msg!(log_collector, "Authority did not sign");
213        return Err(InstructionError::MissingRequiredSignature);
214    }
215    if state.authority_address != *authority_address {
216        ic_logger_msg!(log_collector, "Incorrect authority provided");
217        return Err(InstructionError::IncorrectAuthority);
218    }
219    if matches!(state.status, LoaderV4Status::Finalized) {
220        ic_logger_msg!(log_collector, "Program is finalized");
221        return Err(InstructionError::Immutable);
222    }
223    Ok(*state)
224}
225
226pub fn process_instruction_write(
227    invoke_context: &mut InvokeContext,
228    offset: u32,
229    bytes: Vec<u8>,
230) -> Result<(), InstructionError> {
231    let log_collector = invoke_context.get_log_collector();
232    let transaction_context = &invoke_context.transaction_context;
233    let instruction_context = transaction_context.get_current_instruction_context()?;
234    let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
235    let authority_address = instruction_context
236        .get_index_of_instruction_account_in_transaction(1)
237        .and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
238    let state = check_program_account(
239        &log_collector,
240        instruction_context,
241        &program,
242        authority_address,
243    )?;
244    if !matches!(state.status, LoaderV4Status::Retracted) {
245        ic_logger_msg!(log_collector, "Program is not retracted");
246        return Err(InstructionError::InvalidArgument);
247    }
248    let end_offset = (offset as usize).saturating_add(bytes.len());
249    program
250        .get_data_mut(&invoke_context.feature_set)?
251        .get_mut(
252            LoaderV4State::program_data_offset().saturating_add(offset as usize)
253                ..LoaderV4State::program_data_offset().saturating_add(end_offset),
254        )
255        .ok_or_else(|| {
256            ic_logger_msg!(log_collector, "Write out of bounds");
257            InstructionError::AccountDataTooSmall
258        })?
259        .copy_from_slice(&bytes);
260    Ok(())
261}
262
263pub fn process_instruction_truncate(
264    invoke_context: &mut InvokeContext,
265    new_size: u32,
266) -> Result<(), InstructionError> {
267    let log_collector = invoke_context.get_log_collector();
268    let transaction_context = &invoke_context.transaction_context;
269    let instruction_context = transaction_context.get_current_instruction_context()?;
270    let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
271    let authority_address = instruction_context
272        .get_index_of_instruction_account_in_transaction(1)
273        .and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
274    let is_initialization =
275        new_size > 0 && program.get_data().len() < LoaderV4State::program_data_offset();
276    if is_initialization {
277        if !loader_v4::check_id(program.get_owner()) {
278            ic_logger_msg!(log_collector, "Program not owned by loader");
279            return Err(InstructionError::InvalidAccountOwner);
280        }
281        if !program.is_writable() {
282            ic_logger_msg!(log_collector, "Program is not writeable");
283            return Err(InstructionError::InvalidArgument);
284        }
285        if !program.is_signer() {
286            ic_logger_msg!(log_collector, "Program did not sign");
287            return Err(InstructionError::MissingRequiredSignature);
288        }
289        if !instruction_context.is_instruction_account_signer(1)? {
290            ic_logger_msg!(log_collector, "Authority did not sign");
291            return Err(InstructionError::MissingRequiredSignature);
292        }
293    } else {
294        let state = check_program_account(
295            &log_collector,
296            instruction_context,
297            &program,
298            authority_address,
299        )?;
300        if !matches!(state.status, LoaderV4Status::Retracted) {
301            ic_logger_msg!(log_collector, "Program is not retracted");
302            return Err(InstructionError::InvalidArgument);
303        }
304    }
305    let required_lamports = if new_size == 0 {
306        0
307    } else {
308        let rent = invoke_context.get_sysvar_cache().get_rent()?;
309        rent.minimum_balance(LoaderV4State::program_data_offset().saturating_add(new_size as usize))
310    };
311    match program.get_lamports().cmp(&required_lamports) {
312        std::cmp::Ordering::Less => {
313            ic_logger_msg!(
314                log_collector,
315                "Insufficient lamports, {} are required",
316                required_lamports
317            );
318            return Err(InstructionError::InsufficientFunds);
319        }
320        std::cmp::Ordering::Greater => {
321            let mut recipient =
322                instruction_context.try_borrow_instruction_account(transaction_context, 2)?;
323            if !instruction_context.is_instruction_account_writable(2)? {
324                ic_logger_msg!(log_collector, "Recipient is not writeable");
325                return Err(InstructionError::InvalidArgument);
326            }
327            let lamports_to_receive = program.get_lamports().saturating_sub(required_lamports);
328            program.checked_sub_lamports(lamports_to_receive, &invoke_context.feature_set)?;
329            recipient.checked_add_lamports(lamports_to_receive, &invoke_context.feature_set)?;
330        }
331        std::cmp::Ordering::Equal => {}
332    }
333    if new_size == 0 {
334        program.set_data_length(0, &invoke_context.feature_set)?;
335    } else {
336        program.set_data_length(
337            LoaderV4State::program_data_offset().saturating_add(new_size as usize),
338            &invoke_context.feature_set,
339        )?;
340        if is_initialization {
341            let state = get_state_mut(program.get_data_mut(&invoke_context.feature_set)?)?;
342            state.slot = 0;
343            state.status = LoaderV4Status::Retracted;
344            state.authority_address = *authority_address;
345        }
346    }
347    Ok(())
348}
349
350pub fn process_instruction_deploy(
351    invoke_context: &mut InvokeContext,
352) -> Result<(), InstructionError> {
353    let log_collector = invoke_context.get_log_collector();
354    let transaction_context = &invoke_context.transaction_context;
355    let instruction_context = transaction_context.get_current_instruction_context()?;
356    let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
357    let authority_address = instruction_context
358        .get_index_of_instruction_account_in_transaction(1)
359        .and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
360    let source_program = instruction_context
361        .try_borrow_instruction_account(transaction_context, 2)
362        .ok();
363    let state = check_program_account(
364        &log_collector,
365        instruction_context,
366        &program,
367        authority_address,
368    )?;
369    let current_slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
370
371    // Slot = 0 indicates that the program hasn't been deployed yet. So no need to check for the cooldown slots.
372    // (Without this check, the program deployment is failing in freshly started test validators. That's
373    //  because at startup current_slot is 0, which is < DEPLOYMENT_COOLDOWN_IN_SLOTS).
374    if state.slot != 0 && state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot {
375        ic_logger_msg!(
376            log_collector,
377            "Program was deployed recently, cooldown still in effect"
378        );
379        return Err(InstructionError::InvalidArgument);
380    }
381    if !matches!(state.status, LoaderV4Status::Retracted) {
382        ic_logger_msg!(log_collector, "Destination program is not retracted");
383        return Err(InstructionError::InvalidArgument);
384    }
385    let buffer = if let Some(ref source_program) = source_program {
386        let source_state = check_program_account(
387            &log_collector,
388            instruction_context,
389            source_program,
390            authority_address,
391        )?;
392        if !matches!(source_state.status, LoaderV4Status::Retracted) {
393            ic_logger_msg!(log_collector, "Source program is not retracted");
394            return Err(InstructionError::InvalidArgument);
395        }
396        source_program
397    } else {
398        &program
399    };
400
401    let programdata = buffer
402        .get_data()
403        .get(LoaderV4State::program_data_offset()..)
404        .ok_or(InstructionError::AccountDataTooSmall)?;
405
406    let deployment_slot = state.slot;
407    let effective_slot = deployment_slot.saturating_add(DELAY_VISIBILITY_SLOT_OFFSET);
408
409    let mut load_program_metrics = LoadProgramMetrics {
410        program_id: buffer.get_key().to_string(),
411        ..LoadProgramMetrics::default()
412    };
413    let executor = LoadedProgram::new(
414        &loader_v4::id(),
415        invoke_context
416            .programs_modified_by_tx
417            .environments
418            .program_runtime_v2
419            .clone(),
420        deployment_slot,
421        effective_slot,
422        programdata,
423        buffer.get_data().len(),
424        &mut load_program_metrics,
425    )
426    .map_err(|err| {
427        ic_logger_msg!(log_collector, "{}", err);
428        InstructionError::InvalidAccountData
429    })?;
430    load_program_metrics.submit_datapoint(&mut invoke_context.timings);
431    if let Some(mut source_program) = source_program {
432        let rent = invoke_context.get_sysvar_cache().get_rent()?;
433        let required_lamports = rent.minimum_balance(source_program.get_data().len());
434        let transfer_lamports = required_lamports.saturating_sub(program.get_lamports());
435        program.set_data_from_slice(source_program.get_data(), &invoke_context.feature_set)?;
436        source_program.set_data_length(0, &invoke_context.feature_set)?;
437        source_program.checked_sub_lamports(transfer_lamports, &invoke_context.feature_set)?;
438        program.checked_add_lamports(transfer_lamports, &invoke_context.feature_set)?;
439    }
440    let state = get_state_mut(program.get_data_mut(&invoke_context.feature_set)?)?;
441    state.slot = current_slot;
442    state.status = LoaderV4Status::Deployed;
443
444    if let Some(old_entry) = invoke_context.find_program_in_cache(program.get_key()) {
445        executor.tx_usage_counter.store(
446            old_entry.tx_usage_counter.load(Ordering::Relaxed),
447            Ordering::Relaxed,
448        );
449        executor.ix_usage_counter.store(
450            old_entry.ix_usage_counter.load(Ordering::Relaxed),
451            Ordering::Relaxed,
452        );
453    }
454    invoke_context
455        .programs_modified_by_tx
456        .replenish(*program.get_key(), Arc::new(executor));
457    Ok(())
458}
459
460pub fn process_instruction_retract(
461    invoke_context: &mut InvokeContext,
462) -> Result<(), InstructionError> {
463    let log_collector = invoke_context.get_log_collector();
464    let transaction_context = &invoke_context.transaction_context;
465    let instruction_context = transaction_context.get_current_instruction_context()?;
466    let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
467
468    let authority_address = instruction_context
469        .get_index_of_instruction_account_in_transaction(1)
470        .and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
471    let state = check_program_account(
472        &log_collector,
473        instruction_context,
474        &program,
475        authority_address,
476    )?;
477    let current_slot = invoke_context.get_sysvar_cache().get_clock()?.slot;
478    if state.slot.saturating_add(DEPLOYMENT_COOLDOWN_IN_SLOTS) > current_slot {
479        ic_logger_msg!(
480            log_collector,
481            "Program was deployed recently, cooldown still in effect"
482        );
483        return Err(InstructionError::InvalidArgument);
484    }
485    if matches!(state.status, LoaderV4Status::Retracted) {
486        ic_logger_msg!(log_collector, "Program is not deployed");
487        return Err(InstructionError::InvalidArgument);
488    }
489    let state = get_state_mut(program.get_data_mut(&invoke_context.feature_set)?)?;
490    state.status = LoaderV4Status::Retracted;
491    Ok(())
492}
493
494pub fn process_instruction_transfer_authority(
495    invoke_context: &mut InvokeContext,
496) -> Result<(), InstructionError> {
497    let log_collector = invoke_context.get_log_collector();
498    let transaction_context = &invoke_context.transaction_context;
499    let instruction_context = transaction_context.get_current_instruction_context()?;
500    let mut program = instruction_context.try_borrow_instruction_account(transaction_context, 0)?;
501    let authority_address = instruction_context
502        .get_index_of_instruction_account_in_transaction(1)
503        .and_then(|index| transaction_context.get_key_of_account_at_index(index))?;
504    let new_authority_address = instruction_context
505        .get_index_of_instruction_account_in_transaction(2)
506        .and_then(|index| transaction_context.get_key_of_account_at_index(index))
507        .ok()
508        .cloned();
509    let _state = check_program_account(
510        &log_collector,
511        instruction_context,
512        &program,
513        authority_address,
514    )?;
515    if new_authority_address.is_some() && !instruction_context.is_instruction_account_signer(2)? {
516        ic_logger_msg!(log_collector, "New authority did not sign");
517        return Err(InstructionError::MissingRequiredSignature);
518    }
519    let state = get_state_mut(program.get_data_mut(&invoke_context.feature_set)?)?;
520    if let Some(new_authority_address) = new_authority_address {
521        state.authority_address = new_authority_address;
522    } else if matches!(state.status, LoaderV4Status::Deployed) {
523        state.status = LoaderV4Status::Finalized;
524    } else {
525        ic_logger_msg!(log_collector, "Program must be deployed to be finalized");
526        return Err(InstructionError::InvalidArgument);
527    }
528    Ok(())
529}
530
531declare_builtin_function!(
532    Entrypoint,
533    fn rust(
534        invoke_context: &mut InvokeContext,
535        _arg0: u64,
536        _arg1: u64,
537        _arg2: u64,
538        _arg3: u64,
539        _arg4: u64,
540        _memory_mapping: &mut MemoryMapping,
541    ) -> Result<u64, Box<dyn std::error::Error>> {
542        process_instruction_inner(invoke_context)
543    }
544);
545
546pub fn process_instruction_inner(
547    invoke_context: &mut InvokeContext,
548) -> Result<u64, Box<dyn std::error::Error>> {
549    let log_collector = invoke_context.get_log_collector();
550    let transaction_context = &invoke_context.transaction_context;
551    let instruction_context = transaction_context.get_current_instruction_context()?;
552    let instruction_data = instruction_context.get_instruction_data();
553    let program_id = instruction_context.get_last_program_key(transaction_context)?;
554    if loader_v4::check_id(program_id) {
555        invoke_context.consume_checked(DEFAULT_COMPUTE_UNITS)?;
556        match limited_deserialize(instruction_data)? {
557            LoaderV4Instruction::Write { offset, bytes } => {
558                process_instruction_write(invoke_context, offset, bytes)
559            }
560            LoaderV4Instruction::Truncate { new_size } => {
561                process_instruction_truncate(invoke_context, new_size)
562            }
563            LoaderV4Instruction::Deploy => process_instruction_deploy(invoke_context),
564            LoaderV4Instruction::Retract => process_instruction_retract(invoke_context),
565            LoaderV4Instruction::TransferAuthority => {
566                process_instruction_transfer_authority(invoke_context)
567            }
568        }
569        .map_err(|err| Box::new(err) as Box<dyn std::error::Error>)
570    } else {
571        let program = instruction_context.try_borrow_last_program_account(transaction_context)?;
572        if !loader_v4::check_id(program.get_owner()) {
573            ic_logger_msg!(log_collector, "Program not owned by loader");
574            return Err(Box::new(InstructionError::InvalidAccountOwner));
575        }
576        if program.get_data().is_empty() {
577            ic_logger_msg!(log_collector, "Program is uninitialized");
578            return Err(Box::new(InstructionError::InvalidAccountData));
579        }
580        let state = get_state(program.get_data())?;
581        if matches!(state.status, LoaderV4Status::Retracted) {
582            ic_logger_msg!(log_collector, "Program is not deployed");
583            return Err(Box::new(InstructionError::InvalidArgument));
584        }
585        let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
586        let loaded_program = invoke_context
587            .find_program_in_cache(program.get_key())
588            .ok_or_else(|| {
589                ic_logger_msg!(log_collector, "Program is not cached");
590                InstructionError::InvalidAccountData
591            })?;
592        get_or_create_executor_time.stop();
593        saturating_add_assign!(
594            invoke_context.timings.get_or_create_executor_us,
595            get_or_create_executor_time.as_us()
596        );
597        drop(program);
598        loaded_program
599            .ix_usage_counter
600            .fetch_add(1, Ordering::Relaxed);
601        match &loaded_program.program {
602            LoadedProgramType::FailedVerification(_)
603            | LoadedProgramType::Closed
604            | LoadedProgramType::DelayVisibility => {
605                ic_logger_msg!(log_collector, "Program is not deployed");
606                Err(Box::new(InstructionError::InvalidAccountData) as Box<dyn std::error::Error>)
607            }
608            LoadedProgramType::Typed(executable) => execute(invoke_context, executable),
609            _ => Err(Box::new(InstructionError::IncorrectProgramId) as Box<dyn std::error::Error>),
610        }
611    }
612    .map(|_| 0)
613}
614
615#[cfg(test)]
616mod tests {
617    use {
618        super::*,
619        miraland_program_runtime::invoke_context::mock_process_instruction,
620        miraland_sdk::{
621            account::{
622                create_account_shared_data_for_test, AccountSharedData, ReadableAccount,
623                WritableAccount,
624            },
625            instruction::AccountMeta,
626            slot_history::Slot,
627            sysvar::{clock, rent},
628            transaction_context::IndexOfAccount,
629        },
630        std::{fs::File, io::Read, path::Path},
631    };
632
633    pub fn load_all_invoked_programs(invoke_context: &mut InvokeContext) {
634        let mut load_program_metrics = LoadProgramMetrics::default();
635        let num_accounts = invoke_context.transaction_context.get_number_of_accounts();
636        for index in 0..num_accounts {
637            let account = invoke_context
638                .transaction_context
639                .get_account_at_index(index)
640                .expect("Failed to get the account")
641                .borrow();
642
643            let owner = account.owner();
644            if loader_v4::check_id(owner) {
645                let pubkey = invoke_context
646                    .transaction_context
647                    .get_key_of_account_at_index(index)
648                    .expect("Failed to get account key");
649
650                if let Some(programdata) =
651                    account.data().get(LoaderV4State::program_data_offset()..)
652                {
653                    if let Ok(loaded_program) = LoadedProgram::new(
654                        &loader_v4::id(),
655                        invoke_context
656                            .programs_modified_by_tx
657                            .environments
658                            .program_runtime_v2
659                            .clone(),
660                        0,
661                        0,
662                        programdata,
663                        account.data().len(),
664                        &mut load_program_metrics,
665                    ) {
666                        invoke_context.programs_modified_by_tx.set_slot_for_tests(0);
667                        invoke_context
668                            .programs_modified_by_tx
669                            .replenish(*pubkey, Arc::new(loaded_program));
670                    }
671                }
672            }
673        }
674    }
675
676    fn process_instruction(
677        program_indices: Vec<IndexOfAccount>,
678        instruction_data: &[u8],
679        transaction_accounts: Vec<(Pubkey, AccountSharedData)>,
680        instruction_accounts: &[(IndexOfAccount, bool, bool)],
681        expected_result: Result<(), InstructionError>,
682    ) -> Vec<AccountSharedData> {
683        let instruction_accounts = instruction_accounts
684            .iter()
685            .map(
686                |(index_in_transaction, is_signer, is_writable)| AccountMeta {
687                    pubkey: transaction_accounts[*index_in_transaction as usize].0,
688                    is_signer: *is_signer,
689                    is_writable: *is_writable,
690                },
691            )
692            .collect::<Vec<_>>();
693        mock_process_instruction(
694            &loader_v4::id(),
695            program_indices,
696            instruction_data,
697            transaction_accounts,
698            instruction_accounts,
699            expected_result,
700            Entrypoint::vm,
701            |invoke_context| {
702                invoke_context
703                    .programs_modified_by_tx
704                    .environments
705                    .program_runtime_v2 = Arc::new(create_program_runtime_environment_v2(
706                    &ComputeBudget::default(),
707                    false,
708                ));
709                load_all_invoked_programs(invoke_context);
710            },
711            |_invoke_context| {},
712        )
713    }
714
715    fn load_program_account_from_elf(
716        authority_address: Pubkey,
717        status: LoaderV4Status,
718        path: &str,
719    ) -> AccountSharedData {
720        let path = Path::new("test_elfs/out/").join(path).with_extension("so");
721        let mut file = File::open(path).expect("file open failed");
722        let mut elf_bytes = Vec::new();
723        file.read_to_end(&mut elf_bytes).unwrap();
724        let rent = rent::Rent::default();
725        let account_size =
726            loader_v4::LoaderV4State::program_data_offset().saturating_add(elf_bytes.len());
727        let mut program_account = AccountSharedData::new(
728            rent.minimum_balance(account_size),
729            account_size,
730            &loader_v4::id(),
731        );
732        let state = get_state_mut(program_account.data_as_mut_slice()).unwrap();
733        state.slot = 0;
734        state.authority_address = authority_address;
735        state.status = status;
736        program_account.data_as_mut_slice()[loader_v4::LoaderV4State::program_data_offset()..]
737            .copy_from_slice(&elf_bytes);
738        program_account
739    }
740
741    fn clock(slot: Slot) -> AccountSharedData {
742        let clock = clock::Clock {
743            slot,
744            ..clock::Clock::default()
745        };
746        create_account_shared_data_for_test(&clock)
747    }
748
749    fn test_loader_instruction_general_errors(instruction: LoaderV4Instruction) {
750        let instruction = bincode::serialize(&instruction).unwrap();
751        let authority_address = Pubkey::new_unique();
752        let transaction_accounts = vec![
753            (
754                Pubkey::new_unique(),
755                load_program_account_from_elf(
756                    authority_address,
757                    LoaderV4Status::Deployed,
758                    "relative_call",
759                ),
760            ),
761            (
762                authority_address,
763                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
764            ),
765            (
766                Pubkey::new_unique(),
767                load_program_account_from_elf(
768                    authority_address,
769                    LoaderV4Status::Finalized,
770                    "relative_call",
771                ),
772            ),
773            (
774                clock::id(),
775                create_account_shared_data_for_test(&clock::Clock::default()),
776            ),
777            (
778                rent::id(),
779                create_account_shared_data_for_test(&rent::Rent::default()),
780            ),
781        ];
782
783        // Error: Missing program account
784        process_instruction(
785            vec![],
786            &instruction,
787            transaction_accounts.clone(),
788            &[],
789            Err(InstructionError::NotEnoughAccountKeys),
790        );
791
792        // Error: Missing authority account
793        process_instruction(
794            vec![],
795            &instruction,
796            transaction_accounts.clone(),
797            &[(0, false, true)],
798            Err(InstructionError::NotEnoughAccountKeys),
799        );
800
801        // Error: Program not owned by loader
802        process_instruction(
803            vec![],
804            &instruction,
805            transaction_accounts.clone(),
806            &[(1, false, true), (1, true, false), (2, true, true)],
807            Err(InstructionError::InvalidAccountOwner),
808        );
809
810        // Error: Program is not writeable
811        process_instruction(
812            vec![],
813            &instruction,
814            transaction_accounts.clone(),
815            &[(0, false, false), (1, true, false), (2, true, true)],
816            Err(InstructionError::InvalidArgument),
817        );
818
819        // Error: Authority did not sign
820        process_instruction(
821            vec![],
822            &instruction,
823            transaction_accounts.clone(),
824            &[(0, false, true), (1, false, false), (2, true, true)],
825            Err(InstructionError::MissingRequiredSignature),
826        );
827
828        // Error: Program is finalized
829        process_instruction(
830            vec![],
831            &instruction,
832            transaction_accounts.clone(),
833            &[(2, false, true), (1, true, false), (0, true, true)],
834            Err(InstructionError::Immutable),
835        );
836
837        // Error: Incorrect authority provided
838        process_instruction(
839            vec![],
840            &instruction,
841            transaction_accounts,
842            &[(0, false, true), (2, true, false), (2, true, true)],
843            Err(InstructionError::IncorrectAuthority),
844        );
845    }
846
847    #[test]
848    fn test_loader_instruction_write() {
849        let authority_address = Pubkey::new_unique();
850        let transaction_accounts = vec![
851            (
852                Pubkey::new_unique(),
853                load_program_account_from_elf(
854                    authority_address,
855                    LoaderV4Status::Retracted,
856                    "relative_call",
857                ),
858            ),
859            (
860                authority_address,
861                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
862            ),
863            (
864                Pubkey::new_unique(),
865                load_program_account_from_elf(
866                    authority_address,
867                    LoaderV4Status::Deployed,
868                    "relative_call",
869                ),
870            ),
871            (
872                clock::id(),
873                create_account_shared_data_for_test(&clock::Clock::default()),
874            ),
875            (
876                rent::id(),
877                create_account_shared_data_for_test(&rent::Rent::default()),
878            ),
879        ];
880
881        // Overwrite existing data
882        process_instruction(
883            vec![],
884            &bincode::serialize(&LoaderV4Instruction::Write {
885                offset: 2,
886                bytes: vec![8, 8, 8, 8],
887            })
888            .unwrap(),
889            transaction_accounts.clone(),
890            &[(0, false, true), (1, true, false)],
891            Ok(()),
892        );
893
894        // Empty write
895        process_instruction(
896            vec![],
897            &bincode::serialize(&LoaderV4Instruction::Write {
898                offset: 2,
899                bytes: Vec::new(),
900            })
901            .unwrap(),
902            transaction_accounts.clone(),
903            &[(0, false, true), (1, true, false)],
904            Ok(()),
905        );
906
907        // Error: Program is not retracted
908        process_instruction(
909            vec![],
910            &bincode::serialize(&LoaderV4Instruction::Write {
911                offset: 8,
912                bytes: vec![8, 8, 8, 8],
913            })
914            .unwrap(),
915            transaction_accounts.clone(),
916            &[(2, false, true), (1, true, false)],
917            Err(InstructionError::InvalidArgument),
918        );
919
920        // Error: Write out of bounds
921        process_instruction(
922            vec![],
923            &bincode::serialize(&LoaderV4Instruction::Write {
924                offset: transaction_accounts[0]
925                    .1
926                    .data()
927                    .len()
928                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
929                    .saturating_sub(3) as u32,
930                bytes: vec![8, 8, 8, 8],
931            })
932            .unwrap(),
933            transaction_accounts.clone(),
934            &[(0, false, true), (1, true, false)],
935            Err(InstructionError::AccountDataTooSmall),
936        );
937
938        test_loader_instruction_general_errors(LoaderV4Instruction::Write {
939            offset: 0,
940            bytes: Vec::new(),
941        });
942    }
943
944    #[test]
945    fn test_loader_instruction_truncate() {
946        let authority_address = Pubkey::new_unique();
947        let mut transaction_accounts = vec![
948            (
949                Pubkey::new_unique(),
950                load_program_account_from_elf(
951                    authority_address,
952                    LoaderV4Status::Retracted,
953                    "relative_call",
954                ),
955            ),
956            (
957                authority_address,
958                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
959            ),
960            (
961                Pubkey::new_unique(),
962                AccountSharedData::new(0, 0, &loader_v4::id()),
963            ),
964            (
965                Pubkey::new_unique(),
966                AccountSharedData::new(40000000, 0, &loader_v4::id()),
967            ),
968            (
969                Pubkey::new_unique(),
970                load_program_account_from_elf(
971                    authority_address,
972                    LoaderV4Status::Retracted,
973                    "rodata_section",
974                ),
975            ),
976            (
977                Pubkey::new_unique(),
978                load_program_account_from_elf(
979                    authority_address,
980                    LoaderV4Status::Deployed,
981                    "relative_call",
982                ),
983            ),
984            (
985                clock::id(),
986                create_account_shared_data_for_test(&clock::Clock::default()),
987            ),
988            (
989                rent::id(),
990                create_account_shared_data_for_test(&rent::Rent::default()),
991            ),
992        ];
993
994        // No change
995        let accounts = process_instruction(
996            vec![],
997            &bincode::serialize(&LoaderV4Instruction::Truncate {
998                new_size: transaction_accounts[0]
999                    .1
1000                    .data()
1001                    .len()
1002                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
1003                    as u32,
1004            })
1005            .unwrap(),
1006            transaction_accounts.clone(),
1007            &[(0, false, true), (1, true, false)],
1008            Ok(()),
1009        );
1010        assert_eq!(
1011            accounts[0].data().len(),
1012            transaction_accounts[0].1.data().len(),
1013        );
1014        assert_eq!(accounts[2].lamports(), transaction_accounts[2].1.lamports());
1015        let lamports = transaction_accounts[4].1.lamports();
1016        transaction_accounts[0].1.set_lamports(lamports);
1017
1018        // Initialize program account
1019        let accounts = process_instruction(
1020            vec![],
1021            &bincode::serialize(&LoaderV4Instruction::Truncate {
1022                new_size: transaction_accounts[0]
1023                    .1
1024                    .data()
1025                    .len()
1026                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
1027                    as u32,
1028            })
1029            .unwrap(),
1030            transaction_accounts.clone(),
1031            &[(3, true, true), (1, true, false), (2, false, true)],
1032            Ok(()),
1033        );
1034        assert_eq!(
1035            accounts[3].data().len(),
1036            transaction_accounts[0].1.data().len(),
1037        );
1038
1039        // Increase program account size
1040        let accounts = process_instruction(
1041            vec![],
1042            &bincode::serialize(&LoaderV4Instruction::Truncate {
1043                new_size: transaction_accounts[4]
1044                    .1
1045                    .data()
1046                    .len()
1047                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
1048                    as u32,
1049            })
1050            .unwrap(),
1051            transaction_accounts.clone(),
1052            &[(0, false, true), (1, true, false)],
1053            Ok(()),
1054        );
1055        assert_eq!(
1056            accounts[0].data().len(),
1057            transaction_accounts[4].1.data().len(),
1058        );
1059
1060        // Decrease program account size
1061        let accounts = process_instruction(
1062            vec![],
1063            &bincode::serialize(&LoaderV4Instruction::Truncate {
1064                new_size: transaction_accounts[0]
1065                    .1
1066                    .data()
1067                    .len()
1068                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
1069                    as u32,
1070            })
1071            .unwrap(),
1072            transaction_accounts.clone(),
1073            &[(4, false, true), (1, true, false), (2, false, true)],
1074            Ok(()),
1075        );
1076        assert_eq!(
1077            accounts[4].data().len(),
1078            transaction_accounts[0].1.data().len(),
1079        );
1080        assert_eq!(
1081            accounts[2].lamports(),
1082            transaction_accounts[2].1.lamports().saturating_add(
1083                transaction_accounts[4]
1084                    .1
1085                    .lamports()
1086                    .saturating_sub(accounts[4].lamports())
1087            ),
1088        );
1089
1090        // Close program account
1091        let accounts = process_instruction(
1092            vec![],
1093            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
1094            transaction_accounts.clone(),
1095            &[(0, false, true), (1, true, false), (2, false, true)],
1096            Ok(()),
1097        );
1098        assert_eq!(accounts[0].data().len(), 0);
1099        assert_eq!(
1100            accounts[2].lamports(),
1101            transaction_accounts[2].1.lamports().saturating_add(
1102                transaction_accounts[0]
1103                    .1
1104                    .lamports()
1105                    .saturating_sub(accounts[0].lamports())
1106            ),
1107        );
1108
1109        // Error: Program not owned by loader
1110        process_instruction(
1111            vec![],
1112            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
1113            transaction_accounts.clone(),
1114            &[(1, false, true), (1, true, false), (2, true, true)],
1115            Err(InstructionError::InvalidAccountOwner),
1116        );
1117
1118        // Error: Program is not writeable
1119        process_instruction(
1120            vec![],
1121            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
1122            transaction_accounts.clone(),
1123            &[(3, false, false), (1, true, false), (2, true, true)],
1124            Err(InstructionError::InvalidArgument),
1125        );
1126
1127        // Error: Program did not sign
1128        process_instruction(
1129            vec![],
1130            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
1131            transaction_accounts.clone(),
1132            &[(3, false, true), (1, true, false), (2, true, true)],
1133            Err(InstructionError::MissingRequiredSignature),
1134        );
1135
1136        // Error: Authority did not sign
1137        process_instruction(
1138            vec![],
1139            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
1140            transaction_accounts.clone(),
1141            &[(3, true, true), (1, false, false), (2, true, true)],
1142            Err(InstructionError::MissingRequiredSignature),
1143        );
1144
1145        // Error: Program is and stays uninitialized
1146        process_instruction(
1147            vec![],
1148            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
1149            transaction_accounts.clone(),
1150            &[(3, false, true), (1, true, false), (2, true, true)],
1151            Err(InstructionError::InvalidAccountData),
1152        );
1153
1154        // Error: Program is not retracted
1155        process_instruction(
1156            vec![],
1157            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 8 }).unwrap(),
1158            transaction_accounts.clone(),
1159            &[(5, false, true), (1, true, false), (2, false, true)],
1160            Err(InstructionError::InvalidArgument),
1161        );
1162
1163        // Error: Missing recipient account
1164        process_instruction(
1165            vec![],
1166            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
1167            transaction_accounts.clone(),
1168            &[(0, true, true), (1, true, false)],
1169            Err(InstructionError::NotEnoughAccountKeys),
1170        );
1171
1172        // Error: Recipient is not writeable
1173        process_instruction(
1174            vec![],
1175            &bincode::serialize(&LoaderV4Instruction::Truncate { new_size: 0 }).unwrap(),
1176            transaction_accounts.clone(),
1177            &[(0, false, true), (1, true, false), (2, false, false)],
1178            Err(InstructionError::InvalidArgument),
1179        );
1180
1181        // Error: Insufficient funds
1182        process_instruction(
1183            vec![],
1184            &bincode::serialize(&LoaderV4Instruction::Truncate {
1185                new_size: transaction_accounts[4]
1186                    .1
1187                    .data()
1188                    .len()
1189                    .saturating_sub(loader_v4::LoaderV4State::program_data_offset())
1190                    .saturating_add(1) as u32,
1191            })
1192            .unwrap(),
1193            transaction_accounts.clone(),
1194            &[(0, false, true), (1, true, false)],
1195            Err(InstructionError::InsufficientFunds),
1196        );
1197
1198        test_loader_instruction_general_errors(LoaderV4Instruction::Truncate { new_size: 0 });
1199    }
1200
1201    #[test]
1202    fn test_loader_instruction_deploy() {
1203        let authority_address = Pubkey::new_unique();
1204        let mut transaction_accounts = vec![
1205            (
1206                Pubkey::new_unique(),
1207                load_program_account_from_elf(
1208                    authority_address,
1209                    LoaderV4Status::Retracted,
1210                    "rodata_section",
1211                ),
1212            ),
1213            (
1214                authority_address,
1215                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
1216            ),
1217            (
1218                Pubkey::new_unique(),
1219                load_program_account_from_elf(
1220                    authority_address,
1221                    LoaderV4Status::Retracted,
1222                    "relative_call",
1223                ),
1224            ),
1225            (
1226                Pubkey::new_unique(),
1227                AccountSharedData::new(0, 0, &loader_v4::id()),
1228            ),
1229            (
1230                Pubkey::new_unique(),
1231                load_program_account_from_elf(
1232                    authority_address,
1233                    LoaderV4Status::Retracted,
1234                    "invalid",
1235                ),
1236            ),
1237            (clock::id(), clock(1000)),
1238            (
1239                rent::id(),
1240                create_account_shared_data_for_test(&rent::Rent::default()),
1241            ),
1242        ];
1243
1244        // Deploy from its own data
1245        let accounts = process_instruction(
1246            vec![],
1247            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1248            transaction_accounts.clone(),
1249            &[(0, false, true), (1, true, false)],
1250            Ok(()),
1251        );
1252        transaction_accounts[0].1 = accounts[0].clone();
1253        transaction_accounts[5].1 = clock(2000);
1254        assert_eq!(
1255            accounts[0].data().len(),
1256            transaction_accounts[0].1.data().len(),
1257        );
1258        assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
1259
1260        // Error: Source program is not writable
1261        process_instruction(
1262            vec![],
1263            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1264            transaction_accounts.clone(),
1265            &[(0, false, true), (1, true, false), (2, false, false)],
1266            Err(InstructionError::InvalidArgument),
1267        );
1268
1269        // Error: Source program is not retracted
1270        process_instruction(
1271            vec![],
1272            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1273            transaction_accounts.clone(),
1274            &[(2, false, true), (1, true, false), (0, false, true)],
1275            Err(InstructionError::InvalidArgument),
1276        );
1277
1278        // Redeploy: Retract, then replace data by other source
1279        let accounts = process_instruction(
1280            vec![],
1281            &bincode::serialize(&LoaderV4Instruction::Retract).unwrap(),
1282            transaction_accounts.clone(),
1283            &[(0, false, true), (1, true, false)],
1284            Ok(()),
1285        );
1286        transaction_accounts[0].1 = accounts[0].clone();
1287        let accounts = process_instruction(
1288            vec![],
1289            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1290            transaction_accounts.clone(),
1291            &[(0, false, true), (1, true, false), (2, false, true)],
1292            Ok(()),
1293        );
1294        transaction_accounts[0].1 = accounts[0].clone();
1295        assert_eq!(
1296            accounts[0].data().len(),
1297            transaction_accounts[2].1.data().len(),
1298        );
1299        assert_eq!(accounts[2].data().len(), 0,);
1300        assert_eq!(
1301            accounts[2].lamports(),
1302            transaction_accounts[2].1.lamports().saturating_sub(
1303                accounts[0]
1304                    .lamports()
1305                    .saturating_sub(transaction_accounts[0].1.lamports())
1306            ),
1307        );
1308
1309        // Error: Program was deployed recently, cooldown still in effect
1310        process_instruction(
1311            vec![],
1312            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1313            transaction_accounts.clone(),
1314            &[(0, false, true), (1, true, false)],
1315            Err(InstructionError::InvalidArgument),
1316        );
1317        transaction_accounts[5].1 = clock(3000);
1318
1319        // Error: Program is uninitialized
1320        process_instruction(
1321            vec![],
1322            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1323            transaction_accounts.clone(),
1324            &[(3, false, true), (1, true, false)],
1325            Err(InstructionError::InvalidAccountData),
1326        );
1327
1328        // Error: Program fails verification
1329        process_instruction(
1330            vec![],
1331            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1332            transaction_accounts.clone(),
1333            &[(4, false, true), (1, true, false)],
1334            Err(InstructionError::InvalidAccountData),
1335        );
1336
1337        // Error: Program is deployed already
1338        process_instruction(
1339            vec![],
1340            &bincode::serialize(&LoaderV4Instruction::Deploy).unwrap(),
1341            transaction_accounts.clone(),
1342            &[(0, false, true), (1, true, false)],
1343            Err(InstructionError::InvalidArgument),
1344        );
1345
1346        test_loader_instruction_general_errors(LoaderV4Instruction::Deploy);
1347    }
1348
1349    #[test]
1350    fn test_loader_instruction_retract() {
1351        let authority_address = Pubkey::new_unique();
1352        let mut transaction_accounts = vec![
1353            (
1354                Pubkey::new_unique(),
1355                load_program_account_from_elf(
1356                    authority_address,
1357                    LoaderV4Status::Deployed,
1358                    "rodata_section",
1359                ),
1360            ),
1361            (
1362                authority_address,
1363                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
1364            ),
1365            (
1366                Pubkey::new_unique(),
1367                AccountSharedData::new(0, 0, &loader_v4::id()),
1368            ),
1369            (
1370                Pubkey::new_unique(),
1371                load_program_account_from_elf(
1372                    authority_address,
1373                    LoaderV4Status::Retracted,
1374                    "rodata_section",
1375                ),
1376            ),
1377            (clock::id(), clock(1000)),
1378            (
1379                rent::id(),
1380                create_account_shared_data_for_test(&rent::Rent::default()),
1381            ),
1382        ];
1383
1384        // Retract program
1385        let accounts = process_instruction(
1386            vec![],
1387            &bincode::serialize(&LoaderV4Instruction::Retract).unwrap(),
1388            transaction_accounts.clone(),
1389            &[(0, false, true), (1, true, false)],
1390            Ok(()),
1391        );
1392        assert_eq!(
1393            accounts[0].data().len(),
1394            transaction_accounts[0].1.data().len(),
1395        );
1396        assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
1397
1398        // Error: Program is uninitialized
1399        process_instruction(
1400            vec![],
1401            &bincode::serialize(&LoaderV4Instruction::Retract).unwrap(),
1402            transaction_accounts.clone(),
1403            &[(2, false, true), (1, true, false)],
1404            Err(InstructionError::InvalidAccountData),
1405        );
1406
1407        // Error: Program is not deployed
1408        process_instruction(
1409            vec![],
1410            &bincode::serialize(&LoaderV4Instruction::Retract).unwrap(),
1411            transaction_accounts.clone(),
1412            &[(3, false, true), (1, true, false)],
1413            Err(InstructionError::InvalidArgument),
1414        );
1415
1416        // Error: Program was deployed recently, cooldown still in effect
1417        transaction_accounts[4].1 = clock(0);
1418        process_instruction(
1419            vec![],
1420            &bincode::serialize(&LoaderV4Instruction::Retract).unwrap(),
1421            transaction_accounts.clone(),
1422            &[(0, false, true), (1, true, false)],
1423            Err(InstructionError::InvalidArgument),
1424        );
1425
1426        test_loader_instruction_general_errors(LoaderV4Instruction::Retract);
1427    }
1428
1429    #[test]
1430    fn test_loader_instruction_transfer_authority() {
1431        let authority_address = Pubkey::new_unique();
1432        let transaction_accounts = vec![
1433            (
1434                Pubkey::new_unique(),
1435                load_program_account_from_elf(
1436                    authority_address,
1437                    LoaderV4Status::Deployed,
1438                    "rodata_section",
1439                ),
1440            ),
1441            (
1442                Pubkey::new_unique(),
1443                load_program_account_from_elf(
1444                    authority_address,
1445                    LoaderV4Status::Retracted,
1446                    "rodata_section",
1447                ),
1448            ),
1449            (
1450                Pubkey::new_unique(),
1451                AccountSharedData::new(0, 0, &loader_v4::id()),
1452            ),
1453            (
1454                authority_address,
1455                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
1456            ),
1457            (
1458                Pubkey::new_unique(),
1459                AccountSharedData::new(0, 0, &Pubkey::new_unique()),
1460            ),
1461            (
1462                clock::id(),
1463                create_account_shared_data_for_test(&clock::Clock::default()),
1464            ),
1465            (
1466                rent::id(),
1467                create_account_shared_data_for_test(&rent::Rent::default()),
1468            ),
1469        ];
1470
1471        // Transfer authority
1472        let accounts = process_instruction(
1473            vec![],
1474            &bincode::serialize(&LoaderV4Instruction::TransferAuthority).unwrap(),
1475            transaction_accounts.clone(),
1476            &[(0, false, true), (3, true, false), (4, true, false)],
1477            Ok(()),
1478        );
1479        assert_eq!(
1480            accounts[0].data().len(),
1481            transaction_accounts[0].1.data().len(),
1482        );
1483        assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
1484
1485        // Finalize program
1486        let accounts = process_instruction(
1487            vec![],
1488            &bincode::serialize(&LoaderV4Instruction::TransferAuthority).unwrap(),
1489            transaction_accounts.clone(),
1490            &[(0, false, true), (3, true, false)],
1491            Ok(()),
1492        );
1493        assert_eq!(
1494            accounts[0].data().len(),
1495            transaction_accounts[0].1.data().len(),
1496        );
1497        assert_eq!(accounts[0].lamports(), transaction_accounts[0].1.lamports());
1498
1499        // Error: Program must be deployed to be finalized
1500        process_instruction(
1501            vec![],
1502            &bincode::serialize(&LoaderV4Instruction::TransferAuthority).unwrap(),
1503            transaction_accounts.clone(),
1504            &[(1, false, true), (3, true, false)],
1505            Err(InstructionError::InvalidArgument),
1506        );
1507
1508        // Error: Program is uninitialized
1509        process_instruction(
1510            vec![],
1511            &bincode::serialize(&LoaderV4Instruction::TransferAuthority).unwrap(),
1512            transaction_accounts.clone(),
1513            &[(2, false, true), (3, true, false), (4, true, false)],
1514            Err(InstructionError::InvalidAccountData),
1515        );
1516
1517        // Error: New authority did not sign
1518        process_instruction(
1519            vec![],
1520            &bincode::serialize(&LoaderV4Instruction::TransferAuthority).unwrap(),
1521            transaction_accounts,
1522            &[(0, false, true), (3, true, false), (4, false, false)],
1523            Err(InstructionError::MissingRequiredSignature),
1524        );
1525
1526        test_loader_instruction_general_errors(LoaderV4Instruction::TransferAuthority);
1527    }
1528
1529    #[test]
1530    fn test_execute_program() {
1531        let program_address = Pubkey::new_unique();
1532        let authority_address = Pubkey::new_unique();
1533        let transaction_accounts = vec![
1534            (
1535                program_address,
1536                load_program_account_from_elf(
1537                    authority_address,
1538                    LoaderV4Status::Finalized,
1539                    "rodata_section",
1540                ),
1541            ),
1542            (
1543                Pubkey::new_unique(),
1544                AccountSharedData::new(10000000, 32, &program_address),
1545            ),
1546            (
1547                Pubkey::new_unique(),
1548                AccountSharedData::new(0, 0, &loader_v4::id()),
1549            ),
1550            (
1551                Pubkey::new_unique(),
1552                load_program_account_from_elf(
1553                    authority_address,
1554                    LoaderV4Status::Retracted,
1555                    "rodata_section",
1556                ),
1557            ),
1558            (
1559                Pubkey::new_unique(),
1560                load_program_account_from_elf(
1561                    authority_address,
1562                    LoaderV4Status::Finalized,
1563                    "invalid",
1564                ),
1565            ),
1566        ];
1567
1568        // Execute program
1569        process_instruction(
1570            vec![0],
1571            &[0, 1, 2, 3],
1572            transaction_accounts.clone(),
1573            &[(1, false, true)],
1574            Err(InstructionError::Custom(42)),
1575        );
1576
1577        // Error: Program not owned by loader
1578        process_instruction(
1579            vec![1],
1580            &[0, 1, 2, 3],
1581            transaction_accounts.clone(),
1582            &[(1, false, true)],
1583            Err(InstructionError::InvalidAccountOwner),
1584        );
1585
1586        // Error: Program is uninitialized
1587        process_instruction(
1588            vec![2],
1589            &[0, 1, 2, 3],
1590            transaction_accounts.clone(),
1591            &[(1, false, true)],
1592            Err(InstructionError::InvalidAccountData),
1593        );
1594
1595        // Error: Program is not deployed
1596        process_instruction(
1597            vec![3],
1598            &[0, 1, 2, 3],
1599            transaction_accounts.clone(),
1600            &[(1, false, true)],
1601            Err(InstructionError::InvalidArgument),
1602        );
1603
1604        // Error: Program fails verification
1605        process_instruction(
1606            vec![4],
1607            &[0, 1, 2, 3],
1608            transaction_accounts,
1609            &[(1, false, true)],
1610            Err(InstructionError::InvalidAccountData),
1611        );
1612    }
1613}