Skip to main content

crucible_test_context/
program_builder.rs

1use 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        // Resolve fee payer: explicit > first signer > Pubkey::default (for dirty tracking)
60        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        // Pre-tx: dirty tracking
70        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        // SVM execution — pass all signers, including fee payer
75        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        // Post-tx: outcome parsing
101        let __t_post = std::time::Instant::now();
102        let outcome = tx_result_to_outcome(result);
103
104        // Track tx success/failure for monitor display
105        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}