Skip to main content

solana_loader_v4_program/
lib.rs

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