solana_loader_v4_program/
lib.rs

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