solana_loader_v4_program/
lib.rs

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