solana_loader_v4_program/
lib.rs

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