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