1#![cfg(feature = "agave-unstable-api")]
2#![allow(clippy::arithmetic_side_effects)]
4
5pub 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, create_account_shared_data_for_test,
15 state_traits::StateMut,
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::{
31 Instruction,
32 error::{InstructionError, UNSUPPORTED_SYSVAR},
33 },
34 solana_keypair::Keypair,
35 solana_native_token::LAMPORTS_PER_SOL,
36 solana_poh_config::PohConfig,
37 solana_program_binaries as programs,
38 solana_program_entrypoint::{SUCCESS, deserialize},
39 solana_program_error::{ProgramError, ProgramResult},
40 solana_program_runtime::{
41 invoke_context::BuiltinFunctionRegisterer, program_cache_entry::ProgramCacheEntry,
42 serialization::serialize_parameters, stable_log, sysvar_cache::SysvarCache,
43 },
44 solana_pubkey::Pubkey,
45 solana_rent::Rent,
46 solana_runtime::{
47 bank::Bank,
48 bank_forks::BankForks,
49 commitment::BlockCommitmentCache,
50 genesis_utils::{GenesisConfigInfo, create_genesis_config_with_leader_ex},
51 runtime_config::RuntimeConfig,
52 },
53 solana_signer::Signer,
54 solana_svm_log_collector::ic_msg,
55 solana_sysvar::{SysvarSerialize, last_restart_slot::LastRestartSlot},
56 solana_sysvar_id::SysvarId,
57 solana_vote_program::vote_state::{VoteStateV4, VoteStateVersions},
58 std::{
59 cell::RefCell,
60 collections::{HashMap, HashSet},
61 fs::File,
62 io::{self, Read},
63 mem::transmute,
64 panic::AssertUnwindSafe,
65 path::{Path, PathBuf},
66 ptr,
67 sync::{
68 Arc, RwLock,
69 atomic::{AtomicBool, Ordering},
70 },
71 time::{Duration, Instant},
72 },
73 thiserror::Error,
74 tokio::task::JoinHandle,
75};
76pub use {
78 solana_banks_client::{BanksClient, BanksClientError},
79 solana_banks_interface::BanksTransactionResultWithMetadata,
80 solana_program_runtime::invoke_context::InvokeContext,
81 solana_sbpf::{
82 error::EbpfError,
83 memory_region::MemoryMapping,
84 program::BuiltinFunctionDefinition,
85 vm::{EbpfVm, EncryptedHostAddressToEbpfVm, get_runtime_environment_key},
86 },
87 solana_transaction_context::IndexOfAccount,
88};
89
90#[derive(Error, Debug, PartialEq, Eq)]
92pub enum ProgramTestError {
93 #[error("Warp slot not in the future")]
95 InvalidWarpSlot,
96}
97
98thread_local! {
99 static INVOKE_CONTEXT: RefCell<Option<usize>> = const { RefCell::new(None) };
100}
101fn set_invoke_context(new: &mut InvokeContext) {
102 INVOKE_CONTEXT.with(|invoke_context| unsafe {
103 invoke_context.replace(Some(transmute::<&mut InvokeContext, usize>(new)))
104 });
105}
106fn get_invoke_context<'a, 'b>() -> &'a mut InvokeContext<'b, 'b> {
107 let ptr = INVOKE_CONTEXT.with(|invoke_context| match *invoke_context.borrow() {
108 Some(val) => val,
109 None => panic!("Invoke context not set!"),
110 });
111 unsafe { &mut *ptr::with_exposed_provenance_mut(ptr) }
112}
113
114pub fn invoke_builtin_function(
115 builtin_function: solana_program_entrypoint::ProcessInstruction,
116 invoke_context: &mut InvokeContext,
117) -> Result<u64, Box<dyn std::error::Error>> {
118 set_invoke_context(invoke_context);
119
120 let transaction_context = &invoke_context.transaction_context;
121 let instruction_context = transaction_context.get_current_instruction_context()?;
122 let instruction_account_indices = 0..instruction_context.get_number_of_instruction_accounts();
123
124 invoke_context.compute_meter.consume_checked(1)?;
126
127 let log_collector = invoke_context.get_log_collector();
128 let program_id = instruction_context.get_program_key()?;
129 stable_log::program_invoke(
130 &log_collector,
131 program_id,
132 invoke_context.get_stack_height(),
133 );
134
135 let deduplicated_indices: HashSet<IndexOfAccount> = instruction_account_indices.collect();
137
138 let direct_account_pointers_in_program_input = invoke_context
139 .get_feature_set()
140 .direct_account_pointers_in_program_input;
141
142 let (mut parameter_bytes, _regions, _account_lengths, _instruction_data_offset) =
144 serialize_parameters(
145 &instruction_context,
146 false, false, direct_account_pointers_in_program_input,
149 )?;
150
151 let (program_id, account_infos, input) =
153 unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) };
154
155 match std::panic::catch_unwind(AssertUnwindSafe(|| {
157 builtin_function(program_id, &account_infos, input)
158 })) {
159 Ok(program_result) => {
160 program_result.map_err(|program_error| {
161 let err = InstructionError::from(u64::from(program_error));
162 stable_log::program_failure(&log_collector, program_id, &err);
163 let err: Box<dyn std::error::Error> = Box::new(err);
164 err
165 })?;
166 }
167 Err(_panic_error) => {
168 let err = InstructionError::ProgramFailedToComplete;
169 stable_log::program_failure(&log_collector, program_id, &err);
170 let err: Box<dyn std::error::Error> = Box::new(err);
171 Err(err)?;
172 }
173 };
174
175 stable_log::program_success(&log_collector, program_id);
176
177 let account_info_map: HashMap<_, _> = account_infos.into_iter().map(|a| (a.key, a)).collect();
179
180 let transaction_context = &invoke_context.transaction_context;
183 let instruction_context = transaction_context.get_current_instruction_context()?;
184
185 for i in deduplicated_indices.into_iter() {
187 let mut borrowed_account = instruction_context.try_borrow_instruction_account(i)?;
188 if borrowed_account.is_writable()
189 && let Some(account_info) = account_info_map.get(borrowed_account.get_key())
190 {
191 if borrowed_account.get_lamports() != account_info.lamports() {
192 borrowed_account.set_lamports(account_info.lamports())?;
193 }
194
195 if borrowed_account
196 .can_data_be_resized(account_info.data_len())
197 .is_ok()
198 {
199 borrowed_account.set_data_from_slice(&account_info.data.borrow())?;
200 }
201 if borrowed_account.get_owner() != account_info.owner {
202 borrowed_account.set_owner(account_info.owner.as_ref())?;
203 }
204 }
205 }
206
207 Ok(0)
208}
209
210#[macro_export]
213macro_rules! processor {
214 ($builtin_function:expr) => {{
215 struct Converter;
216 impl $crate::BuiltinFunctionDefinition<$crate::InvokeContext<'_, '_>> for Converter {
217 type Error = Box<dyn std::error::Error>;
218 fn rust(
219 _: &mut $crate::InvokeContext<'_, '_>,
220 _: u64,
221 _: u64,
222 _: u64,
223 _: u64,
224 _: u64,
225 ) -> Result<u64, Box<dyn std::error::Error>> {
226 unreachable!()
227 }
228 fn vm(
229 mut vm: $crate::EncryptedHostAddressToEbpfVm<$crate::InvokeContext>,
230 _: u64,
231 _: u64,
232 _: u64,
233 _: u64,
234 _: u64,
235 ) {
236 unsafe {
237 vm.with_vm(|vm| {
238 vm.program_result =
239 $crate::invoke_builtin_function($builtin_function, vm.context())
240 .map_err(|err| $crate::EbpfError::SyscallError(err))
241 .into();
242 });
243 }
244 }
245 };
246 Some(<Converter as $crate::BuiltinFunctionDefinition<_>>::register)
247 }};
248}
249
250fn get_sysvar<T: Default + SysvarSerialize + Sized + serde::de::DeserializeOwned + Clone>(
251 sysvar: Result<Arc<T>, InstructionError>,
252 var_addr: *mut u8,
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 + T::size_of() 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
272pub 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
280pub 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
288pub 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
296pub 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
304pub 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
312pub 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: SysvarSerialize>(
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 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 let Ok(sysvar) = fetch(get_invoke_context().environment_config.sysvar_cache()) else {
356 return UNSUPPORTED_SYSVAR;
357 };
358
359 let Ok(expected_length) = bincode::serialized_size(&sysvar) else {
362 return UNSUPPORTED_SYSVAR;
363 };
364
365 if offset.saturating_add(length) > expected_length {
366 return UNSUPPORTED_SYSVAR;
367 }
368
369 if let Ok(serialized) = bincode::serialize(&sysvar) {
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 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 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 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 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 #[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 if account_info.data_len() != new_len {
490 account_info.resize(new_len)?;
491 }
492
493 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 )
510 }
511
512 fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 {
513 get_sysvar(
514 get_invoke_context()
515 .environment_config
516 .sysvar_cache()
517 .get_epoch_schedule(),
518 var_addr,
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 )
530 }
531
532 #[allow(deprecated)]
533 fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 {
534 get_sysvar(
535 get_invoke_context()
536 .environment_config
537 .sysvar_cache()
538 .get_fees(),
539 var_addr,
540 )
541 }
542
543 fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 {
544 get_sysvar(
545 get_invoke_context()
546 .environment_config
547 .sysvar_cache()
548 .get_rent(),
549 var_addr,
550 )
551 }
552
553 fn sol_get_last_restart_slot(&self, var_addr: *mut u8) -> u64 {
554 get_sysvar(
555 get_invoke_context()
556 .environment_config
557 .sysvar_cache()
558 .get_last_restart_slot(),
559 var_addr,
560 )
561 }
562
563 fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
564 let (program_id, data) = get_invoke_context().transaction_context.get_return_data();
565 Some((*program_id, data.to_vec()))
566 }
567
568 fn sol_set_return_data(&self, data: &[u8]) {
569 let invoke_context = get_invoke_context();
570 let transaction_context = &mut invoke_context.transaction_context;
571 let instruction_context = transaction_context
572 .get_current_instruction_context()
573 .unwrap();
574 let caller = *instruction_context.get_program_key().unwrap();
575 transaction_context
576 .set_return_data(caller, data.to_vec())
577 .unwrap();
578 }
579
580 fn sol_get_stack_height(&self) -> u64 {
581 let invoke_context = get_invoke_context();
582 invoke_context.get_stack_height().try_into().unwrap()
583 }
584
585 fn sol_get_sysvar(
586 &self,
587 sysvar_id_addr: *const u8,
588 var_addr: *mut u8,
589 offset: u64,
590 length: u64,
591 ) -> u64 {
592 let sysvar_id = unsafe { &*(sysvar_id_addr as *const Pubkey) };
593
594 match *sysvar_id {
595 id if id == Clock::id() => self.fetch_and_write_sysvar::<Clock>(
596 var_addr,
597 offset,
598 length,
599 SysvarCache::get_clock,
600 ),
601 id if id == EpochRewards::id() => self.fetch_and_write_sysvar::<EpochRewards>(
602 var_addr,
603 offset,
604 length,
605 SysvarCache::get_epoch_rewards,
606 ),
607 id if id == EpochSchedule::id() => self.fetch_and_write_sysvar::<EpochSchedule>(
608 var_addr,
609 offset,
610 length,
611 SysvarCache::get_epoch_schedule,
612 ),
613 id if id == LastRestartSlot::id() => self.fetch_and_write_sysvar::<LastRestartSlot>(
614 var_addr,
615 offset,
616 length,
617 SysvarCache::get_last_restart_slot,
618 ),
619 id if id == Rent::id() => {
620 self.fetch_and_write_sysvar::<Rent>(var_addr, offset, length, SysvarCache::get_rent)
621 }
622 _ => UNSUPPORTED_SYSVAR,
623 }
624 }
625}
626
627pub fn find_file(filename: &str) -> Option<PathBuf> {
628 for dir in default_shared_object_dirs() {
629 let candidate = dir.join(filename);
630 if candidate.exists() {
631 return Some(candidate);
632 }
633 }
634 None
635}
636
637fn default_shared_object_dirs() -> Vec<PathBuf> {
638 let mut search_path = vec![];
639 if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
640 search_path.push(PathBuf::from(bpf_out_dir));
641 } else if let Ok(bpf_out_dir) = std::env::var("SBF_OUT_DIR") {
642 search_path.push(PathBuf::from(bpf_out_dir));
643 }
644 search_path.push(PathBuf::from("tests/fixtures"));
645 if let Ok(dir) = std::env::current_dir() {
646 search_path.push(dir);
647 }
648 trace!("SBF .so search path: {search_path:?}");
649 search_path
650}
651
652pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
653 let path = path.as_ref();
654 let mut file = File::open(path)
655 .unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
656
657 let mut file_data = Vec::new();
658 file.read_to_end(&mut file_data)
659 .unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", path.display(), err));
660 file_data
661}
662
663pub struct ProgramTest {
664 accounts: Vec<(Pubkey, AccountSharedData)>,
665 genesis_accounts: Vec<(Pubkey, AccountSharedData)>,
666 builtin_programs: Vec<(Pubkey, &'static str, ProgramCacheEntry)>,
667 compute_max_units: Option<u64>,
668 prefer_bpf: bool,
669 deactivate_feature_set: HashSet<Pubkey>,
670 transaction_account_lock_limit: Option<usize>,
671}
672
673impl Default for ProgramTest {
674 fn default() -> Self {
687 agave_logger::setup_with_default(
688 "solana_sbpf::vm=debug,solana_runtime::message_processor=debug,\
689 solana_runtime::system_instruction_processor=trace,solana_program_test=info",
690 );
691 let prefer_bpf =
692 std::env::var("BPF_OUT_DIR").is_ok() || std::env::var("SBF_OUT_DIR").is_ok();
693
694 Self {
695 accounts: vec![],
696 genesis_accounts: vec![],
697 builtin_programs: vec![],
698 compute_max_units: None,
699 prefer_bpf,
700 deactivate_feature_set: HashSet::default(),
701 transaction_account_lock_limit: None,
702 }
703 }
704}
705
706impl ProgramTest {
707 pub fn new(
715 program_name: &'static str,
716 program_id: Pubkey,
717 builtin: Option<BuiltinFunctionRegisterer>,
718 ) -> Self {
719 let mut me = Self::default();
720 me.add_program(program_name, program_id, builtin);
721 me
722 }
723
724 pub fn prefer_bpf(&mut self, prefer_bpf: bool) {
726 self.prefer_bpf = prefer_bpf;
727 }
728
729 pub fn set_compute_max_units(&mut self, compute_max_units: u64) {
731 debug_assert!(
732 compute_max_units <= i64::MAX as u64,
733 "Compute unit limit must fit in `i64::MAX`"
734 );
735 self.compute_max_units = Some(compute_max_units);
736 }
737
738 pub fn set_transaction_account_lock_limit(&mut self, transaction_account_lock_limit: usize) {
740 self.transaction_account_lock_limit = Some(transaction_account_lock_limit);
741 }
742
743 pub fn add_genesis_account(&mut self, address: Pubkey, account: Account) {
745 self.genesis_accounts
746 .push((address, AccountSharedData::from(account)));
747 }
748
749 pub fn add_account(&mut self, address: Pubkey, account: Account) {
751 self.accounts
752 .push((address, AccountSharedData::from(account)));
753 }
754
755 pub fn add_account_with_file_data(
757 &mut self,
758 address: Pubkey,
759 lamports: u64,
760 owner: Pubkey,
761 filename: &str,
762 ) {
763 self.add_account(
764 address,
765 Account {
766 lamports,
767 data: read_file(find_file(filename).unwrap_or_else(|| {
768 panic!("Unable to locate {filename}");
769 })),
770 owner,
771 executable: false,
772 rent_epoch: 0,
773 },
774 );
775 }
776
777 pub fn add_account_with_base64_data(
780 &mut self,
781 address: Pubkey,
782 lamports: u64,
783 owner: Pubkey,
784 data_base64: &str,
785 ) {
786 self.add_account(
787 address,
788 Account {
789 lamports,
790 data: BASE64_STANDARD
791 .decode(data_base64)
792 .unwrap_or_else(|err| panic!("Failed to base64 decode: {err}")),
793 owner,
794 executable: false,
795 rent_epoch: 0,
796 },
797 );
798 }
799
800 pub fn add_sysvar_account<S: SysvarSerialize>(&mut self, address: Pubkey, sysvar: &S) {
801 let account = create_account_shared_data_for_test(sysvar);
802 self.add_account(address, account.into());
803 }
804
805 pub fn add_upgradeable_program_to_genesis(
818 &mut self,
819 program_name: &'static str,
820 program_id: &Pubkey,
821 ) {
822 let program_file = find_file(&format!("{program_name}.so")).unwrap_or_else(|| {
823 panic!("Program file data not available for {program_name} ({program_id})")
824 });
825 let elf = read_file(program_file);
826 let program_accounts =
827 programs::bpf_loader_upgradeable_program_accounts(program_id, &elf, &Rent::default());
828 for (address, account) in program_accounts {
829 self.add_genesis_account(address, account);
830 }
831 }
832
833 pub fn add_program(
841 &mut self,
842 program_name: &'static str,
843 program_id: Pubkey,
844 builtin_function: Option<BuiltinFunctionRegisterer>,
845 ) {
846 let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| {
847 let data = read_file(&program_file);
848 info!(
849 "\"{}\" SBF program from {}{}",
850 program_name,
851 program_file.display(),
852 std::fs::metadata(&program_file)
853 .map(|metadata| {
854 metadata
855 .modified()
856 .map(|time| {
857 format!(
858 ", modified {}",
859 HumanTime::from(time)
860 .to_text_en(Accuracy::Precise, Tense::Past)
861 )
862 })
863 .ok()
864 })
865 .ok()
866 .flatten()
867 .unwrap_or_default()
868 );
869
870 this.add_account(
871 program_id,
872 Account {
873 lamports: Rent::default().minimum_balance(data.len()).max(1),
874 data,
875 owner: solana_sdk_ids::bpf_loader::id(),
876 executable: true,
877 rent_epoch: 0,
878 },
879 );
880 };
881
882 let warn_invalid_program_name = || {
883 let valid_program_names = default_shared_object_dirs()
884 .iter()
885 .filter_map(|dir| dir.read_dir().ok())
886 .flat_map(|read_dir| {
887 read_dir.filter_map(|entry| {
888 let path = entry.ok()?.path();
889 if !path.is_file() {
890 return None;
891 }
892 match path.extension()?.to_str()? {
893 "so" => Some(path.file_stem()?.to_os_string()),
894 _ => None,
895 }
896 })
897 })
898 .collect::<Vec<_>>();
899
900 if valid_program_names.is_empty() {
901 warn!("No SBF shared objects found.");
904 return;
905 }
906
907 warn!(
908 "Possible bogus program name. Ensure the program name ({program_name}) matches \
909 one of the following recognizable program names:",
910 );
911 for name in valid_program_names {
912 warn!(" - {}", name.to_str().unwrap());
913 }
914 };
915
916 let program_file = find_file(&format!("{program_name}.so"));
917 match (self.prefer_bpf, program_file, builtin_function) {
918 (true, Some(file), _) => add_bpf(self, file),
921
922 (false, _, Some(builtin_function)) => {
925 self.add_builtin_program(program_name, program_id, builtin_function)
926 }
927
928 (true, None, _) => {
930 warn_invalid_program_name();
931 panic!("Program file data not available for {program_name} ({program_id})");
932 }
933
934 (false, _, None) => {
936 panic!("Program processor not available for {program_name} ({program_id})");
937 }
938 }
939 }
940
941 pub fn add_builtin_program(
945 &mut self,
946 program_name: &'static str,
947 program_id: Pubkey,
948 builtin: BuiltinFunctionRegisterer,
949 ) {
950 info!("\"{program_name}\" builtin program");
951 self.builtin_programs.push((
952 program_id,
953 program_name,
954 ProgramCacheEntry::new_builtin(0, program_name.len(), builtin),
955 ));
956 }
957
958 pub fn deactivate_feature(&mut self, feature_id: Pubkey) {
962 self.deactivate_feature_set.insert(feature_id);
963 }
964
965 fn setup_bank(
966 &mut self,
967 ) -> (
968 Arc<RwLock<BankForks>>,
969 Arc<RwLock<BlockCommitmentCache>>,
970 Hash,
971 GenesisConfigInfo,
972 ) {
973 {
974 use std::sync::Once;
975 static ONCE: Once = Once::new();
976
977 ONCE.call_once(|| {
978 solana_sysvar::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
979 });
980 }
981
982 let rent = Rent::default();
983 let fee_rate_governor = FeeRateGovernor {
984 lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2,
986 ..FeeRateGovernor::default()
987 };
988 let bootstrap_validator_pubkey = Pubkey::new_unique();
989 let bootstrap_validator_stake_lamports =
990 rent.minimum_balance(VoteStateV4::size_of()) + 1_000_000 * LAMPORTS_PER_SOL;
991
992 let mint_keypair = Keypair::new();
993 let voting_keypair = Keypair::new();
994
995 let mut feature_set = FeatureSet::all_enabled();
997 for deactivate_feature_pk in &self.deactivate_feature_set {
998 if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
999 feature_set.deactivate(deactivate_feature_pk);
1000 } else {
1001 warn!(
1002 "Feature {deactivate_feature_pk:?} set for deactivation is not a known \
1003 Feature public key"
1004 );
1005 }
1006 }
1007
1008 let mut genesis_config = create_genesis_config_with_leader_ex(
1009 1_000_000 * LAMPORTS_PER_SOL,
1010 &mint_keypair.pubkey(),
1011 &bootstrap_validator_pubkey,
1012 &voting_keypair.pubkey(),
1013 &Pubkey::new_unique(),
1014 None,
1015 bootstrap_validator_stake_lamports,
1016 890_880,
1017 fee_rate_governor,
1018 rent.clone(),
1019 ClusterType::Development,
1020 &feature_set,
1021 std::mem::take(&mut self.genesis_accounts),
1022 );
1023
1024 let target_tick_duration = Duration::from_micros(100);
1025 genesis_config.poh_config = PohConfig::new_sleep(target_tick_duration);
1026 debug!("Payer address: {}", mint_keypair.pubkey());
1027 debug!("Genesis config: {genesis_config}");
1028
1029 let bank = Bank::new_from_genesis(
1030 &genesis_config,
1031 Arc::new(RuntimeConfig {
1032 compute_budget: self.compute_max_units.map(|max_units| ComputeBudget {
1033 compute_unit_limit: max_units,
1034 ..ComputeBudget::new_with_defaults(
1035 genesis_config
1036 .accounts
1037 .contains_key(&raise_cpi_nesting_limit_to_8::id()),
1038 )
1039 }),
1040 transaction_account_lock_limit: self.transaction_account_lock_limit,
1041 ..RuntimeConfig::default()
1042 }),
1043 Vec::default(),
1044 None,
1045 ACCOUNTS_DB_CONFIG_FOR_TESTING,
1046 None,
1047 None,
1048 Arc::default(),
1049 None,
1050 None,
1051 );
1052
1053 for (program_id, account) in programs::spl_programs(&rent).iter() {
1055 bank.store_account(program_id, account);
1056 }
1057
1058 for (program_id, account) in programs::core_bpf_programs(&rent, |feature_id| {
1060 genesis_config.accounts.contains_key(feature_id)
1061 })
1062 .iter()
1063 {
1064 bank.store_account(program_id, account);
1065 }
1066
1067 let mut builtin_programs = Vec::new();
1069 std::mem::swap(&mut self.builtin_programs, &mut builtin_programs);
1070 for (program_id, name, builtin) in builtin_programs.into_iter() {
1071 bank.add_builtin(program_id, name, builtin);
1072 }
1073
1074 for (address, account) in self.accounts.iter() {
1075 if bank.get_account(address).is_some() {
1076 info!("Overriding account at {address}");
1077 }
1078 bank.store_account(address, account);
1079 }
1080 bank.set_capitalization_for_tests(bank.calculate_capitalization_for_tests());
1081 bank.fill_bank_with_ticks_for_tests();
1085 let bank_forks = BankForks::new_rw_arc(bank);
1086 let bank0 = bank_forks.read().unwrap().root_bank();
1087 let bank1 = Bank::new_from_parent(bank0.clone(), *bank0.leader(), bank0.slot() + 1);
1088 let bank1 = {
1089 let mut bf = bank_forks.write().unwrap();
1090 bf.insert(bank1);
1091 bf.working_bank()
1092 };
1093 debug!("Bank slot: {}", bank1.slot());
1094 let slot = bank1.slot();
1095 let last_blockhash = bank1.last_blockhash();
1096 let block_commitment_cache = Arc::new(RwLock::new(
1097 BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
1098 ));
1099
1100 (
1101 bank_forks,
1102 block_commitment_cache,
1103 last_blockhash,
1104 GenesisConfigInfo {
1105 genesis_config,
1106 mint_keypair,
1107 voting_keypair,
1108 validator_pubkey: bootstrap_validator_pubkey,
1109 },
1110 )
1111 }
1112
1113 pub async fn start(mut self) -> (BanksClient, Keypair, Hash) {
1114 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
1115 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
1116 let target_slot_duration = target_tick_duration * gci.genesis_config.ticks_per_slot as u32;
1117 let transport = start_local_server(
1118 bank_forks.clone(),
1119 block_commitment_cache.clone(),
1120 target_tick_duration,
1121 )
1122 .await;
1123 let banks_client = start_client(transport)
1124 .await
1125 .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
1126
1127 tokio::spawn(async move {
1131 loop {
1132 tokio::time::sleep(target_slot_duration).await;
1133 bank_forks
1134 .read()
1135 .unwrap()
1136 .working_bank()
1137 .register_unique_recent_blockhash_for_test();
1138 }
1139 });
1140
1141 (banks_client, gci.mint_keypair, last_blockhash)
1142 }
1143
1144 pub async fn start_with_context(mut self) -> ProgramTestContext {
1149 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
1150 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
1151 let transport = start_local_server(
1152 bank_forks.clone(),
1153 block_commitment_cache.clone(),
1154 target_tick_duration,
1155 )
1156 .await;
1157 let banks_client = start_client(transport)
1158 .await
1159 .unwrap_or_else(|err| panic!("Failed to start banks client: {err}"));
1160
1161 ProgramTestContext::new(
1162 bank_forks,
1163 block_commitment_cache,
1164 banks_client,
1165 last_blockhash,
1166 gci,
1167 )
1168 }
1169}
1170
1171#[async_trait]
1172pub trait ProgramTestBanksClientExt {
1173 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash>;
1175}
1176
1177#[async_trait]
1178impl ProgramTestBanksClientExt for BanksClient {
1179 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash> {
1180 let mut num_retries = 0;
1181 let start = Instant::now();
1182 while start.elapsed().as_secs() < 5 {
1183 let new_blockhash = self.get_latest_blockhash().await?;
1184 if new_blockhash != *blockhash {
1185 return Ok(new_blockhash);
1186 }
1187 debug!("Got same blockhash ({blockhash:?}), will retry...");
1188
1189 tokio::time::sleep(Duration::from_millis(200)).await;
1190 num_retries += 1;
1191 }
1192
1193 Err(io::Error::other(format!(
1194 "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
1195 start.elapsed().as_millis(),
1196 num_retries,
1197 blockhash
1198 )))
1199 }
1200}
1201
1202struct DroppableTask<T>(Arc<AtomicBool>, JoinHandle<T>);
1203
1204impl<T> Drop for DroppableTask<T> {
1205 fn drop(&mut self) {
1206 self.0.store(true, Ordering::Relaxed);
1207 trace!(
1208 "stopping task, which is currently {}",
1209 if self.1.is_finished() {
1210 "finished"
1211 } else {
1212 "running"
1213 }
1214 );
1215 }
1216}
1217
1218pub struct ProgramTestContext {
1219 pub banks_client: BanksClient,
1220 pub last_blockhash: Hash,
1221 pub payer: Keypair,
1222 genesis_config: GenesisConfig,
1223 bank_forks: Arc<RwLock<BankForks>>,
1224 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1225 _bank_task: DroppableTask<()>,
1226}
1227
1228impl ProgramTestContext {
1229 fn new(
1230 bank_forks: Arc<RwLock<BankForks>>,
1231 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1232 banks_client: BanksClient,
1233 last_blockhash: Hash,
1234 genesis_config_info: GenesisConfigInfo,
1235 ) -> Self {
1236 let running_bank_forks = bank_forks.clone();
1240 let target_tick_duration = genesis_config_info
1241 .genesis_config
1242 .poh_config
1243 .target_tick_duration;
1244 let target_slot_duration =
1245 target_tick_duration * genesis_config_info.genesis_config.ticks_per_slot as u32;
1246 let exit = Arc::new(AtomicBool::new(false));
1247 let bank_task = DroppableTask(
1248 exit.clone(),
1249 tokio::spawn(async move {
1250 loop {
1251 if exit.load(Ordering::Relaxed) {
1252 break;
1253 }
1254 tokio::time::sleep(target_slot_duration).await;
1255 running_bank_forks
1256 .read()
1257 .unwrap()
1258 .working_bank()
1259 .register_unique_recent_blockhash_for_test();
1260 }
1261 }),
1262 );
1263
1264 Self {
1265 banks_client,
1266 last_blockhash,
1267 payer: genesis_config_info.mint_keypair,
1268 genesis_config: genesis_config_info.genesis_config,
1269 bank_forks,
1270 block_commitment_cache,
1271 _bank_task: bank_task,
1272 }
1273 }
1274
1275 pub fn genesis_config(&self) -> &GenesisConfig {
1276 &self.genesis_config
1277 }
1278
1279 pub fn is_active(&self, feature: &Address) -> bool {
1280 self.bank_forks
1281 .read()
1282 .unwrap()
1283 .root_bank()
1284 .feature_set
1285 .is_active(feature)
1286 }
1287
1288 pub fn increment_vote_account_credits(
1290 &mut self,
1291 vote_account_address: &Pubkey,
1292 number_of_credits: u64,
1293 ) {
1294 let bank_forks = self.bank_forks.read().unwrap();
1295 let bank = bank_forks.working_bank();
1296
1297 let mut vote_account = bank.get_account(vote_account_address).unwrap();
1299 let mut vote_state =
1300 VoteStateV4::deserialize(vote_account.data(), vote_account_address).unwrap();
1301
1302 let epoch = bank.epoch();
1303 const MAX_EPOCH_CREDITS_HISTORY: usize = 64;
1305 for _ in 0..number_of_credits {
1306 let credits = 1;
1308
1309 if vote_state.epoch_credits.is_empty() {
1311 vote_state.epoch_credits.push((epoch, 0, 0));
1312 } else if epoch != vote_state.epoch_credits.last().unwrap().0 {
1313 let (_, credits_val, prev_credits) = *vote_state.epoch_credits.last().unwrap();
1314
1315 if credits_val != prev_credits {
1316 vote_state
1319 .epoch_credits
1320 .push((epoch, credits_val, credits_val));
1321 } else {
1322 vote_state.epoch_credits.last_mut().unwrap().0 = epoch;
1324 }
1325
1326 if vote_state.epoch_credits.len() > MAX_EPOCH_CREDITS_HISTORY {
1328 vote_state.epoch_credits.remove(0);
1329 }
1330 }
1331
1332 vote_state.epoch_credits.last_mut().unwrap().1 = vote_state
1333 .epoch_credits
1334 .last()
1335 .unwrap()
1336 .1
1337 .saturating_add(credits);
1338 }
1339 let versioned = VoteStateVersions::new_v4(vote_state);
1340 vote_account.set_state(&versioned).unwrap();
1341 bank.store_account(vote_account_address, &vote_account);
1342 }
1343
1344 pub fn set_account(&mut self, address: &Pubkey, account: &AccountSharedData) {
1351 let bank_forks = self.bank_forks.read().unwrap();
1352 let bank = bank_forks.working_bank();
1353 bank.store_account(address, account);
1354 }
1355
1356 pub fn set_sysvar<T: SysvarId + SysvarSerialize>(&self, sysvar: &T) {
1363 let bank_forks = self.bank_forks.read().unwrap();
1364 let bank = bank_forks.working_bank();
1365 bank.set_sysvar_for_tests(sysvar);
1366 }
1367
1368 pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
1370 let bank = self.bank_forks.read().unwrap().working_bank();
1371 let leader = *bank.leader();
1372
1373 bank.fill_bank_with_ticks_for_tests();
1376
1377 let working_slot = bank.slot();
1379 if warp_slot <= working_slot {
1380 return Err(ProgramTestError::InvalidWarpSlot);
1381 }
1382
1383 let pre_warp_slot = warp_slot - 1;
1387 let warp_bank = if pre_warp_slot == working_slot {
1388 bank.freeze();
1389 bank
1390 } else {
1391 let warped = Bank::warp_from_parent(bank, leader, pre_warp_slot);
1392 self.bank_forks
1393 .write()
1394 .unwrap()
1395 .insert(warped)
1396 .clone_without_scheduler()
1397 };
1398
1399 self.bank_forks.write().unwrap().set_root(
1400 pre_warp_slot,
1401 None, Some(pre_warp_slot),
1403 );
1404
1405 let bank_at_warp_slot = Bank::new_from_parent(warp_bank, leader, warp_slot);
1407 self.bank_forks.write().unwrap().insert(bank_at_warp_slot);
1408
1409 let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1412 w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1417
1418 let bank = self.bank_forks.read().unwrap().working_bank();
1419 self.last_blockhash = bank.last_blockhash();
1420 Ok(())
1421 }
1422
1423 pub fn warp_to_epoch(&mut self, warp_epoch: Epoch) -> Result<(), ProgramTestError> {
1424 let warp_slot = self
1425 .genesis_config
1426 .epoch_schedule
1427 .get_first_slot_in_epoch(warp_epoch);
1428 self.warp_to_slot(warp_slot)
1429 }
1430
1431 pub fn warp_forward_force_reward_interval_end(&mut self) -> Result<(), ProgramTestError> {
1433 let bank = self.bank_forks.read().unwrap().working_bank();
1434 let leader = *bank.leader();
1435
1436 bank.fill_bank_with_ticks_for_tests();
1439 let pre_warp_slot = bank.slot();
1440
1441 self.bank_forks.write().unwrap().set_root(
1442 pre_warp_slot,
1443 None, Some(pre_warp_slot),
1445 );
1446
1447 let warp_slot = pre_warp_slot + 1;
1449 let mut warp_bank = Bank::new_from_parent(bank, leader, warp_slot);
1450
1451 warp_bank.force_reward_interval_end_for_tests();
1452 self.bank_forks.write().unwrap().insert(warp_bank);
1453
1454 let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1457 w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1462
1463 let bank = self.bank_forks.read().unwrap().working_bank();
1464 self.last_blockhash = bank.last_blockhash();
1465 Ok(())
1466 }
1467
1468 pub async fn get_new_latest_blockhash(&mut self) -> io::Result<Hash> {
1470 let blockhash = self
1471 .banks_client
1472 .get_new_latest_blockhash(&self.last_blockhash)
1473 .await?;
1474 self.last_blockhash = blockhash;
1475 Ok(blockhash)
1476 }
1477
1478 pub fn register_hard_fork(&mut self, hard_fork_slot: Slot) {
1480 self.bank_forks
1481 .read()
1482 .unwrap()
1483 .working_bank()
1484 .register_hard_fork(hard_fork_slot)
1485 }
1486}