1#![allow(clippy::integer_arithmetic)]
3
4pub use tokio;
6use {
7 async_trait::async_trait,
8 chrono_humanize::{Accuracy, HumanTime, Tense},
9 log::*,
10 solana_banks_client::start_client,
11 safecoin_banks_server::banks_server::start_local_server,
12 solana_bpf_loader_program::serialization::serialize_parameters,
13 solana_program_runtime::{
14 compute_budget::ComputeBudget, ic_msg, invoke_context::ProcessInstructionWithContext,
15 stable_log, timings::ExecuteTimings,
16 },
17 solana_runtime::{
18 bank::Bank,
19 bank_forks::BankForks,
20 builtins::Builtin,
21 commitment::BlockCommitmentCache,
22 genesis_utils::{create_genesis_config_with_leader_ex, GenesisConfigInfo},
23 },
24 solana_sdk::{
25 account::{Account, AccountSharedData, ReadableAccount},
26 account_info::AccountInfo,
27 clock::Slot,
28 entrypoint::{deserialize, ProgramResult, SUCCESS},
29 feature_set::FEATURE_NAMES,
30 fee_calculator::{FeeCalculator, FeeRateGovernor, DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE},
31 genesis_config::{ClusterType, GenesisConfig},
32 hash::Hash,
33 instruction::{Instruction, InstructionError},
34 native_token::sol_to_lamports,
35 poh_config::PohConfig,
36 program_error::{ProgramError, ACCOUNT_BORROW_FAILED, UNSUPPORTED_SYSVAR},
37 pubkey::Pubkey,
38 rent::Rent,
39 signature::{Keypair, Signer},
40 sysvar::{Sysvar, SysvarId},
41 },
42 solana_vote_program::vote_state::{VoteState, VoteStateVersions},
43 std::{
44 cell::RefCell,
45 collections::{HashMap, HashSet},
46 convert::TryFrom,
47 fs::File,
48 io::{self, Read},
49 mem::transmute,
50 path::{Path, PathBuf},
51 sync::{
52 atomic::{AtomicBool, Ordering},
53 Arc, RwLock,
54 },
55 time::{Duration, Instant},
56 },
57 thiserror::Error,
58 tokio::task::JoinHandle,
59};
60pub use {
62 solana_banks_client::{BanksClient, BanksClientError},
63 solana_program_runtime::invoke_context::InvokeContext,
64};
65
66pub mod programs;
67
68#[macro_use]
69extern crate solana_bpf_loader_program;
70
71#[derive(Error, Debug, PartialEq, Eq)]
73pub enum ProgramTestError {
74 #[error("Warp slot not in the future")]
76 InvalidWarpSlot,
77}
78
79thread_local! {
80 static INVOKE_CONTEXT: RefCell<Option<usize>> = RefCell::new(None);
81}
82fn set_invoke_context(new: &mut InvokeContext) {
83 INVOKE_CONTEXT
84 .with(|invoke_context| unsafe { invoke_context.replace(Some(transmute::<_, usize>(new))) });
85}
86fn get_invoke_context<'a, 'b>() -> &'a mut InvokeContext<'b> {
87 let ptr = INVOKE_CONTEXT.with(|invoke_context| match *invoke_context.borrow() {
88 Some(val) => val,
89 None => panic!("Invoke context not set!"),
90 });
91 unsafe { transmute::<usize, &mut InvokeContext>(ptr) }
92}
93
94pub fn builtin_process_instruction(
95 process_instruction: solana_sdk::entrypoint::ProcessInstruction,
96 _first_instruction_account: usize,
97 invoke_context: &mut InvokeContext,
98) -> Result<(), InstructionError> {
99 set_invoke_context(invoke_context);
100
101 let transaction_context = &invoke_context.transaction_context;
102 let instruction_context = transaction_context.get_current_instruction_context()?;
103 let instruction_data = instruction_context.get_instruction_data();
104 let instruction_account_indices = 0..instruction_context.get_number_of_instruction_accounts();
105
106 let log_collector = invoke_context.get_log_collector();
107 let program_id = instruction_context.get_last_program_key(transaction_context)?;
108 stable_log::program_invoke(
109 &log_collector,
110 program_id,
111 invoke_context.get_stack_height(),
112 );
113
114 let deduplicated_indices: HashSet<usize> = instruction_account_indices.collect();
116
117 let (mut parameter_bytes, _account_lengths) = serialize_parameters(
119 invoke_context.transaction_context,
120 invoke_context
121 .transaction_context
122 .get_current_instruction_context()?,
123 true,
124 )?;
125
126 let (program_id, account_infos, _input) =
128 unsafe { deserialize(&mut parameter_bytes.as_slice_mut()[0] as *mut u8) };
129
130 process_instruction(program_id, &account_infos, instruction_data).map_err(|err| {
132 let err = u64::from(err);
133 stable_log::program_failure(&log_collector, program_id, &err.into());
134 err
135 })?;
136 stable_log::program_success(&log_collector, program_id);
137
138 let account_info_map: HashMap<_, _> = account_infos.into_iter().map(|a| (a.key, a)).collect();
140
141 let transaction_context = &invoke_context.transaction_context;
144 let instruction_context = transaction_context.get_current_instruction_context()?;
145
146 for i in deduplicated_indices.into_iter() {
148 let mut borrowed_account =
149 instruction_context.try_borrow_instruction_account(transaction_context, i)?;
150 if borrowed_account.is_writable() {
151 if let Some(account_info) = account_info_map.get(borrowed_account.get_key()) {
152 if borrowed_account.get_lamports() != account_info.lamports() {
153 borrowed_account.set_lamports(account_info.lamports())?;
154 }
155
156 if borrowed_account
157 .can_data_be_resized(account_info.data_len())
158 .is_ok()
159 && borrowed_account.can_data_be_changed().is_ok()
160 {
161 borrowed_account.set_data(&account_info.data.borrow())?;
162 }
163 if borrowed_account.get_owner() != account_info.owner {
164 borrowed_account.set_owner(account_info.owner.as_ref())?;
165 }
166 }
167 }
168 }
169
170 Ok(())
171}
172
173#[macro_export]
176macro_rules! processor {
177 ($process_instruction:expr) => {
178 Some(
179 |first_instruction_account: usize,
180 invoke_context: &mut solana_program_test::InvokeContext| {
181 $crate::builtin_process_instruction(
182 $process_instruction,
183 first_instruction_account,
184 invoke_context,
185 )
186 },
187 )
188 };
189}
190
191fn get_sysvar<T: Default + Sysvar + Sized + serde::de::DeserializeOwned + Clone>(
192 sysvar: Result<Arc<T>, InstructionError>,
193 var_addr: *mut u8,
194) -> u64 {
195 let invoke_context = get_invoke_context();
196 if invoke_context
197 .get_compute_meter()
198 .try_borrow_mut()
199 .map_err(|_| ACCOUNT_BORROW_FAILED)
200 .unwrap()
201 .consume(invoke_context.get_compute_budget().sysvar_base_cost + T::size_of() as u64)
202 .is_err()
203 {
204 panic!("Exceeded compute budget");
205 }
206
207 match sysvar {
208 Ok(sysvar_data) => unsafe {
209 *(var_addr as *mut _ as *mut T) = T::clone(&sysvar_data);
210 SUCCESS
211 },
212 Err(_) => UNSUPPORTED_SYSVAR,
213 }
214}
215
216struct SyscallStubs {}
217impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs {
218 fn sol_log(&self, message: &str) {
219 let invoke_context = get_invoke_context();
220 ic_msg!(invoke_context, "Program log: {}", message);
221 }
222
223 fn sol_invoke_signed(
224 &self,
225 instruction: &Instruction,
226 account_infos: &[AccountInfo],
227 signers_seeds: &[&[&[u8]]],
228 ) -> ProgramResult {
229 let invoke_context = get_invoke_context();
230 let log_collector = invoke_context.get_log_collector();
231 let transaction_context = &invoke_context.transaction_context;
232 let instruction_context = transaction_context
233 .get_current_instruction_context()
234 .unwrap();
235 let caller = instruction_context
236 .get_last_program_key(transaction_context)
237 .unwrap();
238
239 stable_log::program_invoke(
240 &log_collector,
241 &instruction.program_id,
242 invoke_context.get_stack_height(),
243 );
244
245 let signers = signers_seeds
246 .iter()
247 .map(|seeds| Pubkey::create_program_address(seeds, caller).unwrap())
248 .collect::<Vec<_>>();
249
250 let (instruction_accounts, program_indices) = invoke_context
251 .prepare_instruction(instruction, &signers)
252 .unwrap();
253
254 let transaction_context = &invoke_context.transaction_context;
256 let instruction_context = transaction_context
257 .get_current_instruction_context()
258 .unwrap();
259 let mut account_indices = Vec::with_capacity(instruction_accounts.len());
260 for instruction_account in instruction_accounts.iter() {
261 let account_key = transaction_context
262 .get_key_of_account_at_index(instruction_account.index_in_transaction)
263 .unwrap();
264 let account_info_index = account_infos
265 .iter()
266 .position(|account_info| account_info.unsigned_key() == account_key)
267 .ok_or(InstructionError::MissingAccount)
268 .unwrap();
269 let account_info = &account_infos[account_info_index];
270 let mut borrowed_account = instruction_context
271 .try_borrow_instruction_account(
272 transaction_context,
273 instruction_account.index_in_caller,
274 )
275 .unwrap();
276 if borrowed_account.get_lamports() != account_info.lamports() {
277 borrowed_account
278 .set_lamports(account_info.lamports())
279 .unwrap();
280 }
281 let account_info_data = account_info.try_borrow_data().unwrap();
282 match borrowed_account
284 .can_data_be_resized(account_info_data.len())
285 .and_then(|_| borrowed_account.can_data_be_changed())
286 {
287 Ok(()) => borrowed_account.set_data(&account_info_data).unwrap(),
288 Err(err) if borrowed_account.get_data() != *account_info_data => {
289 panic!("{:?}", err);
290 }
291 _ => {}
292 }
293 if borrowed_account.is_executable() != account_info.executable {
294 borrowed_account
295 .set_executable(account_info.executable)
296 .unwrap();
297 }
298 if borrowed_account.get_owner() != account_info.owner {
300 borrowed_account
301 .set_owner(account_info.owner.as_ref())
302 .unwrap();
303 }
304 drop(borrowed_account);
305 let account = transaction_context
306 .get_account_at_index(instruction_account.index_in_transaction)
307 .unwrap()
308 .borrow();
309 assert_eq!(account.rent_epoch(), account_info.rent_epoch);
310 if instruction_account.is_writable {
311 account_indices.push((instruction_account.index_in_caller, account_info_index));
312 }
313 }
314
315 let mut compute_units_consumed = 0;
316 invoke_context
317 .process_instruction(
318 &instruction.data,
319 &instruction_accounts,
320 &program_indices,
321 &mut compute_units_consumed,
322 &mut ExecuteTimings::default(),
323 )
324 .map_err(|err| ProgramError::try_from(err).unwrap_or_else(|err| panic!("{}", err)))?;
325
326 let transaction_context = &invoke_context.transaction_context;
328 let instruction_context = transaction_context
329 .get_current_instruction_context()
330 .unwrap();
331 for (index_in_caller, account_info_index) in account_indices.into_iter() {
332 let borrowed_account = instruction_context
333 .try_borrow_instruction_account(transaction_context, index_in_caller)
334 .unwrap();
335 let account_info = &account_infos[account_info_index];
336 **account_info.try_borrow_mut_lamports().unwrap() = borrowed_account.get_lamports();
337 if account_info.owner != borrowed_account.get_owner() {
338 #[allow(clippy::transmute_ptr_to_ptr)]
340 #[allow(mutable_transmutes)]
341 let account_info_mut =
342 unsafe { transmute::<&Pubkey, &mut Pubkey>(account_info.owner) };
343 *account_info_mut = *borrowed_account.get_owner();
344 }
345
346 let new_data = borrowed_account.get_data();
347 let new_len = new_data.len();
348
349 if account_info.data_len() < new_len {
351 account_info.realloc(new_len, false)?;
352 }
353
354 let mut data = account_info.try_borrow_mut_data()?;
356 data.clone_from_slice(new_data);
357 }
358
359 stable_log::program_success(&log_collector, &instruction.program_id);
360 Ok(())
361 }
362
363 fn sol_get_clock_sysvar(&self, var_addr: *mut u8) -> u64 {
364 get_sysvar(
365 get_invoke_context().get_sysvar_cache().get_clock(),
366 var_addr,
367 )
368 }
369
370 fn sol_get_epoch_schedule_sysvar(&self, var_addr: *mut u8) -> u64 {
371 get_sysvar(
372 get_invoke_context().get_sysvar_cache().get_epoch_schedule(),
373 var_addr,
374 )
375 }
376
377 #[allow(deprecated)]
378 fn sol_get_fees_sysvar(&self, var_addr: *mut u8) -> u64 {
379 get_sysvar(get_invoke_context().get_sysvar_cache().get_fees(), var_addr)
380 }
381
382 fn sol_get_rent_sysvar(&self, var_addr: *mut u8) -> u64 {
383 get_sysvar(get_invoke_context().get_sysvar_cache().get_rent(), var_addr)
384 }
385
386 fn sol_get_return_data(&self) -> Option<(Pubkey, Vec<u8>)> {
387 let (program_id, data) = get_invoke_context().transaction_context.get_return_data();
388 Some((*program_id, data.to_vec()))
389 }
390
391 fn sol_set_return_data(&self, data: &[u8]) {
392 let invoke_context = get_invoke_context();
393 let transaction_context = &mut invoke_context.transaction_context;
394 let instruction_context = transaction_context
395 .get_current_instruction_context()
396 .unwrap();
397 let caller = *instruction_context
398 .get_last_program_key(transaction_context)
399 .unwrap();
400 transaction_context
401 .set_return_data(caller, data.to_vec())
402 .unwrap();
403 }
404}
405
406pub fn find_file(filename: &str) -> Option<PathBuf> {
407 for dir in default_shared_object_dirs() {
408 let candidate = dir.join(filename);
409 if candidate.exists() {
410 return Some(candidate);
411 }
412 }
413 None
414}
415
416fn default_shared_object_dirs() -> Vec<PathBuf> {
417 let mut search_path = vec![];
418 if let Ok(bpf_out_dir) = std::env::var("BPF_OUT_DIR") {
419 search_path.push(PathBuf::from(bpf_out_dir));
420 } else if let Ok(bpf_out_dir) = std::env::var("SBF_OUT_DIR") {
421 search_path.push(PathBuf::from(bpf_out_dir));
422 }
423 search_path.push(PathBuf::from("tests/fixtures"));
424 if let Ok(dir) = std::env::current_dir() {
425 search_path.push(dir);
426 }
427 trace!("BPF .so search path: {:?}", search_path);
428 search_path
429}
430
431pub fn read_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
432 let path = path.as_ref();
433 let mut file = File::open(path)
434 .unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", path.display(), err));
435
436 let mut file_data = Vec::new();
437 file.read_to_end(&mut file_data)
438 .unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", path.display(), err));
439 file_data
440}
441
442pub struct ProgramTest {
443 accounts: Vec<(Pubkey, AccountSharedData)>,
444 builtins: Vec<Builtin>,
445 compute_max_units: Option<u64>,
446 prefer_bpf: bool,
447 use_bpf_jit: bool,
448 deactivate_feature_set: HashSet<Pubkey>,
449}
450
451impl Default for ProgramTest {
452 fn default() -> Self {
465 solana_logger::setup_with_default(
466 "solana_rbpf::vm=debug,\
467 solana_runtime::message_processor=debug,\
468 solana_runtime::system_instruction_processor=trace,\
469 solana_program_test=info",
470 );
471 let prefer_bpf =
472 std::env::var("BPF_OUT_DIR").is_ok() || std::env::var("SBF_OUT_DIR").is_ok();
473
474 Self {
475 accounts: vec![],
476 builtins: vec![],
477 compute_max_units: None,
478 prefer_bpf,
479 use_bpf_jit: false,
480 deactivate_feature_set: HashSet::default(),
481 }
482 }
483}
484
485impl ProgramTest {
486 pub fn new(
494 program_name: &str,
495 program_id: Pubkey,
496 process_instruction: Option<ProcessInstructionWithContext>,
497 ) -> Self {
498 let mut me = Self::default();
499 me.add_program(program_name, program_id, process_instruction);
500 me
501 }
502
503 pub fn prefer_bpf(&mut self, prefer_bpf: bool) {
505 self.prefer_bpf = prefer_bpf;
506 }
507
508 pub fn set_compute_max_units(&mut self, compute_max_units: u64) {
510 self.compute_max_units = Some(compute_max_units);
511 }
512
513 #[allow(deprecated)]
515 #[deprecated(since = "1.8.0", note = "please use `set_compute_max_units` instead")]
516 pub fn set_bpf_compute_max_units(&mut self, bpf_compute_max_units: u64) {
517 self.compute_max_units = Some(bpf_compute_max_units);
518 }
519
520 pub fn use_bpf_jit(&mut self, use_bpf_jit: bool) {
522 self.use_bpf_jit = use_bpf_jit;
523 }
524
525 pub fn add_account(&mut self, address: Pubkey, account: Account) {
527 self.accounts
528 .push((address, AccountSharedData::from(account)));
529 }
530
531 pub fn add_account_with_file_data(
533 &mut self,
534 address: Pubkey,
535 lamports: u64,
536 owner: Pubkey,
537 filename: &str,
538 ) {
539 self.add_account(
540 address,
541 Account {
542 lamports,
543 data: read_file(find_file(filename).unwrap_or_else(|| {
544 panic!("Unable to locate {}", filename);
545 })),
546 owner,
547 executable: false,
548 rent_epoch: 0,
549 },
550 );
551 }
552
553 pub fn add_account_with_base64_data(
556 &mut self,
557 address: Pubkey,
558 lamports: u64,
559 owner: Pubkey,
560 data_base64: &str,
561 ) {
562 self.add_account(
563 address,
564 Account {
565 lamports,
566 data: base64::decode(data_base64)
567 .unwrap_or_else(|err| panic!("Failed to base64 decode: {}", err)),
568 owner,
569 executable: false,
570 rent_epoch: 0,
571 },
572 );
573 }
574
575 pub fn add_program(
583 &mut self,
584 program_name: &str,
585 program_id: Pubkey,
586 process_instruction: Option<ProcessInstructionWithContext>,
587 ) {
588 let add_bpf = |this: &mut ProgramTest, program_file: PathBuf| {
589 let data = read_file(&program_file);
590 info!(
591 "\"{}\" BPF program from {}{}",
592 program_name,
593 program_file.display(),
594 std::fs::metadata(&program_file)
595 .map(|metadata| {
596 metadata
597 .modified()
598 .map(|time| {
599 format!(
600 ", modified {}",
601 HumanTime::from(time)
602 .to_text_en(Accuracy::Precise, Tense::Past)
603 )
604 })
605 .ok()
606 })
607 .ok()
608 .flatten()
609 .unwrap_or_default()
610 );
611
612 this.add_account(
613 program_id,
614 Account {
615 lamports: Rent::default().minimum_balance(data.len()).min(1),
616 data,
617 owner: solana_sdk::bpf_loader::id(),
618 executable: true,
619 rent_epoch: 0,
620 },
621 );
622 };
623
624 let add_native = |this: &mut ProgramTest, process_fn: ProcessInstructionWithContext| {
625 info!("\"{}\" program loaded as native code", program_name);
626 this.builtins
627 .push(Builtin::new(program_name, program_id, process_fn));
628 };
629
630 let warn_invalid_program_name = || {
631 let valid_program_names = default_shared_object_dirs()
632 .iter()
633 .filter_map(|dir| dir.read_dir().ok())
634 .flat_map(|read_dir| {
635 read_dir.filter_map(|entry| {
636 let path = entry.ok()?.path();
637 if !path.is_file() {
638 return None;
639 }
640 match path.extension()?.to_str()? {
641 "so" => Some(path.file_stem()?.to_os_string()),
642 _ => None,
643 }
644 })
645 })
646 .collect::<Vec<_>>();
647
648 if valid_program_names.is_empty() {
649 warn!("No BPF shared objects found.");
652 return;
653 }
654
655 warn!(
656 "Possible bogus program name. Ensure the program name ({}) \
657 matches one of the following recognizable program names:",
658 program_name,
659 );
660 for name in valid_program_names {
661 warn!(" - {}", name.to_str().unwrap());
662 }
663 };
664
665 let program_file = find_file(&format!("{}.so", program_name));
666 match (self.prefer_bpf, program_file, process_instruction) {
667 (true, Some(file), _) => add_bpf(self, file),
670
671 (false, _, Some(process)) => add_native(self, process),
676
677 (true, None, _) => {
679 warn_invalid_program_name();
680 panic!(
681 "Program file data not available for {} ({})",
682 program_name, program_id
683 );
684 }
685
686 (false, _, None) => {
688 panic!(
689 "Program processor not available for {} ({})",
690 program_name, program_id
691 );
692 }
693 }
694 }
695
696 pub fn add_builtin_program(
700 &mut self,
701 program_name: &str,
702 program_id: Pubkey,
703 process_instruction: ProcessInstructionWithContext,
704 ) {
705 info!("\"{}\" builtin program", program_name);
706 self.builtins
707 .push(Builtin::new(program_name, program_id, process_instruction));
708 }
709
710 pub fn deactivate_feature(&mut self, feature_id: Pubkey) {
714 self.deactivate_feature_set.insert(feature_id);
715 }
716
717 fn setup_bank(
718 &self,
719 ) -> (
720 Arc<RwLock<BankForks>>,
721 Arc<RwLock<BlockCommitmentCache>>,
722 Hash,
723 GenesisConfigInfo,
724 ) {
725 {
726 use std::sync::Once;
727 static ONCE: Once = Once::new();
728
729 ONCE.call_once(|| {
730 solana_sdk::program_stubs::set_syscall_stubs(Box::new(SyscallStubs {}));
731 });
732 }
733
734 let rent = Rent::default();
735 let fee_rate_governor = FeeRateGovernor {
736 lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE / 2,
738 ..FeeRateGovernor::default()
739 };
740 let bootstrap_validator_pubkey = Pubkey::new_unique();
741 let bootstrap_validator_stake_lamports =
742 rent.minimum_balance(VoteState::size_of()) + sol_to_lamports(1_000_000.0);
743
744 let mint_keypair = Keypair::new();
745 let voting_keypair = Keypair::new();
746
747 let mut genesis_config = create_genesis_config_with_leader_ex(
748 sol_to_lamports(1_000_000.0),
749 &mint_keypair.pubkey(),
750 &bootstrap_validator_pubkey,
751 &voting_keypair.pubkey(),
752 &Pubkey::new_unique(),
753 bootstrap_validator_stake_lamports,
754 42,
755 fee_rate_governor,
756 rent,
757 ClusterType::Development,
758 vec![],
759 );
760
761 for deactivate_feature_pk in &self.deactivate_feature_set {
763 if FEATURE_NAMES.contains_key(deactivate_feature_pk) {
764 match genesis_config.accounts.remove(deactivate_feature_pk) {
765 Some(_) => debug!("Feature for {:?} deactivated", deactivate_feature_pk),
766 None => warn!(
767 "Feature {:?} set for deactivation not found in genesis_config account list, ignored.",
768 deactivate_feature_pk
769 ),
770 }
771 } else {
772 warn!(
773 "Feature {:?} set for deactivation is not a known Feature public key",
774 deactivate_feature_pk
775 );
776 }
777 }
778
779 let target_tick_duration = Duration::from_micros(100);
780 genesis_config.poh_config = PohConfig::new_sleep(target_tick_duration);
781 debug!("Payer address: {}", mint_keypair.pubkey());
782 debug!("Genesis config: {}", genesis_config);
783
784 let mut bank = Bank::new_for_tests(&genesis_config);
785
786 macro_rules! add_builtin {
788 ($b:expr) => {
789 bank.add_builtin(&$b.0, &$b.1, $b.2)
790 };
791 }
792 add_builtin!(solana_bpf_loader_deprecated_program!());
793 if self.use_bpf_jit {
794 add_builtin!(solana_bpf_loader_program_with_jit!());
795 add_builtin!(solana_bpf_loader_upgradeable_program_with_jit!());
796 } else {
797 add_builtin!(solana_bpf_loader_program!());
798 add_builtin!(solana_bpf_loader_upgradeable_program!());
799 }
800
801 for (program_id, account) in programs::spl_programs(&Rent::default()).iter() {
803 bank.store_account(program_id, account);
804 }
805
806 for builtin in self.builtins.iter() {
808 bank.add_builtin(
809 &builtin.name,
810 &builtin.id,
811 builtin.process_instruction_with_context,
812 );
813 }
814
815 for (address, account) in self.accounts.iter() {
816 if bank.get_account(address).is_some() {
817 info!("Overriding account at {}", address);
818 }
819 bank.store_account(address, account);
820 }
821 bank.set_capitalization();
822 if let Some(max_units) = self.compute_max_units {
823 bank.set_compute_budget(Some(ComputeBudget {
824 compute_unit_limit: max_units,
825 ..ComputeBudget::default()
826 }));
827 }
828 let bank = {
830 let bank = Arc::new(bank);
831 bank.fill_bank_with_ticks_for_tests();
832 let bank = Bank::new_from_parent(&bank, bank.collector_id(), bank.slot() + 1);
833 debug!("Bank slot: {}", bank.slot());
834 bank
835 };
836 let slot = bank.slot();
837 let last_blockhash = bank.last_blockhash();
838 let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
839 let block_commitment_cache = Arc::new(RwLock::new(
840 BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
841 ));
842
843 (
844 bank_forks,
845 block_commitment_cache,
846 last_blockhash,
847 GenesisConfigInfo {
848 genesis_config,
849 mint_keypair,
850 voting_keypair,
851 validator_pubkey: bootstrap_validator_pubkey,
852 },
853 )
854 }
855
856 pub async fn start(self) -> (BanksClient, Keypair, Hash) {
857 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
858 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
859 let target_slot_duration = target_tick_duration * gci.genesis_config.ticks_per_slot as u32;
860 let transport = start_local_server(
861 bank_forks.clone(),
862 block_commitment_cache.clone(),
863 target_tick_duration,
864 )
865 .await;
866 let banks_client = start_client(transport)
867 .await
868 .unwrap_or_else(|err| panic!("Failed to start banks client: {}", err));
869
870 tokio::spawn(async move {
874 loop {
875 tokio::time::sleep(target_slot_duration).await;
876 bank_forks
877 .read()
878 .unwrap()
879 .working_bank()
880 .register_recent_blockhash(&Hash::new_unique());
881 }
882 });
883
884 (banks_client, gci.mint_keypair, last_blockhash)
885 }
886
887 pub async fn start_with_context(self) -> ProgramTestContext {
892 let (bank_forks, block_commitment_cache, last_blockhash, gci) = self.setup_bank();
893 let target_tick_duration = gci.genesis_config.poh_config.target_tick_duration;
894 let transport = start_local_server(
895 bank_forks.clone(),
896 block_commitment_cache.clone(),
897 target_tick_duration,
898 )
899 .await;
900 let banks_client = start_client(transport)
901 .await
902 .unwrap_or_else(|err| panic!("Failed to start banks client: {}", err));
903
904 ProgramTestContext::new(
905 bank_forks,
906 block_commitment_cache,
907 banks_client,
908 last_blockhash,
909 gci,
910 )
911 }
912}
913
914#[async_trait]
915pub trait ProgramTestBanksClientExt {
916 #[deprecated(
920 since = "1.9.0",
921 note = "Please use `get_new_latest_blockhash `instead"
922 )]
923 async fn get_new_blockhash(&mut self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)>;
924 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash>;
926}
927
928#[async_trait]
929impl ProgramTestBanksClientExt for BanksClient {
930 async fn get_new_blockhash(&mut self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> {
931 let mut num_retries = 0;
932 let start = Instant::now();
933 while start.elapsed().as_secs() < 5 {
934 #[allow(deprecated)]
935 if let Ok((fee_calculator, new_blockhash, _slot)) = self.get_fees().await {
936 if new_blockhash != *blockhash {
937 return Ok((new_blockhash, fee_calculator));
938 }
939 }
940 debug!("Got same blockhash ({:?}), will retry...", blockhash);
941
942 tokio::time::sleep(Duration::from_millis(200)).await;
943 num_retries += 1;
944 }
945
946 Err(io::Error::new(
947 io::ErrorKind::Other,
948 format!(
949 "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
950 start.elapsed().as_millis(),
951 num_retries,
952 blockhash
953 ),
954 ))
955 }
956
957 async fn get_new_latest_blockhash(&mut self, blockhash: &Hash) -> io::Result<Hash> {
958 let mut num_retries = 0;
959 let start = Instant::now();
960 while start.elapsed().as_secs() < 5 {
961 let new_blockhash = self.get_latest_blockhash().await?;
962 if new_blockhash != *blockhash {
963 return Ok(new_blockhash);
964 }
965 debug!("Got same blockhash ({:?}), will retry...", blockhash);
966
967 tokio::time::sleep(Duration::from_millis(200)).await;
968 num_retries += 1;
969 }
970
971 Err(io::Error::new(
972 io::ErrorKind::Other,
973 format!(
974 "Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
975 start.elapsed().as_millis(),
976 num_retries,
977 blockhash
978 ),
979 ))
980 }
981}
982
983struct DroppableTask<T>(Arc<AtomicBool>, JoinHandle<T>);
984
985impl<T> Drop for DroppableTask<T> {
986 fn drop(&mut self) {
987 self.0.store(true, Ordering::Relaxed);
988 }
989}
990
991pub struct ProgramTestContext {
992 pub banks_client: BanksClient,
993 pub last_blockhash: Hash,
994 pub payer: Keypair,
995 genesis_config: GenesisConfig,
996 bank_forks: Arc<RwLock<BankForks>>,
997 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
998 _bank_task: DroppableTask<()>,
999}
1000
1001impl ProgramTestContext {
1002 fn new(
1003 bank_forks: Arc<RwLock<BankForks>>,
1004 block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
1005 banks_client: BanksClient,
1006 last_blockhash: Hash,
1007 genesis_config_info: GenesisConfigInfo,
1008 ) -> Self {
1009 let running_bank_forks = bank_forks.clone();
1013 let target_tick_duration = genesis_config_info
1014 .genesis_config
1015 .poh_config
1016 .target_tick_duration;
1017 let target_slot_duration =
1018 target_tick_duration * genesis_config_info.genesis_config.ticks_per_slot as u32;
1019 let exit = Arc::new(AtomicBool::new(false));
1020 let bank_task = DroppableTask(
1021 exit.clone(),
1022 tokio::spawn(async move {
1023 loop {
1024 if exit.load(Ordering::Relaxed) {
1025 break;
1026 }
1027 tokio::time::sleep(target_slot_duration).await;
1028 running_bank_forks
1029 .read()
1030 .unwrap()
1031 .working_bank()
1032 .register_recent_blockhash(&Hash::new_unique());
1033 }
1034 }),
1035 );
1036
1037 Self {
1038 banks_client,
1039 last_blockhash,
1040 payer: genesis_config_info.mint_keypair,
1041 genesis_config: genesis_config_info.genesis_config,
1042 bank_forks,
1043 block_commitment_cache,
1044 _bank_task: bank_task,
1045 }
1046 }
1047
1048 pub fn genesis_config(&self) -> &GenesisConfig {
1049 &self.genesis_config
1050 }
1051
1052 pub fn increment_vote_account_credits(
1054 &mut self,
1055 vote_account_address: &Pubkey,
1056 number_of_credits: u64,
1057 ) {
1058 let bank_forks = self.bank_forks.read().unwrap();
1059 let bank = bank_forks.working_bank();
1060
1061 let mut vote_account = bank.get_account(vote_account_address).unwrap();
1063 let mut vote_state = VoteState::from(&vote_account).unwrap();
1064
1065 let epoch = bank.epoch();
1066 for _ in 0..number_of_credits {
1067 vote_state.increment_credits(epoch, 1);
1068 }
1069 let versioned = VoteStateVersions::new_current(vote_state);
1070 VoteState::to(&versioned, &mut vote_account).unwrap();
1071 bank.store_account(vote_account_address, &vote_account);
1072 }
1073
1074 pub fn set_account(&mut self, address: &Pubkey, account: &AccountSharedData) {
1081 let bank_forks = self.bank_forks.read().unwrap();
1082 let bank = bank_forks.working_bank();
1083 bank.store_account(address, account);
1084 }
1085
1086 pub fn set_sysvar<T: SysvarId + Sysvar>(&self, sysvar: &T) {
1093 let bank_forks = self.bank_forks.read().unwrap();
1094 let bank = bank_forks.working_bank();
1095 bank.set_sysvar_for_tests(sysvar);
1096 }
1097
1098 pub fn warp_to_slot(&mut self, warp_slot: Slot) -> Result<(), ProgramTestError> {
1100 let mut bank_forks = self.bank_forks.write().unwrap();
1101 let bank = bank_forks.working_bank();
1102
1103 bank.fill_bank_with_ticks_for_tests();
1106
1107 let working_slot = bank.slot();
1109 if warp_slot <= working_slot {
1110 return Err(ProgramTestError::InvalidWarpSlot);
1111 }
1112
1113 let pre_warp_slot = warp_slot - 1;
1117 let warp_bank = if pre_warp_slot == working_slot {
1118 bank.freeze();
1119 bank
1120 } else {
1121 bank_forks.insert(Bank::warp_from_parent(
1122 &bank,
1123 &Pubkey::default(),
1124 pre_warp_slot,
1125 ))
1126 };
1127 bank_forks.set_root(
1128 pre_warp_slot,
1129 &solana_runtime::accounts_background_service::AbsRequestSender::default(),
1130 Some(pre_warp_slot),
1131 );
1132
1133 bank_forks.insert(Bank::new_from_parent(
1135 &warp_bank,
1136 &Pubkey::default(),
1137 warp_slot,
1138 ));
1139
1140 let mut w_block_commitment_cache = self.block_commitment_cache.write().unwrap();
1143 w_block_commitment_cache.set_all_slots(warp_slot, warp_slot);
1148
1149 let bank = bank_forks.working_bank();
1150 self.last_blockhash = bank.last_blockhash();
1151 Ok(())
1152 }
1153}