Skip to main content

solana_program_test/
lib.rs

1#![cfg(feature = "agave-unstable-api")]
2//! The solana-program-test provides a BanksClient-based test framework SBF programs
3#![allow(clippy::arithmetic_side_effects)]
4
5// Export tokio for test clients
6pub use tokio;
7use {
8    agave_feature_set::{FEATURE_NAMES, FeatureSet, raise_cpi_nesting_limit_to_8},
9    async_trait::async_trait,
10    base64::{Engine, prelude::BASE64_STANDARD},
11    chrono_humanize::{Accuracy, HumanTime, Tense},
12    log::*,
13    solana_account::{
14        Account, AccountSharedData, ReadableAccount, state_traits::StateMutWincode as _,
15    },
16    solana_account_info::AccountInfo,
17    solana_accounts_db::accounts_db::ACCOUNTS_DB_CONFIG_FOR_TESTING,
18    solana_address::Address,
19    solana_banks_client::start_client,
20    solana_banks_server::banks_server::start_local_server,
21    solana_clock::{Clock, Epoch, Slot},
22    solana_cluster_type::ClusterType,
23    solana_compute_budget::compute_budget::{ComputeBudget, SVMTransactionExecutionCost},
24    solana_epoch_rewards::EpochRewards,
25    solana_epoch_schedule::EpochSchedule,
26    solana_fee_calculator::{DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE, FeeRateGovernor},
27    solana_genesis_config::GenesisConfig,
28    solana_hash::Hash,
29    solana_instruction::Instruction,
30    solana_instruction_error::{InstructionError, UNSUPPORTED_SYSVAR},
31    solana_keypair::Keypair,
32    solana_native_token::LAMPORTS_PER_SOL,
33    solana_poh_config::PohConfig,
34    solana_program_binaries as programs,
35    solana_program_entrypoint::{SUCCESS, deserialize},
36    solana_program_error::{ProgramError, ProgramResult},
37    solana_program_runtime::{
38        invoke_context::BuiltinFunctionRegisterer, program_cache_entry::ProgramCacheEntry,
39        serialization::serialize_parameters, stable_log, sysvar_cache::SysvarCache,
40    },
41    solana_pubkey::Pubkey,
42    solana_rent::Rent,
43    solana_runtime::{
44        bank::Bank,
45        bank_forks::BankForks,
46        commitment::BlockCommitmentCache,
47        genesis_utils::{GenesisConfigInfo, create_genesis_config_with_leader_ex},
48        runtime_config::RuntimeConfig,
49    },
50    solana_sdk_ids::sysvar,
51    solana_signer::Signer,
52    solana_svm_log_collector::ic_msg,
53    solana_sysvar::last_restart_slot::LastRestartSlot,
54    solana_sysvar_id::SysvarId,
55    solana_vote_program::vote_state::{VoteStateV4, VoteStateVersions},
56    std::{
57        cell::RefCell,
58        collections::{HashMap, HashSet},
59        fs::File,
60        io::{self, Read},
61        mem::transmute,
62        panic::AssertUnwindSafe,
63        path::{Path, PathBuf},
64        ptr,
65        sync::{
66            Arc, RwLock,
67            atomic::{AtomicBool, Ordering},
68        },
69        time::{Duration, Instant},
70    },
71    thiserror::Error,
72    tokio::task::JoinHandle,
73    wincode::Serialize,
74};
75// Export types so test clients can limit their solana crate dependencies
76pub use {
77    solana_banks_client::{BanksClient, BanksClientError},
78    solana_banks_interface::BanksTransactionResultWithMetadata,
79    solana_program_runtime::invoke_context::InvokeContext,
80    solana_sbpf::{
81        error::EbpfError,
82        memory_region::MemoryMapping,
83        program::BuiltinFunctionDefinition,
84        vm::{EbpfVm, EncryptedHostAddressToEbpfVm, get_runtime_environment_key},
85    },
86    solana_transaction_context::IndexOfAccount,
87};
88
89/// Errors from the program test environment
90#[derive(Error, Debug, PartialEq, Eq)]
91pub enum ProgramTestError {
92    /// The chosen warp slot is not in the future, so warp is not performed
93    #[error("Warp slot not in the future")]
94    InvalidWarpSlot,
95}
96
97thread_local! {
98    static INVOKE_CONTEXT: RefCell<Option<usize>> = const { RefCell::new(None) };
99}
100fn set_invoke_context(new: &mut InvokeContext) {
101    INVOKE_CONTEXT.with(|invoke_context| unsafe {
102        invoke_context.replace(Some(transmute::<&mut InvokeContext, usize>(new)))
103    });
104}
105fn get_invoke_context<'a, 'b>() -> &'a mut InvokeContext<'b, 'b> {
106    let ptr = INVOKE_CONTEXT.with(|invoke_context| match *invoke_context.borrow() {
107        Some(val) => val,
108        None => panic!("Invoke context not set!"),
109    });
110    unsafe { &mut *ptr::with_exposed_provenance_mut(ptr) }
111}
112
113pub fn invoke_builtin_function(
114    builtin_function: solana_program_entrypoint::ProcessInstruction,
115    invoke_context: &mut InvokeContext,
116) -> Result<u64, Box<dyn std::error::Error>> {
117    set_invoke_context(invoke_context);
118
119    let transaction_context = &invoke_context.transaction_context;
120    let instruction_context = transaction_context.get_current_instruction_context()?;
121    let instruction_account_indices = 0..instruction_context.get_number_of_instruction_accounts();
122
123    // mock builtin program must consume units
124    invoke_context.compute_meter.consume_checked(1)?;
125
126    let log_collector = invoke_context.get_log_collector();
127    let program_id = instruction_context.get_program_key()?;
128    stable_log::program_invoke(
129        &log_collector,
130        program_id,
131        invoke_context.get_stack_height(),
132    );
133
134    // Copy indices_in_instruction into a HashSet to ensure there are no duplicates
135    let deduplicated_indices: HashSet<IndexOfAccount> = instruction_account_indices.collect();
136
137    let direct_account_pointers_in_program_input = invoke_context
138        .get_feature_set()
139        .direct_account_pointers_in_program_input;
140
141    // Serialize entrypoint parameters with SBF ABI
142    let (mut parameter_bytes, _regions, _account_lengths, _instruction_data_offset) =
143        serialize_parameters(
144            &instruction_context,
145            false, // There is no VM so virtual_address_space_adjustments can not be implemented here
146            false, // There is no VM so account_data_direct_mapping can not be implemented here
147            direct_account_pointers_in_program_input,
148        )?;
149
150    // Deserialize data back into instruction params
151    let (program_id, account_infos, input) =
152        unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) };
153
154    // Execute the program
155    match std::panic::catch_unwind(AssertUnwindSafe(|| {
156        builtin_function(program_id, &account_infos, input)
157    })) {
158        Ok(program_result) => {
159            program_result.map_err(|program_error| {
160                let err = InstructionError::from(u64::from(program_error));
161                stable_log::program_failure(&log_collector, program_id, &err);
162                let err: Box<dyn std::error::Error> = Box::new(err);
163                err
164            })?;
165        }
166        Err(_panic_error) => {
167            let err = InstructionError::ProgramFailedToComplete;
168            stable_log::program_failure(&log_collector, program_id, &err);
169            let err: Box<dyn std::error::Error> = Box::new(err);
170            Err(err)?;
171        }
172    };
173
174    stable_log::program_success(&log_collector, program_id);
175
176    // Lookup table for AccountInfo
177    let account_info_map: HashMap<_, _> = account_infos.into_iter().map(|a| (a.key, a)).collect();
178
179    // Re-fetch the instruction context. The previous reference may have been
180    // invalidated due to the `set_invoke_context` in a CPI.
181    let transaction_context = &invoke_context.transaction_context;
182    let instruction_context = transaction_context.get_current_instruction_context()?;
183
184    // Commit AccountInfo changes back into KeyedAccounts
185    for i in deduplicated_indices.into_iter() {
186        let mut borrowed_account = instruction_context.try_borrow_instruction_account(i)?;
187        if borrowed_account.is_writable()
188            && let Some(account_info) = account_info_map.get(borrowed_account.get_key())
189        {
190            if borrowed_account.get_lamports() != account_info.lamports() {
191                borrowed_account.set_lamports(account_info.lamports())?;
192            }
193
194            if borrowed_account
195                .can_data_be_resized(account_info.data_len())
196                .is_ok()
197            {
198                borrowed_account.set_data_from_slice(&account_info.data.borrow())?;
199            }
200            if borrowed_account.get_owner() != account_info.owner {
201                borrowed_account.set_owner(account_info.owner.as_ref())?;
202            }
203        }
204    }
205
206    Ok(0)
207}
208
209/// Converts a `solana-program`-style entrypoint into the runtime's entrypoint style, for
210/// use with `ProgramTest::add_program`
211#[macro_export]
212macro_rules! processor {
213    ($builtin_function:expr) => {{
214        struct Converter;
215        impl $crate::BuiltinFunctionDefinition<$crate::InvokeContext<'_, '_>> for Converter {
216            type Error = Box<dyn std::error::Error>;
217            fn rust(
218                _: &mut $crate::InvokeContext<'_, '_>,
219                _: u64,
220                _: u64,
221                _: u64,
222                _: u64,
223                _: u64,
224            ) -> Result<u64, Box<dyn std::error::Error>> {
225                unreachable!()
226            }
227            fn vm(
228                mut vm: $crate::EncryptedHostAddressToEbpfVm<$crate::InvokeContext>,
229                _: u64,
230                _: u64,
231                _: u64,
232                _: u64,
233                _: u64,
234            ) {
235                unsafe {
236                    vm.with_vm(|vm| {
237                        vm.program_result =
238                            $crate::invoke_builtin_function($builtin_function, vm.context())
239                                .map_err(|err| $crate::EbpfError::SyscallError(err))
240                                .into();
241                    });
242                }
243            }
244        };
245        Some(<Converter as $crate::BuiltinFunctionDefinition<_>>::register)
246    }};
247}
248
249fn get_sysvar<T: Clone>(
250    sysvar: Result<Arc<T>, InstructionError>,
251    var_addr: *mut u8,
252    sysvar_size: usize,
253) -> u64 {
254    let invoke_context = get_invoke_context();
255    if invoke_context
256        .compute_meter
257        .consume_checked(invoke_context.get_execution_cost().sysvar_base_cost + sysvar_size as u64)
258        .is_err()
259    {
260        panic!("Exceeded compute budget");
261    }
262
263    match sysvar {
264        Ok(sysvar_data) => unsafe {
265            *(var_addr as *mut _ as *mut T) = T::clone(&sysvar_data);
266            SUCCESS
267        },
268        Err(_) => UNSUPPORTED_SYSVAR,
269    }
270}
271
272/// Calls the native program-test stub for the legacy clock sysvar syscall.
273pub fn sol_get_clock_sysvar(var_addr: *mut u8) -> u64 {
274    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_clock_sysvar(
275        &SyscallStubs {},
276        var_addr,
277    )
278}
279
280/// Calls the native program-test stub for the legacy epoch schedule sysvar syscall.
281pub fn sol_get_epoch_schedule_sysvar(var_addr: *mut u8) -> u64 {
282    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_epoch_schedule_sysvar(
283        &SyscallStubs {},
284        var_addr,
285    )
286}
287
288/// Calls the native program-test stub for the legacy epoch rewards sysvar syscall.
289pub fn sol_get_epoch_rewards_sysvar(var_addr: *mut u8) -> u64 {
290    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_epoch_rewards_sysvar(
291        &SyscallStubs {},
292        var_addr,
293    )
294}
295
296/// Calls the native program-test stub for the legacy fees sysvar syscall.
297pub fn sol_get_fees_sysvar(var_addr: *mut u8) -> u64 {
298    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_fees_sysvar(
299        &SyscallStubs {},
300        var_addr,
301    )
302}
303
304/// Calls the native program-test stub for the legacy rent sysvar syscall.
305pub fn sol_get_rent_sysvar(var_addr: *mut u8) -> u64 {
306    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_rent_sysvar(
307        &SyscallStubs {},
308        var_addr,
309    )
310}
311
312/// Calls the native program-test stub for the legacy last restart slot syscall.
313pub fn sol_get_last_restart_slot(var_addr: *mut u8) -> u64 {
314    <SyscallStubs as solana_sysvar::program_stubs::SyscallStubs>::sol_get_last_restart_slot(
315        &SyscallStubs {},
316        var_addr,
317    )
318}
319
320struct SyscallStubs {}
321
322impl SyscallStubs {
323    fn fetch_and_write_sysvar<T: Serialize<Src = T>>(
324        &self,
325        var_addr: *mut u8,
326        offset: u64,
327        length: u64,
328        fetch: impl FnOnce(&SysvarCache) -> Result<Arc<T>, InstructionError>,
329    ) -> u64 {
330        // Consume compute units for the syscall.
331        let invoke_context = get_invoke_context();
332        let SVMTransactionExecutionCost {
333            sysvar_base_cost,
334            cpi_bytes_per_unit,
335            mem_op_base_cost,
336            ..
337        } = *invoke_context.get_execution_cost();
338
339        let sysvar_id_cost = 32_u64.checked_div(cpi_bytes_per_unit).unwrap_or(0);
340        let sysvar_buf_cost = length.checked_div(cpi_bytes_per_unit).unwrap_or(0);
341
342        if invoke_context
343            .compute_meter
344            .consume_checked(
345                sysvar_base_cost
346                    .saturating_add(sysvar_id_cost)
347                    .saturating_add(std::cmp::max(sysvar_buf_cost, mem_op_base_cost)),
348            )
349            .is_err()
350        {
351            panic!("Exceeded compute budget");
352        }
353
354        // Fetch the sysvar from the cache.
355        let Ok(sysvar) = fetch(get_invoke_context().environment_config.sysvar_cache()) else {
356            return UNSUPPORTED_SYSVAR;
357        };
358
359        // Check that the requested length is not greater than
360        // the actual serialized length of the sysvar data.
361        let Ok(expected_length) = wincode::serialized_size(sysvar.as_ref()) else {
362            return UNSUPPORTED_SYSVAR;
363        };
364
365        if offset.saturating_add(length) > expected_length {
366            return UNSUPPORTED_SYSVAR;
367        }
368
369        // Write only the requested slice [offset, offset + length).
370        if let Ok(serialized) = wincode::serialize(sysvar.as_ref()) {
371            unsafe {
372                ptr::copy_nonoverlapping(
373                    serialized[offset as usize..].as_ptr(),
374                    var_addr,
375                    length as usize,
376                )
377            };
378            SUCCESS
379        } else {
380            UNSUPPORTED_SYSVAR
381        }
382    }
383}
384impl solana_sysvar::program_stubs::SyscallStubs for SyscallStubs {
385    fn sol_log(&self, message: &str) {
386        let invoke_context = get_invoke_context();
387        ic_msg!(invoke_context, "Program log: {}", message);
388    }
389
390    fn sol_invoke_signed(
391        &self,
392        instruction: &Instruction,
393        account_infos: &[AccountInfo],
394        signers_seeds: &[&[&[u8]]],
395    ) -> ProgramResult {
396        let invoke_context = get_invoke_context();
397        let log_collector = invoke_context.get_log_collector();
398
399        stable_log::program_invoke(
400            &log_collector,
401            &instruction.program_id,
402            invoke_context.get_stack_height(),
403        );
404
405        // Copy the caller's account_info modifications into the invoke context's
406        // accounts so the callee can see them. The set of accounts participating
407        // in the CPI is derived from the instruction's metas, mirroring what
408        // `native_invoke_signed` prepares internally.
409        let transaction_context = &invoke_context.transaction_context;
410        let instruction_context = transaction_context
411            .get_current_instruction_context()
412            .unwrap();
413        let mut account_indices = Vec::with_capacity(instruction.accounts.len());
414        for account_meta in instruction.accounts.iter() {
415            let index_in_transaction = transaction_context
416                .find_index_of_account(&account_meta.pubkey)
417                .ok_or(InstructionError::MissingAccount)
418                .unwrap();
419            let account_info_index = account_infos
420                .iter()
421                .position(|account_info| account_info.unsigned_key() == &account_meta.pubkey)
422                .ok_or(InstructionError::MissingAccount)
423                .unwrap();
424            let account_info = &account_infos[account_info_index];
425            let index_in_caller = instruction_context
426                .get_index_of_account_in_instruction(index_in_transaction)
427                .unwrap();
428            let mut borrowed_account = instruction_context
429                .try_borrow_instruction_account(index_in_caller)
430                .unwrap();
431            if borrowed_account.get_lamports() != account_info.lamports() {
432                borrowed_account
433                    .set_lamports(account_info.lamports())
434                    .unwrap();
435            }
436            let account_info_data = account_info.try_borrow_data().unwrap();
437            // The redundant check helps to avoid the expensive data comparison if we can
438            match borrowed_account.can_data_be_resized(account_info_data.len()) {
439                Ok(()) => borrowed_account
440                    .set_data_from_slice(&account_info_data)
441                    .unwrap(),
442                Err(err) if borrowed_account.get_data() != *account_info_data => {
443                    panic!("{err:?}");
444                }
445                _ => {}
446            }
447            // Change the owner at the end so that we are allowed to change the lamports and data before
448            if borrowed_account.get_owner() != account_info.owner {
449                borrowed_account
450                    .set_owner(account_info.owner.as_ref())
451                    .unwrap();
452            }
453            if account_meta.is_writable {
454                account_indices.push((index_in_transaction, account_info_index));
455            }
456        }
457
458        invoke_context
459            .native_invoke_signed(instruction.clone(), signers_seeds)
460            .map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?;
461
462        // Copy invoke_context accounts modifications into caller's account_info
463        let transaction_context = &invoke_context.transaction_context;
464        let instruction_context = transaction_context
465            .get_current_instruction_context()
466            .unwrap();
467        for (index_in_transaction, account_info_index) in account_indices.into_iter() {
468            let index_in_caller = instruction_context
469                .get_index_of_account_in_instruction(index_in_transaction)
470                .unwrap();
471            let borrowed_account = instruction_context
472                .try_borrow_instruction_account(index_in_caller)
473                .unwrap();
474            let account_info = &account_infos[account_info_index];
475            **account_info.try_borrow_mut_lamports().unwrap() = borrowed_account.get_lamports();
476            if account_info.owner != borrowed_account.get_owner() {
477                // TODO Figure out a better way to allow the System Program to set the account owner
478                #[allow(clippy::transmute_ptr_to_ptr)]
479                #[allow(mutable_transmutes)]
480                let account_info_mut =
481                    unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
482                *account_info_mut = *borrowed_account.get_owner();
483            }
484
485            let new_data = borrowed_account.get_data();
486            let new_len = new_data.len();
487
488            // Resize account_info data
489            if account_info.data_len() != new_len {
490                account_info.resize(new_len)?;
491            }
492
493            // Clone the data
494            let mut data = account_info.try_borrow_mut_data()?;
495            data.clone_from_slice(new_data);
496        }
497
498        stable_log::program_success(&log_collector, &instruction.program_id);
499        Ok(())
500    }
501
502    fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 {
503        get_sysvar(
504            get_invoke_context()
505                .environment_config
506                .sysvar_cache()
507                .get_clock(),
508            var_addr,
509            solana_clock::SIZE,
510        )
511    }
512
513    fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 {
514        get_sysvar(
515            get_invoke_context()
516                .environment_config
517                .sysvar_cache()
518                .get_epoch_schedule(),
519            var_addr,
520            solana_epoch_schedule::SIZE,
521        )
522    }
523
524    fn sol_get_epoch_rewards_sysvar(&self, var_addr: *mut u8) -> u64 {
525        get_sysvar(
526            get_invoke_context()
527                .environment_config
528                .sysvar_cache()
529                .get_epoch_rewards(),
530            var_addr,
531            solana_epoch_rewards::SIZE,
532        )
533    }
534
535    #[allow(deprecated)]
536    fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 {
537        get_sysvar(
538            get_invoke_context()
539                .environment_config
540                .sysvar_cache()
541                .get_fees(),
542            var_addr,
543            solana_sysvar::fees::SIZE,
544        )
545    }
546
547    fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 {
548        get_sysvar(
549            get_invoke_context()
550                .environment_config
551                .sysvar_cache()
552                .get_rent(),
553            var_addr,
554            solana_rent::SIZE,
555        )
556    }
557
558    fn sol_get_last_restart_slot(&self, var_addr: *mut u8) -> u64 {
559        get_sysvar(
560            get_invoke_context()
561                .environment_config
562                .sysvar_cache()
563                .get_last_restart_slot(),
564            var_addr,
565            solana_sysvar::last_restart_slot::SIZE,
566        )
567    }
568
569    fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
570        let (program_id, data) = get_invoke_context().transaction_context.get_return_data();
571        Some((*program_id, data.to_vec()))
572    }
573
574    fn sol_set_return_data(&self, data: &[u8]) {
575        let invoke_context = get_invoke_context();
576        let transaction_context = &mut invoke_context.transaction_context;
577        let instruction_context = transaction_context
578            .get_current_instruction_context()
579            .unwrap();
580        let caller = *instruction_context.get_program_key().unwrap();
581        transaction_context
582            .set_return_data(caller, data.to_vec())
583            .unwrap();
584    }
585
586    fn sol_get_stack_height(&self) -> u64 {
587        let invoke_context = get_invoke_context();
588        invoke_context.get_stack_height().try_into().unwrap()
589    }
590
591    fn sol_get_sysvar(
592        &self,
593        sysvar_id_addr: *const u8,
594        var_addr: *mut u8,
595        offset: u64,
596        length: u64,
597    ) -> u64 {
598        let sysvar_id = unsafe { &*(sysvar_id_addr as *const Pubkey) };
599
600        match *sysvar_id {
601            id if id == Clock::id() => self.fetch_and_write_sysvar::<Clock>(
602                var_addr,
603                offset,
604                length,
605                SysvarCache::get_clock,
606            ),
607            id if id == EpochRewards::id() => self.fetch_and_write_sysvar::<EpochRewards>(
608                var_addr,
609                offset,
610                length,
611                SysvarCache::get_epoch_rewards,
612            ),
613            id if id == EpochSchedule::id() => self.fetch_and_write_sysvar::<EpochSchedule>(
614                var_addr,
615                offset,
616                length,
617                SysvarCache::get_epoch_schedule,
618            ),
619            id if id == LastRestartSlot::id() => self.fetch_and_write_sysvar::<LastRestartSlot>(
620                var_addr,
621                offset,
622                length,
623                SysvarCache::get_last_restart_slot,
624            ),
625            id if id == Rent::id() => {
626                self.fetch_and_write_sysvar::<Rent>(var_addr, offset, length, SysvarCache::get_rent)
627            }
628            _ => UNSUPPORTED_SYSVAR,
629        }
630    }
631}
632
633#[allow(deprecated)]
634fn canonical_sysvar_data_len(sysvar_id: &Pubkey) -> Option<usize> {
635    match *sysvar_id {
636        sysvar::clock::ID => Some(solana_clock::SIZE),
637        sysvar::epoch_rewards::ID => Some(solana_epoch_rewards::SIZE),
638        sysvar::epoch_schedule::ID => Some(solana_epoch_schedule::SIZE),
639        sysvar::fees::ID => Some(solana_sysvar::fees::SIZE),
640        sysvar::last_restart_slot::ID => Some(solana_sysvar::last_restart_slot::SIZE),
641        sysvar::recent_blockhashes::ID => Some(solana_sysvar::recent_blockhashes::SIZE),
642        sysvar::rent::ID => Some(solana_rent::SIZE),
643        sysvar::rewards::ID => Some(solana_sysvar::rewards::SIZE),
644        sysvar::slot_hashes::ID => Some(solana_sysvar::slot_hashes::SIZE),
645        sysvar::slot_history::ID => Some(solana_sysvar::slot_history::SIZE),
646        sysvar::stake_history::ID => Some(solana_sysvar::stake_history::SIZE),
647        _ => None,
648    }
649}
650
651// Preserve the canonical account size for built-in sysvars, but never allocate less than the
652// current serialized value requires. Unknown sysvar IDs have no canonical size, so they use the
653// serialized size directly.
654fn required_sysvar_data_len(sysvar_id: &Pubkey, serialized_len: usize) -> usize {
655    canonical_sysvar_data_len(sysvar_id)
656        .unwrap_or(serialized_len)
657        .max(serialized_len)
658}
659
660fn create_sysvar_account<T: SysvarId + Serialize<Src = T>>(sysvar: &T) -> Account {
661    let serialized_len = wincode::serialized_size(sysvar).unwrap() as usize;
662    let data_len = required_sysvar_data_len(&T::id(), serialized_len);
663    let mut account = Account::new(1, data_len, &sysvar::id());
664    wincode::serialize_into(account.data.as_mut_slice(), sysvar).unwrap();
665    account
666}
667
668pub fn find_file(filename: &str) -> Option<PathBuf> {
669    for dir in default_shared_object_dirs() {
670        let candidate = dir.join(filename);
671        if candidate.exists() {
672            return Some(candidate);
673        }
674    }
675    None
676}
677
678fn default_shared_object_dirs() -> Vec<PathBuf> {
679    let mut search_path = vec![];
680    if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
681        search_path.push(PathBuf::from(bpf_out_dir));
682    } else if let Ok(bpf_out_dir) = std::env::var("SBF_OUT_DIR") {
683        search_path.push(PathBuf::from(bpf_out_dir));
684    }
685    search_path.push(PathBuf::from("tests/fixtures"));
686    if let Ok(dir) = std::env::current_dir() {
687        search_path.push(dir);
688    }
689    trace!("SBF .so search path: {search_path:?}");
690    search_path
691}
692
693pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
694    let path = path.as_ref();
695    let mut file = File::open(path)
696        .unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
697
698    let mut file_data = Vec::new();
699    file.read_to_end(&mut file_data)
700        .unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", path.display(), err));
701    file_data
702}
703
704pub struct ProgramTest {
705    accounts: Vec<(Pubkey, AccountSharedData)>,
706    genesis_accounts: Vec<(Pubkey, AccountSharedData)>,
707    builtin_programs: Vec<(Pubkey, &'static str, ProgramCacheEntry)>,
708    compute_max_units: Option<u64>,
709    prefer_bpf: bool,
710    deactivate_feature_set: HashSet<Pubkey>,
711    transaction_account_lock_limit: Option<usize>,
712}
713
714impl Default for ProgramTest {
715    /// Initialize a new ProgramTest
716    ///
717    /// If the `BPF_OUT_DIR` environment variable is defined, BPF programs will be preferred over
718    /// over a native instruction processor.  The `ProgramTest::prefer_bpf()` method may be
719    /// used to override this preference at runtime.  `cargo test-bpf` will set `BPF_OUT_DIR`
720    /// automatically.
721    ///
722    /// SBF program shared objects and account data files are searched for in
723    /// * the value of the `BPF_OUT_DIR` environment variable
724    /// * the `tests/fixtures` sub-directory
725    /// * the current working directory
726    ///
727    fn default() -> Self {
728        agave_logger::setup_with_default(
729            "solana_sbpf::vm=debug,solana_runtime::message_processor=debug,\
730             solana_runtime::system_instruction_processor=trace,solana_program_test=info",
731        );
732        let prefer_bpf =
733            std::env::var("BPF_OUT_DIR").is_ok() || std::env::var("SBF_OUT_DIR").is_ok();
734
735        Self {
736            accounts: vec![],
737            genesis_accounts: vec![],
738            builtin_programs: vec![],
739            compute_max_units: None,
740            prefer_bpf,
741            deactivate_feature_set: HashSet::default(),
742            transaction_account_lock_limit: None,
743        }
744    }
745}
746
747impl ProgramTest {
748    /// Create a `ProgramTest`.
749    ///
750    /// This is a wrapper around [`default`] and [`add_program`]. See their documentation for more
751    /// details.
752    ///
753    /// [`default`]: #method.default
754    /// [`add_program`]: #method.add_program
755    pub fn new(
756        program_name: &'static str,
757        program_id: Pubkey,
758        builtin: Option<BuiltinFunctionRegisterer>,
759    ) -> Self {
760        let mut me = Self::default();
761        me.add_program(program_name, program_id, builtin);
762        me
763    }
764
765    /// Override default SBF program selection
766    pub fn prefer_bpf(&mut self, prefer_bpf: bool) {
767        self.prefer_bpf = prefer_bpf;
768    }
769
770    /// Override the default maximum compute units
771    pub fn set_compute_max_units(&mut self, compute_max_units: u64) {
772        debug_assert!(
773            compute_max_units <= i64::MAX as u64,
774            "Compute unit limit must fit in `i64::MAX`"
775        );
776        self.compute_max_units = Some(compute_max_units);
777    }
778
779    /// Override the default transaction account lock limit
780    pub fn set_transaction_account_lock_limit(&mut self, transaction_account_lock_limit: usize) {
781        self.transaction_account_lock_limit = Some(transaction_account_lock_limit);
782    }
783
784    /// Add an account to the test environment's genesis config.
785    pub fn add_genesis_account(&mut self, address: Pubkey, account: Account) {
786        self.genesis_accounts
787            .push((address, AccountSharedData::from(account)));
788    }
789
790    /// Add an account to the test environment
791    pub fn add_account(&mut self, address: Pubkey, account: Account) {
792        self.accounts
793            .push((address, AccountSharedData::from(account)));
794    }
795
796    /// Add an account to the test environment with the account data in the provided `filename`
797    pub fn add_account_with_file_data(
798        &mut self,
799        address: Pubkey,
800        lamports: u64,
801        owner: Pubkey,
802        filename: &str,
803    ) {
804        self.add_account(
805            address,
806            Account {
807                lamports,
808                data: read_file(find_file(filename).unwrap_or_else(|| {
809                    panic!("Unable to locate {filename}");
810                })),
811                owner,
812                executable: false,
813                rent_epoch: 0,
814            },
815        );
816    }
817
818    /// Add an account to the test environment with the account data in the provided as a base 64
819    /// string
820    pub fn add_account_with_base64_data(
821        &mut self,
822        address: Pubkey,
823        lamports: u64,
824        owner: Pubkey,
825        data_base64: &str,
826    ) {
827        self.add_account(
828            address,
829            Account {
830                lamports,
831                data: BASE64_STANDARD
832                    .decode(data_base64)
833                    .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
834                owner,
835                executable: false,
836                rent_epoch: 0,
837            },
838        );
839    }
840
841    pub fn add_sysvar_account<S: SysvarId + Serialize<Src = S>>(
842        &mut self,
843        address: Pubkey,
844        sysvar: &S,
845    ) {
846        self.add_account(address, create_sysvar_account(sysvar));
847    }
848
849    /// Add a BPF Upgradeable program to the test environment's genesis config.
850    ///
851    /// When testing BPF programs using the program ID of a runtime builtin
852    /// program - such as Core BPF programs - the program accounts must be
853    /// added to the genesis config in order to make them available to the new
854    /// Bank as it's being initialized.
855    ///
856    /// The presence of these program accounts will cause Bank to skip adding
857    /// the builtin version of the program, allowing the provided BPF program
858    /// to be used at the designated program ID instead.
859    ///
860    /// See https://github.com/anza-xyz/agave/blob/c038908600b8a1b0080229dea015d7fc9939c418/runtime/src/bank.rs#L5109-L5126.
861    pub fn add_upgradeable_program_to_genesis(
862        &mut self,
863        program_name: &'static str,
864        program_id: &Pubkey,
865    ) {
866        let program_file = find_file(&format!("{program_name}.so")).unwrap_or_else(|| {
867            panic!("Program file data not available for {program_name} ({program_id})")
868        });
869        let elf = read_file(program_file);
870        let program_accounts = programs::bpf_loader_upgradeable_program_accounts(
871            program_id,
872            &elf,
873            &Pubkey::default(),
874            &Rent::default(),
875        );
876        for (address, account) in program_accounts {
877            self.add_genesis_account(address, account);
878        }
879    }
880
881    /// Add a SBF program to the test environment.
882    ///
883    /// `program_name` will also be used to locate the SBF shared object in the current or fixtures
884    /// directory.
885    ///
886    /// If `builtin_function` is provided, the natively built-program may be used instead of the
887    /// SBF shared object depending on the `BPF_OUT_DIR` environment variable.
888    pub fn add_program(
889        &mut self,
890        program_name: &'static str,
891        program_id: Pubkey,
892        builtin_function: Option<BuiltinFunctionRegisterer>,
893    ) {
894        let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| {
895            let data = read_file(&program_file);
896            info!(
897                "\"{}\" SBF program from {}{}",
898                program_name,
899                program_file.display(),
900                std::fs::metadata(&program_file)
901                    .map(|metadata| {
902                        metadata
903                            .modified()
904                            .map(|time| {
905                                format!(
906                                    ", modified {}",
907                                    HumanTime::from(time)
908                                        .to_text_en(Accuracy::Precise, Tense::Past)
909                                )
910                            })
911                            .ok()
912                    })
913                    .ok()
914                    .flatten()
915                    .unwrap_or_default()
916            );
917
918            this.add_account(
919                program_id,
920                Account {
921                    lamports: Rent::default().minimum_balance(data.len()).max(1),
922                    data,
923                    owner: solana_sdk_ids::bpf_loader::id(),
924                    executable: true,
925                    rent_epoch: 0,
926                },
927            );
928        };
929
930        let warn_invalid_program_name = || {
931            let valid_program_names = default_shared_object_dirs()
932                .iter()
933                .filter_map(|dir| dir.read_dir().ok())
934                .flat_map(|read_dir| {
935                    read_dir.filter_map(|entry| {
936                        let path = entry.ok()?.path();
937                        if !path.is_file() {
938                            return None;
939                        }
940                        match path.extension()?.to_str()? {
941                            "so" => Some(path.file_stem()?.to_os_string()),
942                            _ => None,
943                        }
944                    })
945                })
946                .collect::<Vec<_>>();
947
948            if valid_program_names.is_empty() {
949                // This should be unreachable as `test-bpf` should guarantee at least one shared
950                // object exists somewhere.
951                warn!("No SBF shared objects found.");
952                return;
953            }
954
955            warn!(
956                "Possible bogus program name. Ensure the program name ({program_name}) matches \
957                 one of the following recognizable program names:",
958            );
959            for name in valid_program_names {
960                warn!(" - {}", name.to_str().unwrap());
961            }
962        };
963
964        let program_file = find_file(&format!("{program_name}.so"));
965        match (self.prefer_bpf, program_file, builtin_function) {
966            // If SBF is preferred (i.e., `test-sbf` is invoked) and a BPF shared object exists,
967            // use that as the program data.
968            (true, Some(file), _) => add_bpf(self, file),
969
970            // If SBF is not required (i.e., we were invoked with `test`), use the provided
971            // processor function as is.
972            (false, _, Some(builtin_function)) => {
973                self.add_builtin_program(program_name, program_id, builtin_function)
974            }
975
976            // Invalid: `test-sbf` invocation with no matching SBF shared object.
977            (true, None, _) => {
978                warn_invalid_program_name();
979                panic!("Program file data not available for {program_name} ({program_id})");
980            }
981
982            // Invalid: regular `test` invocation without a processor.
983            (false, _, None) => {
984                panic!("Program processor not available for {program_name} ({program_id})");
985            }
986        }
987    }
988
989    /// Add a builtin program to the test environment.
990    ///
991    /// Note that builtin programs are responsible for their own `stable_log` output.
992    pub fn add_builtin_program(
993        &mut self,
994        program_name: &'static str,
995        program_id: Pubkey,
996        builtin: BuiltinFunctionRegisterer,
997    ) {
998        info!("\"{program_name}\" builtin program");
999        self.builtin_programs.push((
1000            program_id,
1001            program_name,
1002            ProgramCacheEntry::new_builtin(builtin),
1003        ));
1004    }
1005
1006    /// Deactivate a runtime feature.
1007    ///
1008    /// Note that all features are activated by default.
1009    pub fn deactivate_feature(&mut self, feature_id: Pubkey) {
1010        self.deactivate_feature_set.insert(feature_id);
1011    }
1012
1013    fn setup_bank(
1014        &mut self,
1015    ) -> (
1016        Arc<RwLock<BankForks>>,
1017        Arc<RwLock<BlockCommitmentCache>>,
1018        Hash,
1019        GenesisConfigInfo,
1020    ) {
1021        {
1022            use std::sync::Once;
1023            static ONCE: Once = Once::new();
1024
1025            ONCE.call_once(|| {
1026                solana_sysvar::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
1027            });
1028        }
1029
1030        let rent = Rent::default();
1031        let fee_rate_governor = FeeRateGovernor {
1032            // Initialize with a non-zero fee
1033            lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2,
1034            ..FeeRateGovernor::default()
1035        };
1036        let bootstrap_validator_pubkey = Pubkey::new_unique();
1037        let bootstrap_validator_stake_lamports =
1038            rent.minimum_balance(VoteStateV4::size_of()) + 1_000_000 * LAMPORTS_PER_SOL;
1039
1040        let mint_keypair = Keypair::new();
1041        let voting_keypair = Keypair::new();
1042
1043        // Remove features tagged to deactivate
1044        let mut feature_set = FeatureSet::all_enabled();
1045        for deactivate_feature_pk in &self.deactivate_feature_set {
1046            if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
1047                feature_set.deactivate(deactivate_feature_pk);
1048            } else {
1049                warn!(
1050                    "Feature {deactivate_feature_pk:?} set for deactivation is not a known \
1051                     Feature public key"
1052                );
1053            }
1054        }
1055
1056        let mut genesis_config = create_genesis_config_with_leader_ex(
1057            1_000_000 * LAMPORTS_PER_SOL,
1058            &mint_keypair.pubkey(),
1059            &bootstrap_validator_pubkey,
1060            &voting_keypair.pubkey(),
1061            &Pubkey::new_unique(),
1062            None,
1063            bootstrap_validator_stake_lamports,
1064            890_880,
1065            fee_rate_governor,
1066            rent.clone(),
1067            ClusterType::Development,
1068            &feature_set,
1069            std::mem::take(&mut self.genesis_accounts),
1070        );
1071
1072        let target_tick_duration = Duration::from_micros(100);
1073        genesis_config.poh_config = PohConfig::new_sleep(target_tick_duration);
1074        debug!("Payer address: {}", mint_keypair.pubkey());
1075        debug!("Genesis config: {genesis_config}");
1076
1077        let bank = Bank::new_from_genesis(
1078            &genesis_config,
1079            Arc::new(RuntimeConfig {
1080                compute_budget: self.compute_max_units.map(|max_units| ComputeBudget {
1081                    compute_unit_limit: max_units,
1082                    ..ComputeBudget::new_with_defaults(
1083                        genesis_config
1084                            .accounts
1085                            .contains_key(&raise_cpi_nesting_limit_to_8::id()),
1086                    )
1087                }),
1088                transaction_account_lock_limit: self.transaction_account_lock_limit,
1089                ..RuntimeConfig::default()
1090            }),
1091            Vec::default(),
1092            None,
1093            ACCOUNTS_DB_CONFIG_FOR_TESTING,
1094            None,
1095            None,
1096            Arc::default(),
1097            None,
1098            None,
1099        );
1100
1101        // Add commonly-used SPL programs as a convenience to the user
1102        for (program_id, account) in programs::spl_programs(&rent).iter() {
1103            bank.store_account(program_id, account);
1104        }
1105
1106        // Add migrated Core BPF programs.
1107        for (program_id, account) in programs::core_bpf_programs(&rent, |feature_id| {
1108            genesis_config.accounts.contains_key(feature_id)
1109        })
1110        .iter()
1111        {
1112            bank.store_account(program_id, account);
1113        }
1114
1115        // User-supplied additional builtins
1116        let mut builtin_programs = Vec::new();
1117        std::mem::swap(&mut self.builtin_programs, &mut builtin_programs);
1118        for (program_id, name, builtin) in builtin_programs.into_iter() {
1119            bank.add_builtin(program_id, name, builtin);
1120        }
1121
1122        for (address, account) in self.accounts.iter() {
1123            if bank.get_account(address).is_some() {
1124                info!("Overriding account at {address}");
1125            }
1126            bank.store_account(address, account);
1127        }
1128        bank.set_capitalization_for_tests(bank.calculate_capitalization_for_tests());
1129        // Advance beyond slot 0 for a slightly more realistic test environment.
1130        // Create BankForks from the genesis bank first so fork_graph is set before creating
1131        // the child bank (required for ProgramCache::extract in new_from_parent).
1132        bank.fill_bank_with_ticks_for_tests();
1133        let bank_forks = BankForks::new_rw_arc(bank);
1134        let bank0 = bank_forks.read().unwrap().root_bank();
1135        let bank1 = Bank::new_from_parent(bank0.clone(), *bank0.leader(), bank0.slot() + 1);
1136        let bank1 = {
1137            let mut bf = bank_forks.write().unwrap();
1138            bf.insert(bank1);
1139            bf.working_bank()
1140        };
1141        debug!("Bank slot: {}", bank1.slot());
1142        let slot = bank1.slot();
1143        let last_blockhash = bank1.last_blockhash();
1144        let block_commitment_cache = Arc::new(RwLock::new(
1145            BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
1146        ));
1147
1148        (
1149            bank_forks,
1150            block_commitment_cache,
1151            last_blockhash,
1152            GenesisConfigInfo {
1153                genesis_config,
1154                mint_keypair,
1155                voting_keypair,
1156                validator_pubkey: bootstrap_validator_pubkey,
1157            },
1158        )
1159    }
1160
1161    pub async fn start(mut self) -> (BanksClient, Keypair, Hash) {
1162        let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
1163        let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
1164        let target_slot_duration = target_tick_duration * gci.genesis_config.ticks_per_slot as u32;
1165        let transport = start_local_server(
1166            bank_forks.clone(),
1167            block_commitment_cache.clone(),
1168            target_tick_duration,
1169        )
1170        .await;
1171        let banks_client = start_client(transport)
1172            .await
1173            .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
1174
1175        // Run a simulated PohService to provide the client with new blockhashes.  New blockhashes
1176        // are required when sending multiple otherwise identical transactions in series from a
1177        // test
1178        tokio::spawn(async move {
1179            loop {
1180                tokio::time::sleep(target_slot_duration).await;
1181                bank_forks
1182                    .read()
1183                    .unwrap()
1184                    .working_bank()
1185                    .register_unique_recent_blockhash_for_test();
1186            }
1187        });
1188
1189        (banks_client, gci.mint_keypair, last_blockhash)
1190    }
1191
1192    /// Start the test client
1193    ///
1194    /// Returns a `BanksClient` interface into the test environment as well as a payer `Keypair`
1195    /// with SOL for sending transactions
1196    pub async fn start_with_context(mut self) -> ProgramTestContext {
1197        let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
1198        let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
1199        let transport = start_local_server(
1200            bank_forks.clone(),
1201            block_commitment_cache.clone(),
1202            target_tick_duration,
1203        )
1204        .await;
1205        let banks_client = start_client(transport)
1206            .await
1207            .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
1208
1209        ProgramTestContext::new(
1210            bank_forks,
1211            block_commitment_cache,
1212            banks_client,
1213            last_blockhash,
1214            gci,
1215        )
1216    }
1217}
1218
1219#[async_trait]
1220pub trait ProgramTestBanksClientExt {
1221    /// Get a new latest blockhash, similar in spirit to RpcClient::get_latest_blockhash()
1222    async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash>;
1223}
1224
1225#[async_trait]
1226impl ProgramTestBanksClientExt for BanksClient {
1227    async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash> {
1228        let mut num_retries = 0;
1229        let start = Instant::now();
1230        while start.elapsed().as_secs() < 5 {
1231            let new_blockhash = self.get_latest_blockhash().await?;
1232            if new_blockhash != *blockhash {
1233                return Ok(new_blockhash);
1234            }
1235            debug!("Got same blockhash ({blockhash:?}), will retry...");
1236
1237            tokio::time::sleep(Duration::from_millis(200)).await;
1238            num_retries += 1;
1239        }
1240
1241        Err(io::Error::other(format!(
1242            "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
1243            start.elapsed().as_millis(),
1244            num_retries,
1245            blockhash
1246        )))
1247    }
1248}
1249
1250struct DroppableTask<T>(Arc<AtomicBool>, JoinHandle<T>);
1251
1252impl<T> Drop for DroppableTask<T> {
1253    fn drop(&mut self) {
1254        self.0.store(true, Ordering::Relaxed);
1255        trace!(
1256            "stopping task, which is currently {}",
1257            if self.1.is_finished() {
1258                "finished"
1259            } else {
1260                "running"
1261            }
1262        );
1263    }
1264}
1265
1266pub struct ProgramTestContext {
1267    pub banks_client: BanksClient,
1268    pub last_blockhash: Hash,
1269    pub payer: Keypair,
1270    genesis_config: GenesisConfig,
1271    bank_forks: Arc<RwLock<BankForks>>,
1272    block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1273    _bank_task: DroppableTask<()>,
1274}
1275
1276impl ProgramTestContext {
1277    fn new(
1278        bank_forks: Arc<RwLock<BankForks>>,
1279        block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1280        banks_client: BanksClient,
1281        last_blockhash: Hash,
1282        genesis_config_info: GenesisConfigInfo,
1283    ) -> Self {
1284        // Run a simulated PohService to provide the client with new blockhashes.  New blockhashes
1285        // are required when sending multiple otherwise identical transactions in series from a
1286        // test
1287        let running_bank_forks = bank_forks.clone();
1288        let target_tick_duration = genesis_config_info
1289            .genesis_config
1290            .poh_config
1291            .target_tick_duration;
1292        let target_slot_duration =
1293            target_tick_duration * genesis_config_info.genesis_config.ticks_per_slot as u32;
1294        let exit = Arc::new(AtomicBool::new(false));
1295        let bank_task = DroppableTask(
1296            exit.clone(),
1297            tokio::spawn(async move {
1298                loop {
1299                    if exit.load(Ordering::Relaxed) {
1300                        break;
1301                    }
1302                    tokio::time::sleep(target_slot_duration).await;
1303                    running_bank_forks
1304                        .read()
1305                        .unwrap()
1306                        .working_bank()
1307                        .register_unique_recent_blockhash_for_test();
1308                }
1309            }),
1310        );
1311
1312        Self {
1313            banks_client,
1314            last_blockhash,
1315            payer: genesis_config_info.mint_keypair,
1316            genesis_config: genesis_config_info.genesis_config,
1317            bank_forks,
1318            block_commitment_cache,
1319            _bank_task: bank_task,
1320        }
1321    }
1322
1323    pub fn genesis_config(&self) -> &GenesisConfig {
1324        &self.genesis_config
1325    }
1326
1327    pub fn is_active(&self, feature: &Address) -> bool {
1328        self.bank_forks
1329            .read()
1330            .unwrap()
1331            .root_bank()
1332            .feature_set
1333            .is_active(feature)
1334    }
1335
1336    /// Manually increment vote credits for the current epoch in the specified vote account to simulate validator voting activity
1337    pub fn increment_vote_account_credits(
1338        &mut self,
1339        vote_account_address: &Pubkey,
1340        number_of_credits: u64,
1341    ) {
1342        let bank_forks = self.bank_forks.read().unwrap();
1343        let bank = bank_forks.working_bank();
1344
1345        // generate some vote activity for rewards
1346        let mut vote_account = bank.get_account(vote_account_address).unwrap();
1347        let mut vote_state =
1348            VoteStateV4::deserialize(vote_account.data(), vote_account_address).unwrap();
1349
1350        let epoch = bank.epoch();
1351        // Inlined from vote program - maximum number of epoch credits to keep in history
1352        const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
1353        for _ in 0..number_of_credits {
1354            // Inline increment_credits logic from vote program.
1355            let credits = 1;
1356
1357            // never seen a credit
1358            if vote_state.epoch_credits.is_empty() {
1359                vote_state.epoch_credits.push((epoch, 0, 0));
1360            } else if epoch != vote_state.epoch_credits.last().unwrap().0 {
1361                let (_, credits_val, prev_credits) = *vote_state.epoch_credits.last().unwrap();
1362
1363                if credits_val != prev_credits {
1364                    // if credits were earned previous epoch
1365                    // append entry at end of list for the new epoch
1366                    vote_state
1367                        .epoch_credits
1368                        .push((epoch, credits_val, credits_val));
1369                } else {
1370                    // else just move the current epoch
1371                    vote_state.epoch_credits.last_mut().unwrap().0 = epoch;
1372                }
1373
1374                // Remove too old epoch_credits
1375                if vote_state.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
1376                    vote_state.epoch_credits.remove(0);
1377                }
1378            }
1379
1380            vote_state.epoch_credits.last_mut().unwrap().1 = vote_state
1381                .epoch_credits
1382                .last()
1383                .unwrap()
1384                .1
1385                .saturating_add(credits);
1386        }
1387        let versioned = VoteStateVersions::new_v4(vote_state);
1388        vote_account.set_state(&versioned).unwrap();
1389        bank.store_account(vote_account_address, &vote_account);
1390    }
1391
1392    /// Create or overwrite an account, subverting normal runtime checks.
1393    ///
1394    /// This method exists to make it easier to set up artificial situations
1395    /// that would be difficult to replicate by sending individual transactions.
1396    /// Beware that it can be used to create states that would not be reachable
1397    /// by sending transactions!
1398    pub fn set_account(&mut self, address: &Pubkey, account: &AccountSharedData) {
1399        let bank_forks = self.bank_forks.read().unwrap();
1400        let bank = bank_forks.working_bank();
1401        bank.store_account(address, account);
1402    }
1403
1404    /// Create or overwrite a sysvar, subverting normal runtime checks.
1405    ///
1406    /// This method exists to make it easier to set up artificial situations
1407    /// that would be difficult to replicate on a new test cluster. Beware
1408    /// that it can be used to create states that would not be reachable
1409    /// under normal conditions!
1410    pub fn set_sysvar<T: SysvarId + Serialize<Src = T>>(&self, sysvar: &T) {
1411        let bank_forks = self.bank_forks.read().unwrap();
1412        let bank = bank_forks.working_bank();
1413        bank.set_sysvar_for_tests(sysvar);
1414    }
1415
1416    /// Force the working bank ahead to a new slot
1417    pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
1418        let bank = self.bank_forks.read().unwrap().working_bank();
1419        let leader = *bank.leader();
1420
1421        // Fill ticks until a new blockhash is recorded, otherwise retried transactions will have
1422        // the same signature
1423        bank.fill_bank_with_ticks_for_tests();
1424
1425        // Ensure that we are actually progressing forward
1426        let working_slot = bank.slot();
1427        if warp_slot <= working_slot {
1428            return Err(ProgramTestError::InvalidWarpSlot);
1429        }
1430
1431        // Warp ahead to one slot *before* the desired slot because the bank
1432        // from Bank::warp_from_parent() is frozen. If the desired slot is one
1433        // slot *after* the working_slot, no need to warp at all.
1434        let pre_warp_slot = warp_slot - 1;
1435        let warp_bank = if pre_warp_slot == working_slot {
1436            bank.freeze();
1437            bank
1438        } else {
1439            let warped = Bank::warp_from_parent(bank, leader, pre_warp_slot);
1440            self.bank_forks
1441                .write()
1442                .unwrap()
1443                .insert(warped)
1444                .clone_without_scheduler()
1445        };
1446
1447        self.bank_forks.write().unwrap().set_root(
1448            pre_warp_slot,
1449            None, // snapshots are disabled
1450            Some(pre_warp_slot),
1451        );
1452
1453        // warp_bank is frozen so go forward to get unfrozen bank at warp_slot
1454        let bank_at_warp_slot = Bank::new_from_parent(warp_bank, leader, warp_slot);
1455        self.bank_forks.write().unwrap().insert(bank_at_warp_slot);
1456
1457        // Update block commitment cache, otherwise banks server will poll at
1458        // the wrong slot
1459        let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1460        // HACK: The root set here should be `pre_warp_slot`, but since we're
1461        // in a testing environment, the root bank never updates after a warp.
1462        // The ticking thread only updates the working bank, and never the root
1463        // bank.
1464        w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1465
1466        let bank = self.bank_forks.read().unwrap().working_bank();
1467        self.last_blockhash = bank.last_blockhash();
1468        Ok(())
1469    }
1470
1471    pub fn warp_to_epoch(&mut self, warp_epoch: Epoch) -> Result<(), ProgramTestError> {
1472        let warp_slot = self
1473            .genesis_config
1474            .epoch_schedule
1475            .get_first_slot_in_epoch(warp_epoch);
1476        self.warp_to_slot(warp_slot)
1477    }
1478
1479    /// warp forward one more slot and force reward interval end
1480    pub fn warp_forward_force_reward_interval_end(&mut self) -> Result<(), ProgramTestError> {
1481        let bank = self.bank_forks.read().unwrap().working_bank();
1482        let leader = *bank.leader();
1483
1484        // Fill ticks until a new blockhash is recorded, otherwise retried transactions will have
1485        // the same signature
1486        bank.fill_bank_with_ticks_for_tests();
1487        let pre_warp_slot = bank.slot();
1488
1489        self.bank_forks.write().unwrap().set_root(
1490            pre_warp_slot,
1491            None, // snapshot_controller
1492            Some(pre_warp_slot),
1493        );
1494
1495        // warp_bank is frozen so go forward to get unfrozen bank at warp_slot
1496        let warp_slot = pre_warp_slot + 1;
1497        let mut warp_bank = Bank::new_from_parent(bank, leader, warp_slot);
1498
1499        warp_bank.force_reward_interval_end_for_tests();
1500        self.bank_forks.write().unwrap().insert(warp_bank);
1501
1502        // Update block commitment cache, otherwise banks server will poll at
1503        // the wrong slot
1504        let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1505        // HACK: The root set here should be `pre_warp_slot`, but since we're
1506        // in a testing environment, the root bank never updates after a warp.
1507        // The ticking thread only updates the working bank, and never the root
1508        // bank.
1509        w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1510
1511        let bank = self.bank_forks.read().unwrap().working_bank();
1512        self.last_blockhash = bank.last_blockhash();
1513        Ok(())
1514    }
1515
1516    /// Get a new latest blockhash, similar in spirit to RpcClient::get_latest_blockhash()
1517    pub async fn get_new_latest_blockhash(&mut self) -> io::Result<Hash> {
1518        let blockhash = self
1519            .banks_client
1520            .get_new_latest_blockhash(&self.last_blockhash)
1521            .await?;
1522        self.last_blockhash = blockhash;
1523        Ok(blockhash)
1524    }
1525
1526    /// record a hard fork slot in working bank; should be in the past
1527    pub fn register_hard_fork(&mut self, hard_fork_slot: Slot) {
1528        self.bank_forks
1529            .read()
1530            .unwrap()
1531            .working_bank()
1532            .register_hard_fork(hard_fork_slot)
1533    }
1534}