crucible_test_context/
program_builder.rs1use crate::instruction_builder;
2use crate::{tx_result_to_outcome, TestContext, TxOutcome};
3use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
4use anchor_lang::{InstructionData, ToAccountMetas};
5use anyhow::{Context, Result};
6use solana_keypair::Keypair;
7use solana_pubkey::Pubkey;
8use solana_signer::Signer;
9
10pub struct ProgramBuilder<'a> {
11 pub(crate) ctx: &'a mut TestContext,
12 pub(crate) instruction: Instruction,
13 pub(crate) signers: Vec<Keypair>,
14 pub(crate) fee_payer: Option<Keypair>,
15}
16
17impl ProgramBuilder<'_> {
18 pub fn call<I>(mut self, instruction: I) -> Self
19 where
20 I: InstructionData,
21 {
22 self.instruction.data = instruction.data();
23 self
24 }
25
26 pub fn accounts<A>(mut self, accounts: A) -> Self
27 where
28 A: ToAccountMetas,
29 {
30 self.instruction.accounts = accounts.to_account_metas(None);
31 self
32 }
33
34 pub fn remaining_accounts(mut self, accounts: Vec<Pubkey>) -> Self {
35 for pubkey in accounts {
36 self.instruction
37 .accounts
38 .push(AccountMeta::new_readonly(pubkey, false));
39 }
40 self
41 }
42
43 pub fn remaining_accounts_metas(mut self, metas: Vec<AccountMeta>) -> Self {
44 self.instruction.accounts.extend(metas);
45 self
46 }
47
48 pub fn signers(mut self, signers: &[&Keypair]) -> Self {
49 self.signers = signers.iter().map(|k| k.insecure_clone()).collect();
50 self
51 }
52
53 pub fn fee_payer(mut self, fee_payer: &Keypair) -> Self {
54 self.fee_payer = Some(fee_payer.insecure_clone());
55 self
56 }
57
58 pub fn send(self) -> Result<TxOutcome> {
59 let fee_payer_pubkey = self
61 .fee_payer
62 .as_ref()
63 .map(|kp| kp.pubkey())
64 .or_else(|| self.signers.first().map(|kp| kp.pubkey()))
65 .unwrap_or_default();
66
67 let ixs = std::slice::from_ref(&self.instruction);
68
69 let __t_pre = std::time::Instant::now();
71 self.ctx.dirty_tracker.record_tx(ixs, &fee_payer_pubkey);
72 crate::SEND_BATCH_PRE_NS.with(|c| c.set(c.get() + __t_pre.elapsed().as_nanos() as u64));
73
74 let __t_svm = std::time::Instant::now();
76 let mut all_signers = self.signers;
77 if let Some(ref fp) = self.fee_payer {
78 if !all_signers.iter().any(|k| k.pubkey() == fp.pubkey()) {
79 all_signers.insert(0, fp.insecure_clone());
80 }
81 }
82 let payer = self
83 .fee_payer
84 .as_ref()
85 .unwrap_or(
86 all_signers
87 .first()
88 .context("At least one signer required")?,
89 )
90 .insecure_clone();
91 let result = instruction_builder::send_instruction(
92 &mut self.ctx.svm,
93 self.instruction,
94 &all_signers,
95 &payer,
96 self.ctx.sigverify,
97 )?;
98 crate::SEND_BATCH_SVM_NS.with(|c| c.set(c.get() + __t_svm.elapsed().as_nanos() as u64));
99
100 let __t_post = std::time::Instant::now();
102 let outcome = tx_result_to_outcome(result);
103
104 crate::increment_action_count();
106 if outcome.is_success() {
107 crate::increment_action_success_count();
108 }
109 crate::SEND_BATCH_POST_NS.with(|c| c.set(c.get() + __t_post.elapsed().as_nanos() as u64));
110
111 Ok(outcome)
112 }
113
114 pub fn add_transaction(self) -> Result<()> {
115 self.ctx.pending_instructions.push(self.instruction);
116 self.ctx.pending_signers.extend(self.signers);
117 Ok(())
118 }
119}