1use crate::solana_sdk::clock::UnixTimestamp;
2use std::convert::TryFrom;
3use std::{
4 collections::{HashMap, HashSet},
5 convert::TryInto,
6 path::Path,
7 time::{SystemTime, UNIX_EPOCH},
8};
9
10use borsh::BorshDeserialize;
11use bpf_loader_upgradeable::UpgradeableLoaderState;
12use itertools::izip;
13use rand::{prelude::StdRng, rngs::OsRng, SeedableRng};
14use serde::de::DeserializeOwned;
15use sha2::{Digest, Sha256};
16use solana_bpf_loader_program::{
17 solana_bpf_loader_deprecated_program, solana_bpf_loader_program,
18 solana_bpf_loader_upgradeable_program,
19};
20use solana_cli_output::display::println_transaction;
21use solana_client::{rpc_client::RpcClient, rpc_config::RpcTransactionConfig};
22use solana_program::{
23 bpf_loader, bpf_loader_upgradeable,
24 hash::Hash,
25 instruction::Instruction,
26 loader_instruction,
27 message::{v0::LoadedAddresses, Message, SanitizedMessage},
28 program_option::COption,
29 program_pack::Pack,
30 pubkey::Pubkey,
31 system_instruction, system_program,
32 sysvar::{self, rent},
33};
34use solana_runtime::{
35 accounts_db::AccountShrinkThreshold,
36 accounts_index::AccountSecondaryIndexes,
37 bank::{
38 Bank, TransactionBalancesSet, TransactionExecutionDetails, TransactionExecutionResult,
39 TransactionResults,
40 },
41 builtins::{Builtin, Builtins},
42 genesis_utils,
43};
44use solana_sdk::{
45 account::{Account, AccountSharedData},
46 commitment_config::CommitmentConfig,
47 genesis_config::GenesisConfig,
48 packet,
49 signature::Keypair,
50 signature::Signer,
51 system_transaction,
52 transaction::{Transaction, VersionedTransaction},
53};
54use solana_transaction_status::{
55 token_balances, ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta,
56 InnerInstructions, TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta,
57 UiTransactionEncoding, VersionedTransactionWithStatusMeta,
58};
59use spl_associated_token_account::get_associated_token_address;
60
61pub use bincode;
62pub use borsh;
63pub use serde;
64pub use solana_client;
65pub use solana_program;
66pub use solana_sdk;
67pub use solana_transaction_status;
68pub use spl_associated_token_account;
69pub use spl_memo;
70pub use spl_token;
71
72mod keys;
73mod programs;
74
75pub trait Environment {
77 fn payer(&self) -> Keypair;
79 fn execute_transaction(
81 &mut self,
82 txs: Transaction,
83 ) -> EncodedConfirmedTransactionWithStatusMeta;
84 #[deprecated(since = "0.2.0", note = "Please use `get_latest_blockhash()` instead")]
86 fn get_recent_blockhash(&self) -> Hash {
87 self.get_latest_blockhash()
88 }
89 fn get_latest_blockhash(&self) -> Hash;
91 fn get_rent_excemption(&self, data: usize) -> u64;
93 fn get_account(&self, pubkey: Pubkey) -> Option<Account>;
95
96 fn tx_with_instructions(
98 &self,
99 instructions: &[Instruction],
100 signers: &[&Keypair],
101 ) -> Transaction {
102 let payer = self.payer();
103 let mut signer_vec = vec![&payer];
104 signer_vec.extend_from_slice(signers);
105
106 let message = Message::new(instructions, Some(&self.payer().pubkey()));
107 let num_sigs: usize = message.header.num_required_signatures.into();
108 let required_sigs = message.account_keys[..num_sigs]
109 .into_iter()
110 .copied()
111 .collect::<HashSet<_>>();
112 let provided_sigs = signer_vec
113 .iter()
114 .map(|x| x.pubkey())
115 .collect::<HashSet<_>>();
116
117 for key in required_sigs.difference(&provided_sigs) {
118 println!("missing signature from {}", key.to_string());
119 }
120
121 for key in provided_sigs.difference(&required_sigs) {
122 println!("unnecessary signature from {}", key.to_string());
123 }
124
125 Transaction::new(&signer_vec, message, self.get_latest_blockhash())
126 }
127
128 fn execute_as_transaction(
130 &mut self,
131 instructions: &[Instruction],
132 signers: &[&Keypair],
133 ) -> EncodedConfirmedTransactionWithStatusMeta {
134 let tx = self.tx_with_instructions(instructions, signers);
135 return self.execute_transaction(tx);
136 }
137
138 fn execute_as_transaction_debug(
141 &mut self,
142 instructions: &[Instruction],
143 signers: &[&Keypair],
144 ) -> EncodedConfirmedTransactionWithStatusMeta {
145 let tx = self.tx_with_instructions(instructions, signers);
146 println!("{:#?}", &tx);
147 return self.execute_transaction(tx);
148 }
149
150 fn create_account(&mut self, keypair: &Keypair, lamports: u64, space: usize, owner: Pubkey) {
152 self.execute_transaction(system_transaction::create_account(
153 &self.payer(),
154 &keypair,
155 self.get_latest_blockhash(),
156 lamports,
157 space as u64,
158 &owner,
159 ))
160 .assert_success();
161 }
162
163 fn create_account_rent_excempt(&mut self, keypair: &Keypair, space: usize, owner: Pubkey) {
165 self.execute_transaction(system_transaction::create_account(
166 &self.payer(),
167 &keypair,
168 self.get_latest_blockhash(),
169 self.get_rent_excemption(space),
170 space as u64,
171 &owner,
172 ))
173 .assert_success();
174 }
175
176 fn create_token_mint(
178 &mut self,
179 mint: &Keypair,
180 authority: Pubkey,
181 freeze_authority: Option<Pubkey>,
182 decimals: u8,
183 ) {
184 self.execute_as_transaction(
185 &[
186 system_instruction::create_account(
187 &self.payer().pubkey(),
188 &mint.pubkey(),
189 self.get_rent_excemption(spl_token::state::Mint::LEN),
190 spl_token::state::Mint::LEN as u64,
191 &spl_token::ID,
192 ),
193 spl_token::instruction::initialize_mint(
194 &spl_token::ID,
195 &mint.pubkey(),
196 &authority,
197 freeze_authority.as_ref(),
198 decimals,
199 )
200 .unwrap(),
201 ],
202 &[mint],
203 )
204 .assert_success();
205 }
206
207 fn mint_tokens(&mut self, mint: Pubkey, authority: &Keypair, account: Pubkey, amount: u64) {
209 self.execute_as_transaction(
210 &[spl_token::instruction::mint_to(
211 &spl_token::ID,
212 &mint,
213 &account,
214 &authority.pubkey(),
215 &[],
216 amount,
217 )
218 .unwrap()],
219 &[authority],
220 )
221 .assert_success();
222 }
223
224 fn create_token_account(&mut self, account: &Keypair, mint: Pubkey) {
227 self.execute_as_transaction(
228 &[
229 system_instruction::create_account(
230 &self.payer().pubkey(),
231 &account.pubkey(),
232 self.get_rent_excemption(spl_token::state::Account::LEN),
233 spl_token::state::Account::LEN as u64,
234 &spl_token::ID,
235 ),
236 spl_token::instruction::initialize_account(
237 &spl_token::ID,
238 &account.pubkey(),
239 &mint,
240 &account.pubkey(),
241 )
242 .unwrap(),
243 ],
244 &[account],
245 )
246 .assert_success();
247 }
248
249 fn create_associated_token_account(&mut self, owner: &Keypair, mint: Pubkey) -> Pubkey {
251 self.execute_as_transaction(
252 &[
253 spl_associated_token_account::instruction::create_associated_token_account(
254 &self.payer().pubkey(),
255 &owner.pubkey(),
256 &mint,
257 ),
258 ],
259 &[],
260 );
261 get_associated_token_address(&owner.pubkey(), &mint)
262 }
263
264 fn get_or_create_associated_token_account(&mut self, owner: &Keypair, mint: Pubkey) -> Pubkey {
266 let acc = get_associated_token_address(&owner.pubkey(), &mint);
267 if self.get_account(acc).is_none() {
268 self.create_associated_token_account(owner, mint);
269 }
270 acc
271 }
272
273 fn create_account_with_data(&mut self, account: &Keypair, data: Vec<u8>) {
276 self.execute_transaction(system_transaction::create_account(
277 &self.payer(),
278 account,
279 self.get_latest_blockhash(),
280 self.get_rent_excemption(data.len()),
281 data.len() as u64,
282 &bpf_loader::id(),
283 ))
284 .assert_success();
285
286 let mut offset = 0usize;
287 for chunk in data.chunks(900) {
288 println!("writing bytes {} to {}", offset, offset + chunk.len());
289 self.execute_as_transaction(
290 &[loader_instruction::write(
291 &account.pubkey(),
292 &bpf_loader::id(),
293 offset as u32,
294 chunk.to_vec(),
295 )],
296 &[account],
297 )
298 .assert_success();
299 offset += chunk.len();
300 }
301 }
302
303 fn deploy_program<P: AsRef<Path>>(&mut self, program_path: P) -> Pubkey {
306 let data = std::fs::read(program_path).unwrap();
307 let mut hash = Sha256::default();
308 hash.update(&data);
309 let mut rng = StdRng::from_seed(hash.finalize()[..].try_into().unwrap());
310 let keypair = Keypair::generate(&mut rng);
311
312 if self.get_account(keypair.pubkey()).is_none() {
313 self.create_account_with_data(&keypair, data);
314 self.execute_as_transaction(
315 &[loader_instruction::finalize(
316 &keypair.pubkey(),
317 &bpf_loader::id(),
318 )],
319 &[&keypair],
320 )
321 .assert_success();
322 }
323
324 keypair.pubkey()
325 }
326
327 fn get_unpacked_account<T: Pack>(&self, pubkey: Pubkey) -> Option<T> {
329 let acc = self.get_account(pubkey)?;
330 Some(T::unpack_unchecked(&acc.data).unwrap())
331 }
332
333 fn get_deserialized_account<T: BorshDeserialize>(&self, pubkey: Pubkey) -> Option<T> {
335 let acc = self.get_account(pubkey)?;
336 Some(T::try_from_slice(&acc.data).unwrap())
337 }
338
339 fn get_serde_deserialized_account<'a, T: DeserializeOwned>(&self, pubkey: Pubkey) -> Option<T> {
341 let acc = self.get_account(pubkey)?;
342 Some(bincode::deserialize(&acc.data).unwrap())
343 }
344}
345
346pub struct LocalEnvironment {
349 bank: Bank,
350 faucet: Keypair,
351}
352
353impl LocalEnvironment {
354 pub fn builder() -> LocalEnvironmentBuilder {
356 LocalEnvironmentBuilder::new()
357 }
358
359 pub fn new() -> LocalEnvironment {
361 Self::builder().build()
362 }
363
364 pub fn bank(&mut self) -> &mut Bank {
365 &mut self.bank
366 }
367
368 pub fn advance_blockhash(&self) -> Hash {
370 let parent_distance = if self.bank.slot() == 0 {
371 1
372 } else {
373 self.bank.slot() - self.bank.parent_slot()
374 };
375
376 for _ in 0..parent_distance {
377 let last_blockhash = self.bank.last_blockhash();
378 while self.bank.last_blockhash() == last_blockhash {
379 self.bank.register_tick(&Hash::new_unique())
380 }
381 }
382
383 self.get_latest_blockhash()
384 }
385}
386
387impl Environment for LocalEnvironment {
388 fn payer(&self) -> Keypair {
389 clone_keypair(&self.faucet)
390 }
391
392 fn execute_transaction(
393 &mut self,
394 tx: Transaction,
395 ) -> EncodedConfirmedTransactionWithStatusMeta {
396 let len = bincode::serialize(&tx).unwrap().len();
397 if len > packet::PACKET_DATA_SIZE {
398 panic!(
399 "tx {:?} of size {} is {} too large",
400 tx,
401 len,
402 len - packet::PACKET_DATA_SIZE
403 )
404 }
405 let txs = vec![tx];
406
407 let batch = self.bank.prepare_batch_for_tests(txs.clone());
408 let mut mint_decimals = HashMap::new();
409 let tx_pre_token_balances =
410 token_balances::collect_token_balances(&self.bank, &batch, &mut mint_decimals);
411 let slot = self.bank.slot();
412 let mut timings = Default::default();
413 let (
414 TransactionResults {
415 execution_results, ..
416 },
417 TransactionBalancesSet {
418 pre_balances,
419 post_balances,
420 ..
421 },
422 ) = self.bank.load_execute_and_commit_transactions(
423 &batch,
424 usize::MAX,
425 true,
426 true,
427 true,
428 true,
429 &mut timings,
430 );
431
432 let tx_post_token_balances =
433 token_balances::collect_token_balances(&self.bank, &batch, &mut mint_decimals);
434 izip!(
435 txs.iter(),
436 execution_results.into_iter(),
437 pre_balances.into_iter(),
438 post_balances.into_iter(),
439 tx_pre_token_balances.into_iter(),
440 tx_post_token_balances.into_iter(),
441 )
442 .map(
443 |(
444 tx,
445 execution_result,
446 pre_balances,
447 post_balances,
448 pre_token_balances,
449 post_token_balances,
450 ): (
451 &Transaction,
452 TransactionExecutionResult,
453 Vec<u64>,
454 Vec<u64>,
455 Vec<TransactionTokenBalance>,
456 Vec<TransactionTokenBalance>,
457 )| {
458 let fee = self.bank.get_fee_for_message(&SanitizedMessage::try_from(tx.message().clone()).expect("Failed to sanitize transaction"))
459 .expect("Fee calculation must succeed");
460
461 let (status, inner_instructions, log_messages) = match execution_result {
462 TransactionExecutionResult::Executed { details: TransactionExecutionDetails { status, inner_instructions, log_messages, .. }, .. } =>
463 (status, inner_instructions, log_messages),
464 TransactionExecutionResult::NotExecuted(err) => (Err(err), None, None)
465 };
466
467 let inner_instructions = inner_instructions.map(|inner_instructions| {
468 inner_instructions
469 .into_iter()
470 .enumerate()
471 .map(|(index, instructions)| InnerInstructions {
472 index: index as u8,
473 instructions,
474 })
475 .filter(|i| !i.instructions.is_empty())
476 .collect()
477 });
478
479 let tx_status_meta = TransactionStatusMeta {
480 status,
481 fee,
482 pre_balances,
483 post_balances,
484 pre_token_balances: Some(pre_token_balances),
485 post_token_balances: Some(post_token_balances),
486 inner_instructions,
487 log_messages,
488 rewards: None,
489 loaded_addresses: LoadedAddresses {
490 writable: vec![], readonly: vec![], },
493 return_data: None
494 };
495
496 ConfirmedTransactionWithStatusMeta {
497 slot,
498 tx_with_meta: TransactionWithStatusMeta::Complete(VersionedTransactionWithStatusMeta {
499 transaction: VersionedTransaction::from(tx.clone()),
500 meta: tx_status_meta,
501 }),
502 block_time: Some(
503 SystemTime::now()
504 .duration_since(UNIX_EPOCH)
505 .unwrap()
506 .as_secs()
507 .try_into()
508 .unwrap(),
509 ),
510 }
511 .encode(UiTransactionEncoding::Binary, None)
512 .expect("Failed to encode transaction")
513 },
514 )
515 .next().expect("transaction could not be executed. Enable debug logging to get more information on why")
516 }
517
518 fn get_latest_blockhash(&self) -> Hash {
519 self.bank.last_blockhash()
520 }
521
522 fn get_rent_excemption(&self, data: usize) -> u64 {
523 self.bank.get_minimum_balance_for_rent_exemption(data)
524 }
525
526 fn get_account(&self, pubkey: Pubkey) -> Option<Account> {
527 self.bank.get_account(&pubkey).map(|acc| acc.into())
528 }
529}
530
531pub struct LocalEnvironmentBuilder {
532 config: GenesisConfig,
533 faucet: Keypair,
534}
535
536impl LocalEnvironmentBuilder {
537 fn new() -> Self {
538 let faucet = random_keypair();
539 let mut config = GenesisConfig::new(
540 &[(
541 faucet.pubkey(),
542 AccountSharedData::new(1u64 << 48, 0, &system_program::id()),
543 )],
544 &[],
545 );
546 genesis_utils::activate_all_features(&mut config);
547
548 let mut builder = LocalEnvironmentBuilder { faucet, config };
549 builder.add_account_with_data(
550 spl_associated_token_account::ID,
551 bpf_loader::ID,
552 programs::SPL_ASSOCIATED_TOKEN,
553 true,
554 );
555 builder.add_account_with_data(
556 "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
557 .parse()
558 .unwrap(),
559 bpf_loader::ID,
560 programs::SPL_MEMO1,
561 true,
562 );
563 builder.add_account_with_data(spl_memo::ID, bpf_loader::ID, programs::SPL_MEMO3, true);
564 builder.add_account_with_data(spl_token::ID, bpf_loader::ID, programs::SPL_TOKEN, true);
565 builder.add_account_with_lamports(rent::ID, sysvar::ID, 1);
566 builder
567 }
568
569 pub fn set_creation_time(&mut self, unix_timestamp: UnixTimestamp) -> &mut Self {
571 self.config.creation_time = unix_timestamp as UnixTimestamp;
572 self
573 }
574
575 pub fn add_account(&mut self, pubkey: Pubkey, account: Account) -> &mut Self {
577 self.config.add_account(pubkey, account.into());
578 self
579 }
580
581 pub fn add_program<P: AsRef<Path>>(&mut self, pubkey: Pubkey, path: P) -> &mut Self {
583 self.add_account_with_data(pubkey, bpf_loader::ID, &std::fs::read(path).unwrap(), true);
584 self
585 }
586
587 pub fn add_account_with_data(
589 &mut self,
590 pubkey: Pubkey,
591 owner: Pubkey,
592 data: &[u8],
593 executable: bool,
594 ) -> &mut Self {
595 self.add_account(
596 pubkey,
597 Account {
598 lamports: self.config.rent.minimum_balance(data.len()),
599 data: data.to_vec(),
600 executable,
601 owner,
602 rent_epoch: 0,
603 },
604 )
605 }
606
607 pub fn add_account_with_lamports(
609 &mut self,
610 pubkey: Pubkey,
611 owner: Pubkey,
612 lamports: u64,
613 ) -> &mut Self {
614 self.add_account(
615 pubkey,
616 Account {
617 lamports,
618 data: vec![],
619 executable: false,
620 owner,
621 rent_epoch: 0,
622 },
623 )
624 }
625
626 pub fn add_account_with_packable<P: Pack>(
628 &mut self,
629 pubkey: Pubkey,
630 owner: Pubkey,
631 data: P,
632 ) -> &mut Self {
633 let data = {
634 let mut buf = vec![0u8; P::LEN];
635 data.pack_into_slice(&mut buf[..]);
636 buf
637 };
638 self.add_account_with_data(pubkey, owner, &data, false)
639 }
640
641 pub fn add_token_mint(
643 &mut self,
644 pubkey: Pubkey,
645 mint_authority: Option<Pubkey>,
646 supply: u64,
647 decimals: u8,
648 freeze_authority: Option<Pubkey>,
649 ) -> &mut Self {
650 self.add_account_with_packable(
651 pubkey,
652 spl_token::ID,
653 spl_token::state::Mint {
654 mint_authority: COption::from(mint_authority.map(|c| c.clone())),
655 supply,
656 decimals,
657 is_initialized: true,
658 freeze_authority: COption::from(freeze_authority.map(|c| c.clone())),
659 },
660 )
661 }
662
663 pub fn add_account_with_tokens(
665 &mut self,
666 pubkey: Pubkey,
667 mint: Pubkey,
668 owner: Pubkey,
669 amount: u64,
670 ) -> &mut Self {
671 self.add_account_with_packable(
672 pubkey,
673 spl_token::ID,
674 spl_token::state::Account {
675 mint,
676 owner,
677 amount,
678 delegate: COption::None,
679 state: spl_token::state::AccountState::Initialized,
680 is_native: COption::None,
681 delegated_amount: 0,
682 close_authority: COption::None,
683 },
684 )
685 }
686
687 pub fn add_associated_account_with_tokens(
689 &mut self,
690 owner: Pubkey,
691 mint: Pubkey,
692 amount: u64,
693 ) -> &mut Self {
694 self.add_account_with_packable(
695 get_associated_token_address(&owner, &mint),
696 spl_token::ID,
697 spl_token::state::Account {
698 mint,
699 owner,
700 amount,
701 delegate: COption::None,
702 state: spl_token::state::AccountState::Initialized,
703 is_native: COption::None,
704 delegated_amount: 0,
705 close_authority: COption::None,
706 },
707 )
708 }
709
710 pub fn clone_account_from_cluster(&mut self, pubkey: Pubkey, client: &RpcClient) -> &mut Self {
712 println!("Loading account {} from cluster", pubkey);
713 let account = client
714 .get_account(&pubkey)
715 .expect("couldn't retrieve account");
716 self.add_account(
717 pubkey,
718 Account {
719 lamports: account.lamports,
720 data: account.data,
721 executable: account.executable,
722 owner: account.owner,
723 rent_epoch: 0,
724 },
725 )
726 }
727
728 pub fn clone_accounts_from_cluster(
730 &mut self,
731 pubkeys: &[Pubkey],
732 client: &RpcClient,
733 ) -> &mut Self {
734 for &pubkey in pubkeys {
735 self.clone_account_from_cluster(pubkey, client);
736 }
737 self
738 }
739
740 pub fn clone_upgradable_program_from_cluster(
742 &mut self,
743 client: &RpcClient,
744 pubkey: Pubkey,
745 ) -> &mut Self {
746 println!("Loading upgradable program {} from cluster", pubkey);
747 let account = client
748 .get_account(&pubkey)
749 .expect("couldn't retrieve account");
750 let upgradable: UpgradeableLoaderState = account.deserialize_data().unwrap();
751 if let UpgradeableLoaderState::Program {
752 programdata_address,
753 } = upgradable
754 {
755 self.add_account(pubkey, account);
756 self.clone_account_from_cluster(programdata_address, client);
757 } else {
758 panic!("Account is not an upgradable program")
759 }
760 self
761 }
762
763 pub fn build(&mut self) -> LocalEnvironment {
765 let tmpdir = Path::new("/tmp/");
766
767 let bank = Bank::new_with_paths(
768 &self.config,
769 vec![tmpdir.to_path_buf()],
770 None,
771 Some(&Builtins {
772 genesis_builtins: [
773 solana_bpf_loader_upgradeable_program!(),
774 solana_bpf_loader_program!(),
775 solana_bpf_loader_deprecated_program!(),
776 ]
777 .iter()
778 .map(|p| Builtin::new(&p.0, p.1, p.2))
779 .collect(),
780 feature_transitions: vec![],
781 }),
782 AccountSecondaryIndexes {
783 keys: None,
784 indexes: HashSet::new(),
785 },
786 false,
787 AccountShrinkThreshold::default(),
788 false,
789 None,
790 None,
791 );
792
793 let env = LocalEnvironment {
794 bank,
795 faucet: clone_keypair(&self.faucet),
796 };
797 env.advance_blockhash();
798
799 env
800 }
801}
802
803pub struct RemoteEnvironment {
805 client: RpcClient,
806 payer: Keypair,
807}
808
809impl RemoteEnvironment {
810 pub fn new(client: RpcClient, payer: Keypair) -> Self {
812 RemoteEnvironment { client, payer }
813 }
814
815 pub fn new_with_airdrop(client: RpcClient, payer: Keypair, lamports: u64) -> Self {
817 let env = RemoteEnvironment { client, payer };
818 env.airdrop(env.payer().pubkey(), lamports);
819 env
820 }
821
822 pub fn airdrop(&self, account: Pubkey, lamports: u64) {
824 if self.client.get_balance(&account).expect("get balance") < lamports {
825 println!("Requesting airdrop...");
826 let blockhash = self.client.get_latest_blockhash().unwrap();
827 let sig = self
828 .client
829 .request_airdrop_with_blockhash(&account, lamports, &blockhash)
830 .unwrap();
831 self.client
832 .confirm_transaction_with_spinner(&sig, &blockhash, CommitmentConfig::confirmed())
833 .unwrap();
834 }
835 }
836}
837
838impl Environment for RemoteEnvironment {
839 fn payer(&self) -> Keypair {
840 clone_keypair(&self.payer)
841 }
842
843 fn execute_transaction(
844 &mut self,
845 tx: Transaction,
846 ) -> EncodedConfirmedTransactionWithStatusMeta {
847 let sig = match self.client.send_and_confirm_transaction(&tx) {
848 Err(e) => panic!("{:#?}", e),
849 Ok(sig) => sig,
850 };
851 self.client
852 .get_transaction_with_config(
853 &sig,
854 RpcTransactionConfig {
855 encoding: Some(UiTransactionEncoding::Binary),
856 commitment: Some(CommitmentConfig::confirmed()),
857 ..RpcTransactionConfig::default()
858 },
859 )
860 .unwrap()
861 }
862
863 fn get_latest_blockhash(&self) -> Hash {
864 self.client.get_latest_blockhash().unwrap()
865 }
866
867 fn get_rent_excemption(&self, data: usize) -> u64 {
868 self.client
869 .get_minimum_balance_for_rent_exemption(data)
870 .unwrap()
871 }
872
873 fn get_account(&self, pubkey: Pubkey) -> Option<Account> {
874 self.client
875 .get_account_with_commitment(&pubkey, self.client.commitment())
876 .unwrap()
877 .value
878 }
879}
880
881pub trait PrintableTransaction {
883 fn print_named(&self, name: &str);
885
886 fn print(&self) {
888 self.print_named("");
889 }
890
891 fn assert_success(&self);
893}
894
895impl PrintableTransaction for ConfirmedTransactionWithStatusMeta {
896 fn print_named(&self, name: &str) {
897 let tx = self.tx_with_meta.get_transaction();
898 let encoded = self
899 .clone()
900 .encode(UiTransactionEncoding::JsonParsed, None)
901 .expect("Failed to encode");
902 println!("EXECUTE {} (slot {})", name, encoded.slot);
903 println_transaction(&tx, encoded.transaction.meta.as_ref(), " ", None, None);
904 }
905
906 fn assert_success(&self) {
907 match &self.tx_with_meta.get_status_meta() {
908 Some(meta) if meta.status.is_err() => {
909 self.print();
910 panic!("tx failed!")
911 }
912 _ => (),
913 }
914 }
915}
916
917impl PrintableTransaction for EncodedConfirmedTransactionWithStatusMeta {
918 fn print_named(&self, name: &str) {
919 let tx = self.transaction.transaction.decode().unwrap();
920 println!("EXECUTE {} (slot {})", name, self.slot);
921 println_transaction(&tx, self.transaction.meta.as_ref(), " ", None, None);
922 }
923
924 fn assert_success(&self) {
925 match &self.transaction.meta {
926 Some(meta) if meta.err.is_some() => {
927 self.print();
928 panic!("tx failed!")
929 }
930 _ => (),
931 }
932 }
933}
934
935pub enum LogLevel {
936 TRACE,
937 DEBUG,
938 INFO,
939 WARN,
940 ERROR,
941}
942
943pub fn setup_logging(level: LogLevel) {
945 match level {
946 LogLevel::TRACE => solana_logger::setup_with_default(
947 "trace,solana_runtime::message_processor=trace,solana_metrics::metrics=error",
948 ),
949 LogLevel::DEBUG => solana_logger::setup_with_default(
950 "debug,solana_runtime::message_processor=debug,solana_metrics::metrics=error",
951 ),
952 LogLevel::INFO => solana_logger::setup_with_default(
953 "info,solana_runtime::message_processor=info,solana_metrics::metrics=error",
954 ),
955 LogLevel::WARN => solana_logger::setup_with_default(
956 "warn,solana_runtime::message_processor=warn,solana_metrics::metrics=error",
957 ),
958 LogLevel::ERROR => solana_logger::setup_with_default(
959 "error,solana_runtime::message_processor=error,solana_metrics::metrics=error",
960 ),
961 }
962}
963
964pub fn clone_keypair(keypair: &Keypair) -> Keypair {
966 Keypair::from_bytes(&keypair.to_bytes()).unwrap()
967}
968
969pub fn random_keypair() -> Keypair {
971 Keypair::generate(&mut OsRng::default())
972}
973
974pub fn keypair(n: u8) -> Keypair {
977 Keypair::from_bytes(&keys::KEYPAIRS[n as usize]).unwrap()
978}
979
980pub fn devnet_client() -> RpcClient {
982 RpcClient::new_with_commitment(
983 "https://api.devnet.solana.com/".to_string(),
984 CommitmentConfig::confirmed(),
985 )
986}
987
988pub fn testnet_client() -> RpcClient {
990 RpcClient::new_with_commitment(
991 "https://api.testnet.solana.com/".to_string(),
992 CommitmentConfig::confirmed(),
993 )
994}
995
996pub fn localhost_client() -> RpcClient {
998 RpcClient::new_with_commitment(
999 "http://localhost:8899/".to_string(),
1000 CommitmentConfig::confirmed(),
1001 )
1002}