Skip to main content

solana_bpf_loader_program/
lib.rs

1#![cfg(feature = "agave-unstable-api")]
2#![deny(clippy::arithmetic_side_effects)]
3#![deny(clippy::indexing_slicing)]
4
5#[cfg(feature = "svm-internal")]
6use qualifier_attr::qualifiers;
7use {
8    solana_bincode::limited_deserialize,
9    solana_instruction::AccountMeta,
10    solana_instruction_error::InstructionError,
11    solana_loader_v3_interface::{
12        instruction::{MINIMUM_EXTEND_PROGRAM_BYTES, UpgradeableLoaderInstruction},
13        state::UpgradeableLoaderState,
14    },
15    solana_program_runtime::{
16        deploy_program,
17        invoke_context::InvokeContext,
18        program_cache_entry::{ProgramCacheEntry, ProgramCacheEntryOwner, ProgramCacheEntryType},
19        sysvar_cache::get_sysvar_with_account_check,
20        vm::execute,
21    },
22    solana_pubkey::Pubkey,
23    solana_sbpf::{declare_builtin_function, elf::get_sbpf_version, program::SBPFVersion},
24    solana_sdk_ids::{bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, native_loader},
25    solana_svm_log_collector::{LogCollector, ic_logger_msg},
26    solana_svm_measure::measure::Measure,
27    solana_svm_type_overrides::sync::Arc,
28    solana_system_interface::{MAX_PERMITTED_DATA_LENGTH, instruction as system_instruction},
29    solana_transaction_context::{IndexOfAccount, instruction::InstructionContext},
30    std::{cell::RefCell, rc::Rc},
31    wincode::SchemaRead,
32};
33
34#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
35const DEFAULT_LOADER_COMPUTE_UNITS: u64 = 570;
36#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
37const DEPRECATED_LOADER_COMPUTE_UNITS: u64 = 1_140;
38#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
39const UPGRADEABLE_LOADER_COMPUTE_UNITS: u64 = 2_370;
40
41// `UpgradeableLoaderInstruction::Write`
42#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
43const WRITE_INSTRUCTION_TAG: [u8; 4] = 1u32.to_le_bytes();
44
45// u32 tag, u32 offset, u64 payload length
46#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
47const WRITE_INSTRUCTION_HEADER_LEN: usize = 16;
48
49fn parse_write_instruction(
50    instruction_data: &[u8],
51    unbound_loader_v3_instruction_data: bool,
52) -> Result<(u32, &[u8]), InstructionError> {
53    #[derive(SchemaRead)]
54    struct WriteInstruction<'a> {
55        tag: u32,
56        offset: u32,
57        bytes: &'a [u8],
58    }
59
60    let WriteInstruction { tag, offset, bytes } = wincode::deserialize(instruction_data)
61        .map_err(|_| InstructionError::InvalidInstructionData)?;
62
63    if tag.to_le_bytes() != WRITE_INSTRUCTION_TAG {
64        // unreachable because we only call this in the relevant `match` arm but included for safety
65        return Err(InstructionError::InvalidInstructionData);
66    }
67
68    if !unbound_loader_v3_instruction_data
69        && WRITE_INSTRUCTION_HEADER_LEN.saturating_add(bytes.len())
70            > solana_packet::PACKET_DATA_SIZE
71    {
72        return Err(InstructionError::InvalidInstructionData);
73    }
74
75    Ok((offset, bytes))
76}
77
78fn write_program_data(
79    program_data_offset: usize,
80    bytes: &[u8],
81    instruction_context: &InstructionContext,
82    log_collector: &Option<Rc<RefCell<LogCollector>>>,
83) -> Result<(), InstructionError> {
84    let mut program = instruction_context.try_borrow_instruction_account(0)?;
85    let data = program.get_data_mut()?;
86    let write_offset = program_data_offset.saturating_add(bytes.len());
87    if data.len() < write_offset {
88        ic_logger_msg!(
89            log_collector,
90            "Write overflow: {} < {}",
91            data.len(),
92            write_offset,
93        );
94        return Err(InstructionError::AccountDataTooSmall);
95    }
96    data.get_mut(program_data_offset..write_offset)
97        .ok_or(InstructionError::AccountDataTooSmall)?
98        .copy_from_slice(bytes);
99    Ok(())
100}
101
102declare_builtin_function!(
103    Entrypoint,
104    fn rust(
105        invoke_context: &mut InvokeContext<'static, 'static>,
106        _arg0: u64,
107        _arg1: u64,
108        _arg2: u64,
109        _arg3: u64,
110        _arg4: u64,
111    ) -> Result<u64, Box<dyn std::error::Error>> {
112        process_instruction_inner(invoke_context)
113    }
114);
115
116mod migration_authority {
117    solana_pubkey::declare_id!("3Scf35jMNk2xXBD6areNjgMtXgp5ZspDhms8vdcbzC42");
118}
119
120#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
121pub(crate) fn process_instruction_inner<'a>(
122    invoke_context: &mut InvokeContext<'a, 'a>,
123) -> Result<u64, Box<dyn std::error::Error>> {
124    let log_collector = invoke_context.get_log_collector();
125    let transaction_context = &invoke_context.transaction_context;
126    let instruction_context = transaction_context.get_current_instruction_context()?;
127    let program_id = instruction_context.get_program_key()?;
128    let owner_id = instruction_context.get_program_owner()?;
129
130    // Program Management Instruction
131    if native_loader::check_id(&owner_id) {
132        let program_id = instruction_context.get_program_key()?;
133        return if bpf_loader_upgradeable::check_id(program_id) {
134            invoke_context.consume_checked(UPGRADEABLE_LOADER_COMPUTE_UNITS)?;
135            process_loader_upgradeable_instruction(invoke_context)
136        } else if bpf_loader::check_id(program_id) {
137            invoke_context.consume_checked(DEFAULT_LOADER_COMPUTE_UNITS)?;
138            ic_logger_msg!(
139                log_collector,
140                "BPF loader management instructions are no longer supported",
141            );
142            Err(InstructionError::UnsupportedProgramId)
143        } else if bpf_loader_deprecated::check_id(program_id) {
144            invoke_context.consume_checked(DEPRECATED_LOADER_COMPUTE_UNITS)?;
145            ic_logger_msg!(log_collector, "Deprecated loader is no longer supported");
146            Err(InstructionError::UnsupportedProgramId)
147        } else {
148            ic_logger_msg!(log_collector, "Invalid BPF loader id");
149            Err(InstructionError::UnsupportedProgramId)
150        }
151        .map(|_| 0)
152        .map_err(|error| Box::new(error) as Box<dyn std::error::Error>);
153    }
154
155    // Program Invocation
156    let mut get_or_create_executor_time = Measure::start("get_or_create_executor_time");
157    let executor = invoke_context
158        .program_cache_for_tx_batch
159        .find(program_id)
160        .ok_or_else(|| {
161            ic_logger_msg!(log_collector, "Program is not cached");
162            InstructionError::UnsupportedProgramId
163        })?;
164    get_or_create_executor_time.stop();
165    invoke_context.timings.get_or_create_executor_us += get_or_create_executor_time.as_us();
166
167    match &executor.program {
168        ProgramCacheEntryType::FailedVerification(_)
169        | ProgramCacheEntryType::Closed
170        | ProgramCacheEntryType::DelayVisibility => {
171            ic_logger_msg!(log_collector, "Program is not deployed");
172            Err(Box::new(InstructionError::UnsupportedProgramId) as Box<dyn std::error::Error>)
173        }
174        ProgramCacheEntryType::Loaded(executable) => execute(executable, invoke_context, &executor),
175        _ => Err(Box::new(InstructionError::UnsupportedProgramId) as Box<dyn std::error::Error>),
176    }
177    .map(|_| 0)
178}
179
180fn process_loader_upgradeable_instruction(
181    invoke_context: &mut InvokeContext,
182) -> Result<(), InstructionError> {
183    let log_collector = invoke_context.get_log_collector();
184    let transaction_context = &invoke_context.transaction_context;
185    let instruction_context = transaction_context.get_current_instruction_context()?;
186    let instruction_data = instruction_context.get_instruction_data();
187    let program_id = instruction_context.get_program_key()?;
188    let unbound_loader_v3_instruction_data = invoke_context
189        .get_feature_set()
190        .unbound_loader_v3_instruction_data;
191
192    let instruction = if instruction_data.starts_with(&WRITE_INSTRUCTION_TAG) {
193        // `Write` is parsed in-place in the match arm
194        UpgradeableLoaderInstruction::Write {
195            offset: 0,
196            bytes: Vec::new(),
197        }
198    } else if unbound_loader_v3_instruction_data {
199        wincode::deserialize(instruction_data)
200            .map_err(|_| InstructionError::InvalidInstructionData)?
201    } else {
202        limited_deserialize(instruction_data, solana_packet::PACKET_DATA_SIZE as u64)?
203    };
204
205    match instruction {
206        UpgradeableLoaderInstruction::InitializeBuffer => {
207            instruction_context.check_number_of_instruction_accounts(2)?;
208            let mut buffer = instruction_context.try_borrow_instruction_account(0)?;
209
210            if UpgradeableLoaderState::Uninitialized != buffer.get_state()? {
211                ic_logger_msg!(log_collector, "Buffer account already initialized");
212                return Err(InstructionError::AccountAlreadyInitialized);
213            }
214
215            let authority_key = Some(*instruction_context.get_key_of_instruction_account(1)?);
216
217            buffer.set_state(&UpgradeableLoaderState::Buffer {
218                authority_address: authority_key,
219            })?;
220        }
221        UpgradeableLoaderInstruction::Write { .. } => {
222            let (offset, payload) =
223                parse_write_instruction(instruction_data, unbound_loader_v3_instruction_data)?;
224            instruction_context.check_number_of_instruction_accounts(2)?;
225            let buffer = instruction_context.try_borrow_instruction_account(0)?;
226
227            if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
228                if authority_address.is_none() {
229                    ic_logger_msg!(log_collector, "Buffer is immutable");
230                    return Err(InstructionError::Immutable); // TODO better error code
231                }
232                let authority_key = Some(*instruction_context.get_key_of_instruction_account(1)?);
233                if authority_address != authority_key {
234                    ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
235                    return Err(InstructionError::IncorrectAuthority);
236                }
237                if !instruction_context.is_instruction_account_signer(1)? {
238                    ic_logger_msg!(log_collector, "Buffer authority did not sign");
239                    return Err(InstructionError::MissingRequiredSignature);
240                }
241            } else {
242                ic_logger_msg!(log_collector, "Invalid Buffer account");
243                return Err(InstructionError::InvalidAccountData);
244            }
245            drop(buffer);
246            write_program_data(
247                UpgradeableLoaderState::size_of_buffer_metadata().saturating_add(offset as usize),
248                payload,
249                &instruction_context,
250                &log_collector,
251            )?;
252        }
253        UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => {
254            instruction_context.check_number_of_instruction_accounts(4)?;
255            let payer_key = *instruction_context.get_key_of_instruction_account(0)?;
256            let programdata_key = *instruction_context.get_key_of_instruction_account(1)?;
257            let rent =
258                get_sysvar_with_account_check::rent(invoke_context, &instruction_context, 4)?;
259            let clock =
260                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 5)?;
261            instruction_context.check_number_of_instruction_accounts(8)?;
262            let authority_key = Some(*instruction_context.get_key_of_instruction_account(7)?);
263
264            // Verify Program account
265
266            let program = instruction_context.try_borrow_instruction_account(2)?;
267            if UpgradeableLoaderState::Uninitialized != program.get_state()? {
268                ic_logger_msg!(log_collector, "Program account already initialized");
269                return Err(InstructionError::AccountAlreadyInitialized);
270            }
271            if program.get_data().len() < UpgradeableLoaderState::size_of_program() {
272                ic_logger_msg!(log_collector, "Program account too small");
273                return Err(InstructionError::AccountDataTooSmall);
274            }
275            if program.get_lamports() < rent.minimum_balance(program.get_data().len()) {
276                ic_logger_msg!(log_collector, "Program account not rent-exempt");
277                return Err(InstructionError::ExecutableAccountNotRentExempt);
278            }
279            let new_program_id = *program.get_key();
280            drop(program);
281
282            // Verify Buffer account
283
284            let buffer = instruction_context.try_borrow_instruction_account(3)?;
285            if !buffer.is_writable() {
286                ic_logger_msg!(log_collector, "Buffer account not writeable");
287                return Err(InstructionError::InvalidArgument);
288            }
289            if buffer.get_owner() != program_id {
290                ic_logger_msg!(log_collector, "Buffer account not owned by loader");
291                return Err(InstructionError::IncorrectProgramId);
292            }
293            if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
294                if authority_address != authority_key {
295                    ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match");
296                    return Err(InstructionError::IncorrectAuthority);
297                }
298                if !instruction_context.is_instruction_account_signer(7)? {
299                    ic_logger_msg!(log_collector, "Upgrade authority did not sign");
300                    return Err(InstructionError::MissingRequiredSignature);
301                }
302            } else {
303                ic_logger_msg!(log_collector, "Invalid Buffer account");
304                return Err(InstructionError::InvalidArgument);
305            }
306            let buffer_key = *buffer.get_key();
307            let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata();
308            let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset);
309            let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
310            let programdata_len = UpgradeableLoaderState::size_of_programdata(max_data_len);
311            if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata()
312                || buffer_data_len == 0
313            {
314                ic_logger_msg!(log_collector, "Buffer account too small");
315                return Err(InstructionError::InvalidAccountData);
316            }
317            drop(buffer);
318            if max_data_len < buffer_data_len {
319                ic_logger_msg!(
320                    log_collector,
321                    "Max data length is too small to hold Buffer data"
322                );
323                return Err(InstructionError::AccountDataTooSmall);
324            }
325            if programdata_len > MAX_PERMITTED_DATA_LENGTH as usize {
326                ic_logger_msg!(log_collector, "Max data length is too large");
327                return Err(InstructionError::InvalidArgument);
328            }
329
330            // Create ProgramData account
331            let (derived_address, bump_seed) =
332                Pubkey::find_program_address(&[new_program_id.as_ref()], program_id);
333            if derived_address != programdata_key {
334                ic_logger_msg!(log_collector, "ProgramData address is not derived");
335                return Err(InstructionError::InvalidArgument);
336            }
337
338            // Drain the Buffer account to payer before paying for programdata account
339            {
340                let mut buffer = instruction_context.try_borrow_instruction_account(3)?;
341                let mut payer = instruction_context.try_borrow_instruction_account(0)?;
342                payer.checked_add_lamports(buffer.get_lamports())?;
343                buffer.set_lamports(0)?;
344            }
345
346            let owner_id = *program_id;
347            let mut instruction = system_instruction::create_account(
348                &payer_key,
349                &programdata_key,
350                1.max(rent.minimum_balance(programdata_len)),
351                programdata_len as u64,
352                program_id,
353            );
354
355            // pass an extra account to avoid the overly strict UnbalancedInstruction error
356            instruction
357                .accounts
358                .push(AccountMeta::new(buffer_key, false));
359
360            invoke_context
361                .native_invoke_signed(instruction, &[&[new_program_id.as_ref(), &[bump_seed]]])?;
362
363            // Load and verify the program bits
364            let transaction_context = &invoke_context.transaction_context;
365            let instruction_context = transaction_context.get_current_instruction_context()?;
366            let buffer = instruction_context.try_borrow_instruction_account(3)?;
367            deploy_program!(
368                invoke_context,
369                &new_program_id,
370                &owner_id,
371                buffer
372                    .get_data()
373                    .get(buffer_data_offset..)
374                    .ok_or(InstructionError::AccountDataTooSmall)?,
375                clock.slot,
376                invoke_context
377                    .get_feature_set()
378                    .disable_sbpf_v0_v1_v2_deployment,
379            );
380            drop(buffer);
381
382            let transaction_context = &invoke_context.transaction_context;
383            let instruction_context = transaction_context.get_current_instruction_context()?;
384
385            // Update the ProgramData account and record the program bits
386            {
387                let mut programdata = instruction_context.try_borrow_instruction_account(1)?;
388                programdata.set_state(&UpgradeableLoaderState::ProgramData {
389                    slot: clock.slot,
390                    upgrade_authority_address: authority_key,
391                })?;
392                let dst_slice = programdata
393                    .get_data_mut()?
394                    .get_mut(
395                        programdata_data_offset
396                            ..programdata_data_offset.saturating_add(buffer_data_len),
397                    )
398                    .ok_or(InstructionError::AccountDataTooSmall)?;
399                let mut buffer = instruction_context.try_borrow_instruction_account(3)?;
400                let src_slice = buffer
401                    .get_data()
402                    .get(buffer_data_offset..)
403                    .ok_or(InstructionError::AccountDataTooSmall)?;
404                dst_slice.copy_from_slice(src_slice);
405                buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?;
406            }
407
408            // Update the Program account
409            let mut program = instruction_context.try_borrow_instruction_account(2)?;
410            program.set_state(&UpgradeableLoaderState::Program {
411                programdata_address: programdata_key,
412            })?;
413            program.set_executable(true)?;
414            drop(program);
415
416            ic_logger_msg!(log_collector, "Deployed program {:?}", new_program_id);
417        }
418        UpgradeableLoaderInstruction::Upgrade => {
419            instruction_context.check_number_of_instruction_accounts(3)?;
420            let programdata_key = *instruction_context.get_key_of_instruction_account(0)?;
421            let rent =
422                get_sysvar_with_account_check::rent(invoke_context, &instruction_context, 4)?;
423            let clock =
424                get_sysvar_with_account_check::clock(invoke_context, &instruction_context, 5)?;
425            instruction_context.check_number_of_instruction_accounts(7)?;
426            let authority_key = Some(*instruction_context.get_key_of_instruction_account(6)?);
427
428            let set_programdata_to_elf_len = invoke_context
429                .get_feature_set()
430                .loader_v3_set_program_data_to_elf_length;
431
432            // Verify Program account
433
434            let program = instruction_context.try_borrow_instruction_account(1)?;
435            if !program.is_writable() {
436                ic_logger_msg!(log_collector, "Program account not writeable");
437                return Err(InstructionError::InvalidArgument);
438            }
439            if program.get_owner() != program_id {
440                ic_logger_msg!(log_collector, "Program account not owned by loader");
441                return Err(InstructionError::IncorrectProgramId);
442            }
443            if let UpgradeableLoaderState::Program {
444                programdata_address,
445            } = program.get_state()?
446            {
447                if programdata_address != programdata_key {
448                    ic_logger_msg!(log_collector, "Program and ProgramData account mismatch");
449                    return Err(InstructionError::InvalidArgument);
450                }
451            } else {
452                ic_logger_msg!(log_collector, "Invalid Program account");
453                return Err(InstructionError::InvalidAccountData);
454            }
455            let new_program_id = *program.get_key();
456            drop(program);
457
458            // Verify Buffer account
459
460            let buffer = instruction_context.try_borrow_instruction_account(2)?;
461            if !buffer.is_writable() {
462                ic_logger_msg!(log_collector, "Buffer account not writeable");
463                return Err(InstructionError::InvalidArgument);
464            }
465            if buffer.get_owner() != program_id {
466                ic_logger_msg!(log_collector, "Buffer account not owned by loader");
467                return Err(InstructionError::IncorrectProgramId);
468            }
469            if let UpgradeableLoaderState::Buffer { authority_address } = buffer.get_state()? {
470                if authority_address != authority_key {
471                    ic_logger_msg!(log_collector, "Buffer and upgrade authority don't match");
472                    return Err(InstructionError::IncorrectAuthority);
473                }
474                if !instruction_context.is_instruction_account_signer(6)? {
475                    ic_logger_msg!(log_collector, "Upgrade authority did not sign");
476                    return Err(InstructionError::MissingRequiredSignature);
477                }
478            } else {
479                ic_logger_msg!(log_collector, "Invalid Buffer account");
480                return Err(InstructionError::InvalidArgument);
481            }
482            let buffer_lamports = buffer.get_lamports();
483            let buffer_data_offset = UpgradeableLoaderState::size_of_buffer_metadata();
484            let buffer_data_len = buffer.get_data().len().saturating_sub(buffer_data_offset);
485            if buffer.get_data().len() < UpgradeableLoaderState::size_of_buffer_metadata()
486                || buffer_data_len == 0
487            {
488                ic_logger_msg!(log_collector, "Buffer account too small");
489                return Err(InstructionError::InvalidAccountData);
490            }
491            drop(buffer);
492
493            // Verify ProgramData account
494
495            let programdata = instruction_context.try_borrow_instruction_account(0)?;
496            let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
497            let (programdata_len, programdata_balance_required) = if set_programdata_to_elf_len {
498                // SIMD-0433: we'll resize the programdata account to the new ELF.
499                let new_len = programdata_data_offset.saturating_add(buffer_data_len);
500                if new_len > MAX_PERMITTED_DATA_LENGTH as usize {
501                    ic_logger_msg!(
502                        log_collector,
503                        "Resized ProgramData length of {} bytes exceeds max account data length",
504                        new_len
505                    );
506                    return Err(InstructionError::InvalidAccountData);
507                }
508                (new_len, 1.max(rent.minimum_balance(new_len)))
509            } else {
510                // Before SIMD-0433 accounts must be expanded manually and cannot
511                // change size here.
512                let len = programdata.get_data().len();
513                if len < UpgradeableLoaderState::size_of_programdata(buffer_data_len) {
514                    ic_logger_msg!(log_collector, "ProgramData account not large enough");
515                    return Err(InstructionError::AccountDataTooSmall);
516                }
517                (len, 1.max(rent.minimum_balance(len)))
518            };
519            if programdata.get_lamports().saturating_add(buffer_lamports)
520                < programdata_balance_required
521            {
522                ic_logger_msg!(
523                    log_collector,
524                    "Buffer account balance too low to fund upgrade"
525                );
526                return Err(InstructionError::InsufficientFunds);
527            }
528            if let UpgradeableLoaderState::ProgramData {
529                slot,
530                upgrade_authority_address,
531            } = programdata.get_state()?
532            {
533                if clock.slot == slot {
534                    ic_logger_msg!(log_collector, "Program was deployed in this block already");
535                    return Err(InstructionError::InvalidArgument);
536                }
537                if upgrade_authority_address.is_none() {
538                    ic_logger_msg!(log_collector, "Program not upgradeable");
539                    return Err(InstructionError::Immutable);
540                }
541                if upgrade_authority_address != authority_key {
542                    ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
543                    return Err(InstructionError::IncorrectAuthority);
544                }
545                if !instruction_context.is_instruction_account_signer(6)? {
546                    ic_logger_msg!(log_collector, "Upgrade authority did not sign");
547                    return Err(InstructionError::MissingRequiredSignature);
548                }
549            } else {
550                ic_logger_msg!(log_collector, "Invalid ProgramData account");
551                return Err(InstructionError::InvalidAccountData);
552            };
553            drop(programdata);
554
555            // Load and verify the program bits
556            let buffer = instruction_context.try_borrow_instruction_account(2)?;
557            deploy_program!(
558                invoke_context,
559                &new_program_id,
560                program_id,
561                buffer
562                    .get_data()
563                    .get(buffer_data_offset..)
564                    .ok_or(InstructionError::AccountDataTooSmall)?,
565                clock.slot,
566                invoke_context
567                    .get_feature_set()
568                    .disable_sbpf_v0_v1_v2_deployment,
569            );
570            drop(buffer);
571
572            let transaction_context = &invoke_context.transaction_context;
573            let instruction_context = transaction_context.get_current_instruction_context()?;
574
575            // Update the ProgramData account
576            let mut programdata = instruction_context.try_borrow_instruction_account(0)?;
577            {
578                programdata.set_data_length(programdata_len)?;
579                programdata.set_state(&UpgradeableLoaderState::ProgramData {
580                    slot: clock.slot,
581                    upgrade_authority_address: authority_key,
582                })?;
583                let dst_slice = programdata
584                    .get_data_mut()?
585                    .get_mut(
586                        programdata_data_offset
587                            ..programdata_data_offset.saturating_add(buffer_data_len),
588                    )
589                    .ok_or(InstructionError::AccountDataTooSmall)?;
590                let buffer = instruction_context.try_borrow_instruction_account(2)?;
591                let src_slice = buffer
592                    .get_data()
593                    .get(buffer_data_offset..)
594                    .ok_or(InstructionError::AccountDataTooSmall)?;
595                dst_slice.copy_from_slice(src_slice);
596            }
597            programdata
598                .get_data_mut()?
599                .get_mut(programdata_data_offset.saturating_add(buffer_data_len)..)
600                .ok_or(InstructionError::AccountDataTooSmall)?
601                .fill(0);
602
603            // Fund ProgramData to rent-exemption, spill the rest
604            let mut buffer = instruction_context.try_borrow_instruction_account(2)?;
605            let mut spill = instruction_context.try_borrow_instruction_account(3)?;
606            spill.checked_add_lamports(
607                programdata
608                    .get_lamports()
609                    .saturating_add(buffer_lamports)
610                    .saturating_sub(programdata_balance_required),
611            )?;
612            buffer.set_lamports(0)?;
613            programdata.set_lamports(programdata_balance_required)?;
614            buffer.set_data_length(UpgradeableLoaderState::size_of_buffer(0))?;
615
616            ic_logger_msg!(log_collector, "Upgraded program {:?}", new_program_id);
617        }
618        UpgradeableLoaderInstruction::SetAuthority => {
619            instruction_context.check_number_of_instruction_accounts(2)?;
620            let mut account = instruction_context.try_borrow_instruction_account(0)?;
621            let present_authority_key = instruction_context.get_key_of_instruction_account(1)?;
622            let new_authority = instruction_context.get_key_of_instruction_account(2).ok();
623
624            match account.get_state()? {
625                UpgradeableLoaderState::Buffer { authority_address } => {
626                    if new_authority.is_none() {
627                        ic_logger_msg!(log_collector, "Buffer authority is not optional");
628                        return Err(InstructionError::IncorrectAuthority);
629                    }
630                    if authority_address.is_none() {
631                        ic_logger_msg!(log_collector, "Buffer is immutable");
632                        return Err(InstructionError::Immutable);
633                    }
634                    if authority_address != Some(*present_authority_key) {
635                        ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
636                        return Err(InstructionError::IncorrectAuthority);
637                    }
638                    if !instruction_context.is_instruction_account_signer(1)? {
639                        ic_logger_msg!(log_collector, "Buffer authority did not sign");
640                        return Err(InstructionError::MissingRequiredSignature);
641                    }
642                    account.set_state(&UpgradeableLoaderState::Buffer {
643                        authority_address: new_authority.cloned(),
644                    })?;
645                }
646                UpgradeableLoaderState::ProgramData {
647                    slot,
648                    upgrade_authority_address,
649                } => {
650                    if upgrade_authority_address.is_none() {
651                        ic_logger_msg!(log_collector, "Program not upgradeable");
652                        return Err(InstructionError::Immutable);
653                    }
654                    if upgrade_authority_address != Some(*present_authority_key) {
655                        ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
656                        return Err(InstructionError::IncorrectAuthority);
657                    }
658                    if !instruction_context.is_instruction_account_signer(1)? {
659                        ic_logger_msg!(log_collector, "Upgrade authority did not sign");
660                        return Err(InstructionError::MissingRequiredSignature);
661                    }
662                    if invoke_context
663                        .get_feature_set()
664                        .disable_sbpf_v0_v1_v2_deployment
665                        && new_authority.is_none()
666                        && let Some(program) = account
667                            .get_data()
668                            .get(UpgradeableLoaderState::size_of_programdata_metadata()..)
669                        && let Ok(sbpf_version) = get_sbpf_version(program)
670                        && sbpf_version < SBPFVersion::V3
671                    {
672                        ic_logger_msg!(
673                            log_collector,
674                            "Program targets SBPF {:?}, but finalizing requires SBPF V3 or newer",
675                            sbpf_version
676                        );
677                        return Err(InstructionError::InvalidAccountData);
678                    }
679                    account.set_state(&UpgradeableLoaderState::ProgramData {
680                        slot,
681                        upgrade_authority_address: new_authority.cloned(),
682                    })?;
683                }
684                _ => {
685                    ic_logger_msg!(log_collector, "Account does not support authorities");
686                    return Err(InstructionError::InvalidArgument);
687                }
688            }
689
690            ic_logger_msg!(log_collector, "New authority {:?}", new_authority);
691        }
692        UpgradeableLoaderInstruction::SetAuthorityChecked => {
693            if !invoke_context
694                .get_feature_set()
695                .enable_bpf_loader_set_authority_checked_ix
696            {
697                return Err(InstructionError::InvalidInstructionData);
698            }
699
700            instruction_context.check_number_of_instruction_accounts(3)?;
701            let mut account = instruction_context.try_borrow_instruction_account(0)?;
702            let present_authority_key = instruction_context.get_key_of_instruction_account(1)?;
703            let new_authority_key = instruction_context.get_key_of_instruction_account(2)?;
704
705            match account.get_state()? {
706                UpgradeableLoaderState::Buffer { authority_address } => {
707                    if authority_address.is_none() {
708                        ic_logger_msg!(log_collector, "Buffer is immutable");
709                        return Err(InstructionError::Immutable);
710                    }
711                    if authority_address != Some(*present_authority_key) {
712                        ic_logger_msg!(log_collector, "Incorrect buffer authority provided");
713                        return Err(InstructionError::IncorrectAuthority);
714                    }
715                    if !instruction_context.is_instruction_account_signer(1)? {
716                        ic_logger_msg!(log_collector, "Buffer authority did not sign");
717                        return Err(InstructionError::MissingRequiredSignature);
718                    }
719                    if !instruction_context.is_instruction_account_signer(2)? {
720                        ic_logger_msg!(log_collector, "New authority did not sign");
721                        return Err(InstructionError::MissingRequiredSignature);
722                    }
723                    account.set_state(&UpgradeableLoaderState::Buffer {
724                        authority_address: Some(*new_authority_key),
725                    })?;
726                }
727                UpgradeableLoaderState::ProgramData {
728                    slot,
729                    upgrade_authority_address,
730                } => {
731                    if upgrade_authority_address.is_none() {
732                        ic_logger_msg!(log_collector, "Program not upgradeable");
733                        return Err(InstructionError::Immutable);
734                    }
735                    if upgrade_authority_address != Some(*present_authority_key) {
736                        ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
737                        return Err(InstructionError::IncorrectAuthority);
738                    }
739                    if !instruction_context.is_instruction_account_signer(1)? {
740                        ic_logger_msg!(log_collector, "Upgrade authority did not sign");
741                        return Err(InstructionError::MissingRequiredSignature);
742                    }
743                    if !instruction_context.is_instruction_account_signer(2)? {
744                        ic_logger_msg!(log_collector, "New authority did not sign");
745                        return Err(InstructionError::MissingRequiredSignature);
746                    }
747                    account.set_state(&UpgradeableLoaderState::ProgramData {
748                        slot,
749                        upgrade_authority_address: Some(*new_authority_key),
750                    })?;
751                }
752                _ => {
753                    ic_logger_msg!(log_collector, "Account does not support authorities");
754                    return Err(InstructionError::InvalidArgument);
755                }
756            }
757
758            ic_logger_msg!(log_collector, "New authority {:?}", new_authority_key);
759        }
760        UpgradeableLoaderInstruction::Close => {
761            instruction_context.check_number_of_instruction_accounts(2)?;
762            if instruction_context.get_index_of_instruction_account_in_transaction(0)?
763                == instruction_context.get_index_of_instruction_account_in_transaction(1)?
764            {
765                ic_logger_msg!(
766                    log_collector,
767                    "Recipient is the same as the account being closed"
768                );
769                return Err(InstructionError::InvalidArgument);
770            }
771            let mut close_account = instruction_context.try_borrow_instruction_account(0)?;
772            let close_key = *close_account.get_key();
773            let close_account_state = close_account.get_state()?;
774            match close_account_state {
775                UpgradeableLoaderState::Uninitialized => {
776                    let mut recipient_account =
777                        instruction_context.try_borrow_instruction_account(1)?;
778                    recipient_account.checked_add_lamports(close_account.get_lamports())?;
779                    close_account.set_lamports(0)?;
780                    close_account
781                        .set_data_length(UpgradeableLoaderState::size_of_uninitialized())?;
782                    ic_logger_msg!(log_collector, "Closed Uninitialized {}", close_key);
783                }
784                UpgradeableLoaderState::Buffer { authority_address } => {
785                    instruction_context.check_number_of_instruction_accounts(3)?;
786                    drop(close_account);
787                    common_close_account(&authority_address, &instruction_context, &log_collector)?;
788                    ic_logger_msg!(log_collector, "Closed Buffer {}", close_key);
789                }
790                UpgradeableLoaderState::ProgramData {
791                    slot,
792                    upgrade_authority_address: authority_address,
793                } => {
794                    instruction_context.check_number_of_instruction_accounts(4)?;
795                    drop(close_account);
796                    let program_account = instruction_context.try_borrow_instruction_account(3)?;
797                    let program_key = *program_account.get_key();
798
799                    if !program_account.is_writable() {
800                        ic_logger_msg!(log_collector, "Program account is not writable");
801                        return Err(InstructionError::InvalidArgument);
802                    }
803                    if program_account.get_owner() != program_id {
804                        ic_logger_msg!(log_collector, "Program account not owned by loader");
805                        return Err(InstructionError::IncorrectProgramId);
806                    }
807                    let clock = invoke_context
808                        .environment_config
809                        .sysvar_cache()
810                        .get_clock()?;
811                    if clock.slot == slot {
812                        ic_logger_msg!(log_collector, "Program was deployed in this block already");
813                        return Err(InstructionError::InvalidArgument);
814                    }
815
816                    match program_account.get_state()? {
817                        UpgradeableLoaderState::Program {
818                            programdata_address,
819                        } => {
820                            if programdata_address != close_key {
821                                ic_logger_msg!(
822                                    log_collector,
823                                    "ProgramData account does not match ProgramData account"
824                                );
825                                return Err(InstructionError::InvalidArgument);
826                            }
827
828                            drop(program_account);
829                            common_close_account(
830                                &authority_address,
831                                &instruction_context,
832                                &log_collector,
833                            )?;
834                            let clock = invoke_context
835                                .environment_config
836                                .sysvar_cache()
837                                .get_clock()?;
838                            invoke_context
839                                .program_cache_for_tx_batch
840                                .store_modified_entry(
841                                    program_key,
842                                    Arc::new(ProgramCacheEntry::new_closed_tombstone(
843                                        clock.slot,
844                                        ProgramCacheEntryOwner::LoaderV3,
845                                    )),
846                                );
847                        }
848                        _ => {
849                            ic_logger_msg!(log_collector, "Invalid Program account");
850                            return Err(InstructionError::InvalidArgument);
851                        }
852                    }
853
854                    ic_logger_msg!(log_collector, "Closed Program {}", program_key);
855                }
856                _ => {
857                    ic_logger_msg!(log_collector, "Account does not support closing");
858                    return Err(InstructionError::InvalidArgument);
859                }
860            }
861        }
862        UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => {
863            common_extend_program(invoke_context, additional_bytes, false)?;
864        }
865    }
866
867    Ok(())
868}
869
870fn common_extend_program(
871    invoke_context: &mut InvokeContext,
872    additional_bytes: u32,
873    check_authority: bool,
874) -> Result<(), InstructionError> {
875    let log_collector = invoke_context.get_log_collector();
876    let transaction_context = &invoke_context.transaction_context;
877    let instruction_context = transaction_context.get_current_instruction_context()?;
878    let program_id = instruction_context.get_program_key()?;
879
880    const PROGRAM_DATA_ACCOUNT_INDEX: IndexOfAccount = 0;
881    const PROGRAM_ACCOUNT_INDEX: IndexOfAccount = 1;
882    const AUTHORITY_ACCOUNT_INDEX: IndexOfAccount = 2;
883    // The unused `system_program_account_index` is 3 if `check_authority` and 2 otherwise.
884    let optional_payer_account_index = if check_authority { 4 } else { 3 };
885
886    if additional_bytes == 0 {
887        ic_logger_msg!(log_collector, "Additional bytes must be greater than 0");
888        return Err(InstructionError::InvalidInstructionData);
889    }
890
891    let programdata_account =
892        instruction_context.try_borrow_instruction_account(PROGRAM_DATA_ACCOUNT_INDEX)?;
893    let programdata_key = *programdata_account.get_key();
894
895    if program_id != programdata_account.get_owner() {
896        ic_logger_msg!(log_collector, "ProgramData owner is invalid");
897        return Err(InstructionError::InvalidAccountOwner);
898    }
899    if !programdata_account.is_writable() {
900        ic_logger_msg!(log_collector, "ProgramData is not writable");
901        return Err(InstructionError::InvalidArgument);
902    }
903
904    let program_account =
905        instruction_context.try_borrow_instruction_account(PROGRAM_ACCOUNT_INDEX)?;
906    if !program_account.is_writable() {
907        ic_logger_msg!(log_collector, "Program account is not writable");
908        return Err(InstructionError::InvalidArgument);
909    }
910    if program_account.get_owner() != program_id {
911        ic_logger_msg!(log_collector, "Program account not owned by loader");
912        return Err(InstructionError::InvalidAccountOwner);
913    }
914    let program_key = *program_account.get_key();
915    match program_account.get_state()? {
916        UpgradeableLoaderState::Program {
917            programdata_address,
918        } => {
919            if programdata_address != programdata_key {
920                ic_logger_msg!(
921                    log_collector,
922                    "Program account does not match ProgramData account"
923                );
924                return Err(InstructionError::InvalidArgument);
925            }
926        }
927        _ => {
928            ic_logger_msg!(log_collector, "Invalid Program account");
929            return Err(InstructionError::InvalidAccountData);
930        }
931    }
932    drop(program_account);
933
934    let old_len = programdata_account.get_data().len();
935    let new_len = old_len.saturating_add(additional_bytes as usize);
936    if new_len > MAX_PERMITTED_DATA_LENGTH as usize {
937        ic_logger_msg!(
938            log_collector,
939            "Extended ProgramData length of {} bytes exceeds max account data length of {} bytes",
940            new_len,
941            MAX_PERMITTED_DATA_LENGTH
942        );
943        return Err(InstructionError::InvalidRealloc);
944    }
945
946    // SIMD-0431: Minimum Extend Program Size
947    //
948    // All extensions must be >= 10 KiB in additional_bytes, unless
949    // MAX_PERMITTED_DATA_LENGTH - current_len < 10 KiB. In that case,
950    // additional_bytes must be equal to the remaining free space.
951    let headroom = (MAX_PERMITTED_DATA_LENGTH as usize).saturating_sub(old_len);
952    if additional_bytes < MINIMUM_EXTEND_PROGRAM_BYTES && (additional_bytes as usize) != headroom {
953        ic_logger_msg!(
954            log_collector,
955            "ExtendProgram requires a minimum of {} additional bytes or to extend to maximum \
956             size, but only {} were requested",
957            MINIMUM_EXTEND_PROGRAM_BYTES,
958            additional_bytes,
959        );
960        return Err(InstructionError::InvalidArgument);
961    }
962
963    let clock_slot = invoke_context
964        .environment_config
965        .sysvar_cache()
966        .get_clock()
967        .map(|clock| clock.slot)?;
968
969    let upgrade_authority_address = if let UpgradeableLoaderState::ProgramData {
970        slot,
971        upgrade_authority_address,
972    } = programdata_account.get_state()?
973    {
974        if clock_slot == slot {
975            ic_logger_msg!(log_collector, "Program was extended in this block already");
976            return Err(InstructionError::InvalidArgument);
977        }
978
979        if upgrade_authority_address.is_none() {
980            ic_logger_msg!(
981                log_collector,
982                "Cannot extend ProgramData accounts that are not upgradeable"
983            );
984            return Err(InstructionError::Immutable);
985        }
986
987        if check_authority {
988            let authority_key =
989                Some(*instruction_context.get_key_of_instruction_account(AUTHORITY_ACCOUNT_INDEX)?);
990            if upgrade_authority_address != authority_key {
991                ic_logger_msg!(log_collector, "Incorrect upgrade authority provided");
992                return Err(InstructionError::IncorrectAuthority);
993            }
994            if !instruction_context.is_instruction_account_signer(AUTHORITY_ACCOUNT_INDEX)? {
995                ic_logger_msg!(log_collector, "Upgrade authority did not sign");
996                return Err(InstructionError::MissingRequiredSignature);
997            }
998        }
999
1000        upgrade_authority_address
1001    } else {
1002        ic_logger_msg!(log_collector, "ProgramData state is invalid");
1003        return Err(InstructionError::InvalidAccountData);
1004    };
1005
1006    let required_payment = {
1007        let balance = programdata_account.get_lamports();
1008        let rent = invoke_context
1009            .environment_config
1010            .sysvar_cache()
1011            .get_rent()?;
1012        let min_balance = rent.minimum_balance(new_len).max(1);
1013        min_balance.saturating_sub(balance)
1014    };
1015
1016    // Borrowed accounts need to be dropped before native_invoke_signed
1017    drop(programdata_account);
1018
1019    // Dereference the program ID to prevent overlapping mutable/immutable borrow of invoke context
1020    let program_id = *program_id;
1021    if required_payment > 0 {
1022        let payer_key =
1023            *instruction_context.get_key_of_instruction_account(optional_payer_account_index)?;
1024
1025        invoke_context.native_invoke_signed(
1026            system_instruction::transfer(&payer_key, &programdata_key, required_payment),
1027            &[],
1028        )?;
1029    }
1030
1031    let transaction_context = &invoke_context.transaction_context;
1032    let instruction_context = transaction_context.get_current_instruction_context()?;
1033    let mut programdata_account =
1034        instruction_context.try_borrow_instruction_account(PROGRAM_DATA_ACCOUNT_INDEX)?;
1035    programdata_account.set_data_length(new_len)?;
1036
1037    let programdata_data_offset = UpgradeableLoaderState::size_of_programdata_metadata();
1038
1039    deploy_program!(
1040        invoke_context,
1041        &program_key,
1042        &program_id,
1043        programdata_account
1044            .get_data()
1045            .get(programdata_data_offset..)
1046            .ok_or(InstructionError::AccountDataTooSmall)?,
1047        clock_slot,
1048        false, // disable_sbpf_v0_v1_v2_deployment // explicitly continue to allow them for extend program
1049    );
1050    drop(programdata_account);
1051
1052    let mut programdata_account =
1053        instruction_context.try_borrow_instruction_account(PROGRAM_DATA_ACCOUNT_INDEX)?;
1054    programdata_account.set_state(&UpgradeableLoaderState::ProgramData {
1055        slot: clock_slot,
1056        upgrade_authority_address,
1057    })?;
1058
1059    ic_logger_msg!(
1060        log_collector,
1061        "Extended ProgramData account by {} bytes",
1062        additional_bytes
1063    );
1064
1065    Ok(())
1066}
1067
1068fn common_close_account(
1069    authority_address: &Option<Pubkey>,
1070    instruction_context: &InstructionContext,
1071    log_collector: &Option<Rc<RefCell<LogCollector>>>,
1072) -> Result<(), InstructionError> {
1073    if authority_address.is_none() {
1074        ic_logger_msg!(log_collector, "Account is immutable");
1075        return Err(InstructionError::Immutable);
1076    }
1077    if *authority_address != Some(*instruction_context.get_key_of_instruction_account(2)?) {
1078        ic_logger_msg!(log_collector, "Incorrect authority provided");
1079        return Err(InstructionError::IncorrectAuthority);
1080    }
1081    if !instruction_context.is_instruction_account_signer(2)? {
1082        ic_logger_msg!(log_collector, "Authority did not sign");
1083        return Err(InstructionError::MissingRequiredSignature);
1084    }
1085
1086    let mut close_account = instruction_context.try_borrow_instruction_account(0)?;
1087    let mut recipient_account = instruction_context.try_borrow_instruction_account(1)?;
1088
1089    recipient_account.checked_add_lamports(close_account.get_lamports())?;
1090    close_account.set_lamports(0)?;
1091    close_account.set_data_length(UpgradeableLoaderState::size_of_uninitialized())?;
1092    close_account.set_state(&UpgradeableLoaderState::Uninitialized)?;
1093    Ok(())
1094}
1095
1096#[cfg_attr(feature = "svm-internal", qualifiers(pub))]
1097mod test_utils {
1098    #[cfg(all(feature = "svm-internal", feature = "metrics"))]
1099    use solana_program_runtime::program_metrics::LoadProgramMetrics;
1100    #[cfg(feature = "svm-internal")]
1101    use {
1102        super::*, solana_account::ReadableAccount,
1103        solana_program_runtime::loaded_programs::ProgramRuntimeEnvironment,
1104        solana_syscalls::create_program_runtime_environment,
1105    };
1106
1107    #[cfg(feature = "svm-internal")]
1108    fn check_loader_id(id: &Pubkey) -> bool {
1109        bpf_loader::check_id(id)
1110            || bpf_loader_deprecated::check_id(id)
1111            || bpf_loader_upgradeable::check_id(id)
1112    }
1113
1114    #[cfg(feature = "svm-internal")]
1115    #[cfg_attr(feature = "svm-internal", qualifiers(pub))]
1116    fn load_all_invoked_programs(invoke_context: &mut InvokeContext) {
1117        let program_runtime_environment = create_program_runtime_environment(
1118            invoke_context.get_feature_set(),
1119            invoke_context.get_compute_budget(),
1120            false, /* deployment */
1121            false, /* debugging_features */
1122        )
1123        .unwrap();
1124        let num_accounts = invoke_context.transaction_context.get_number_of_accounts();
1125        for index in 0..num_accounts {
1126            let account = invoke_context
1127                .transaction_context
1128                .accounts()
1129                .try_borrow(index)
1130                .expect("Failed to get the account");
1131
1132            let owner = account.owner();
1133            if check_loader_id(owner) {
1134                let programdata_data_offset = 0;
1135                let pubkey = invoke_context
1136                    .transaction_context
1137                    .get_key_of_account_at_index(index)
1138                    .expect("Failed to get account key");
1139
1140                let programdata = account
1141                    .data()
1142                    .get(programdata_data_offset.min(account.data().len())..)
1143                    .unwrap();
1144                let loaded_program = ProgramCacheEntry::load(
1145                    owner,
1146                    ProgramRuntimeEnvironment::clone(&program_runtime_environment),
1147                    0,
1148                    programdata,
1149                    #[cfg(feature = "metrics")]
1150                    &mut LoadProgramMetrics::default(),
1151                )
1152                .map_err(|_| InstructionError::InvalidAccountData);
1153                if let Ok(loaded_program) = loaded_program {
1154                    invoke_context
1155                        .program_cache_for_tx_batch
1156                        .store_modified_entry(*pubkey, Arc::new(loaded_program));
1157                }
1158            }
1159        }
1160    }
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165    use {
1166        super::*,
1167        assert_matches::assert_matches,
1168        solana_account::{AccountSharedData, state_traits::StateMutWincode as _},
1169        solana_epoch_schedule::EpochSchedule,
1170        solana_instruction_error::InstructionError,
1171        solana_program_runtime::{
1172            loaded_programs::ProgramRuntimeEnvironment, program_metrics::ProgramStatistics,
1173            vm::calculate_heap_cost, with_mock_invoke_context,
1174        },
1175        solana_pubkey::Pubkey,
1176        solana_sbpf::program::BuiltinProgram,
1177        solana_sdk_ids::sysvar,
1178        solana_svm_type_overrides::sync::atomic::{AtomicU64, Ordering},
1179        solana_sysvar_id::SysvarId,
1180        std::{fs::File, io::Read},
1181        test_case::test_case,
1182    };
1183
1184    fn create_sysvar_account<T>(value: &T) -> AccountSharedData
1185    where
1186        T: wincode::SchemaWrite<solana_account::WincodeConfig, Src = T>
1187            + for<'de> wincode::SchemaRead<'de, solana_account::WincodeConfig, Dst = T>
1188            + SysvarId,
1189    {
1190        let serialized_len = wincode::config::serialized_size(value, solana_account::WINCODE_CONFIG)
1191            .unwrap() as usize;
1192        let canonical_data_len = match T::id() {
1193            sysvar::clock::ID => solana_clock::SIZE,
1194            sysvar::epoch_schedule::ID => solana_epoch_schedule::SIZE,
1195            sysvar::rent::ID => solana_rent::SIZE,
1196            id => panic!("unsupported sysvar: {id}"),
1197        };
1198        let required_data_len = canonical_data_len.max(serialized_len);
1199        AccountSharedData::new_data_with_space(1, value, required_data_len, &sysvar::id()).unwrap()
1200    }
1201
1202    #[test_case(false; "legacy")]
1203    #[test_case(true; "SIMD-0648")]
1204    fn test_write_instruction_matches_sdk(unbound_loader_v3_instruction_data: bool) {
1205        let instruction = UpgradeableLoaderInstruction::Write {
1206            offset: 7,
1207            bytes: vec![1, 2, 3],
1208        };
1209        // If this pattern fails, it means UpgradeableLoaderInstruction::Write has been changed
1210        // in the SDK. WriteInstruction in parse_write_instruction() MUST be changed to match it.
1211        let UpgradeableLoaderInstruction::Write { offset, bytes } = &instruction else {
1212            unreachable!()
1213        };
1214        let serialized = bincode::serialize(&instruction).unwrap();
1215        assert!(serialized.starts_with(&WRITE_INSTRUCTION_TAG));
1216        assert_eq!(
1217            parse_write_instruction(&serialized, unbound_loader_v3_instruction_data).unwrap(),
1218            (*offset, bytes.as_slice())
1219        );
1220    }
1221
1222    #[test]
1223    fn test_calculate_heap_cost() {
1224        let heap_cost = 8_u64;
1225
1226        // heap allocations are in 32K block, `heap_cost` of CU is consumed per additional 32k
1227
1228        // assert less than 32K heap should cost zero unit
1229        assert_eq!(0, calculate_heap_cost(31 * 1024, heap_cost));
1230
1231        // assert exact 32K heap should be cost zero unit
1232        assert_eq!(0, calculate_heap_cost(32 * 1024, heap_cost));
1233
1234        // assert slightly more than 32K heap should cost 1 * heap_cost
1235        assert_eq!(heap_cost, calculate_heap_cost(33 * 1024, heap_cost));
1236
1237        // assert exact 64K heap should cost 1 * heap_cost
1238        assert_eq!(heap_cost, calculate_heap_cost(64 * 1024, heap_cost));
1239    }
1240
1241    fn deploy_test_program(
1242        invoke_context: &mut InvokeContext,
1243        program_id: Pubkey,
1244    ) -> Result<(), InstructionError> {
1245        let mut file = File::open("test_elfs/out/sbpfv3_return_ok.so").expect("file open failed");
1246        let mut elf = Vec::new();
1247        file.read_to_end(&mut elf).unwrap();
1248        deploy_program!(
1249            invoke_context,
1250            &program_id,
1251            &bpf_loader_upgradeable::id(),
1252            &elf,
1253            2_u64,
1254            true, // disable_sbpf_v0_v1_v2_deployment
1255        );
1256        Ok(())
1257    }
1258
1259    // Concurrency rationale: these tests construct `ProgramCacheEntry` instances
1260    // directly. The struct's `latest_access_slot: AtomicU64` field is defined in
1261    // `solana-program-runtime`; under the `shuttle-test` feature
1262    // `solana-svm-type-overrides` swaps `std::sync::atomic::AtomicU64` for
1263    // `shuttle::sync::atomic::AtomicU64`, whose Shuttle-backed operations
1264    // (load, fetch_max, and similar) must run inside an active Shuttle
1265    // scheduler. We therefore extract the test bodies into `do_test_*` helpers
1266    // and drive them via `shuttle::check_random` stubs when the feature is on.
1267    // We use `check_random` only (no `check_dfs` companion) because the test
1268    // bodies spawn no Shuttle threads, so DFS gives no meaningful interleaving
1269    // coverage; `check_random` is enough to provide the scheduler context.
1270    // This matches the single-scheduler pattern used in
1271    // `net-utils/src/token_bucket.rs` and `poh/src/record_channels.rs`.
1272    //
1273    // 100 iterations is intentionally low: the test bodies are single-threaded
1274    // (no `shuttle::thread::spawn`), so additional iterations validate only
1275    // the harness wiring, not concurrent interleavings. Bump this if a future
1276    // refactor introduces real concurrency in the test bodies.
1277    #[cfg(feature = "shuttle-test")]
1278    const PROGRAM_USAGE_COUNT_RANDOM_ITERATIONS: usize = 100;
1279
1280    #[test]
1281    fn test_program_usage_count_on_upgrade() {
1282        #[cfg(feature = "shuttle-test")]
1283        shuttle::check_random(
1284            do_test_program_usage_count_on_upgrade,
1285            PROGRAM_USAGE_COUNT_RANDOM_ITERATIONS,
1286        );
1287        #[cfg(not(feature = "shuttle-test"))]
1288        do_test_program_usage_count_on_upgrade();
1289    }
1290
1291    fn do_test_program_usage_count_on_upgrade() {
1292        let transaction_accounts = vec![(
1293            sysvar::epoch_schedule::id(),
1294            create_sysvar_account(&EpochSchedule::default()),
1295        )];
1296        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1297        let program_id = Pubkey::new_unique();
1298        let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1299        let stats = ProgramStatistics {
1300            uses: 100.into(),
1301            ..Default::default()
1302        };
1303        let program = ProgramCacheEntry {
1304            program: ProgramCacheEntryType::Unloaded(env),
1305            account_owner: ProgramCacheEntryOwner::LoaderV2,
1306            deployment_slot: 0,
1307            stats: stats.into(),
1308            latest_access_slot: AtomicU64::new(0),
1309        };
1310        invoke_context
1311            .program_cache_for_tx_batch
1312            .replenish(program_id, Arc::new(program));
1313        invoke_context
1314            .program_cache_for_tx_batch
1315            .set_slot_for_tests(2);
1316
1317        assert_matches!(
1318            deploy_test_program(&mut invoke_context, program_id,),
1319            Ok(())
1320        );
1321
1322        let updated_program = invoke_context
1323            .program_cache_for_tx_batch
1324            .find(&program_id)
1325            .expect("Didn't find upgraded program in the cache");
1326
1327        assert_eq!(updated_program.deployment_slot, 2);
1328        assert_eq!(updated_program.stats.uses.load(Ordering::Relaxed), 100);
1329    }
1330
1331    #[test]
1332    fn test_program_usage_count_on_non_upgrade() {
1333        #[cfg(feature = "shuttle-test")]
1334        shuttle::check_random(
1335            do_test_program_usage_count_on_non_upgrade,
1336            PROGRAM_USAGE_COUNT_RANDOM_ITERATIONS,
1337        );
1338        #[cfg(not(feature = "shuttle-test"))]
1339        do_test_program_usage_count_on_non_upgrade();
1340    }
1341
1342    fn do_test_program_usage_count_on_non_upgrade() {
1343        let transaction_accounts = vec![(
1344            sysvar::epoch_schedule::id(),
1345            create_sysvar_account(&EpochSchedule::default()),
1346        )];
1347        with_mock_invoke_context!(invoke_context, transaction_context, transaction_accounts);
1348        let program_id = Pubkey::new_unique();
1349        let env = ProgramRuntimeEnvironment::from(BuiltinProgram::new_mock());
1350        let stats = ProgramStatistics {
1351            uses: 100.into(),
1352            ..Default::default()
1353        };
1354        let program = ProgramCacheEntry {
1355            program: ProgramCacheEntryType::Unloaded(env),
1356            account_owner: ProgramCacheEntryOwner::LoaderV2,
1357            deployment_slot: 0,
1358            stats: stats.into(),
1359            latest_access_slot: AtomicU64::new(0),
1360        };
1361        invoke_context
1362            .program_cache_for_tx_batch
1363            .replenish(program_id, Arc::new(program));
1364        invoke_context
1365            .program_cache_for_tx_batch
1366            .set_slot_for_tests(2);
1367
1368        let program_id2 = Pubkey::new_unique();
1369        assert_matches!(
1370            deploy_test_program(&mut invoke_context, program_id2),
1371            Ok(())
1372        );
1373
1374        let program2 = invoke_context
1375            .program_cache_for_tx_batch
1376            .find(&program_id2)
1377            .expect("Didn't find upgraded program in the cache");
1378
1379        assert_eq!(program2.deployment_slot, 2);
1380        assert_eq!(program2.stats.uses.load(Ordering::Relaxed), 0);
1381    }
1382}