use crate::{tx_result_to_outcome, TestContext, TxOutcome};
use anchor_lang::solana_program::instruction::Instruction;
use anyhow::{Context, Result};
use solana_keypair::Keypair;
use solana_message::{legacy::Message, VersionedMessage};
use solana_pubkey::Pubkey;
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
pub struct InstructionBuilder<'a> {
pub(crate) ctx: &'a mut TestContext,
pub(crate) instruction: Instruction,
pub(crate) signers: Vec<Keypair>,
pub(crate) fee_payer: Option<Keypair>,
}
impl InstructionBuilder<'_> {
pub fn signers(mut self, signers: &[&Keypair]) -> Self {
self.signers = signers.iter().map(|k| k.insecure_clone()).collect();
self
}
pub fn fee_payer(mut self, fee_payer: &Keypair) -> Self {
self.fee_payer = Some(fee_payer.insecure_clone());
self
}
pub fn send(self) -> Result<TxOutcome> {
let ixs = std::slice::from_ref(&self.instruction);
let fee_payer_pubkey = self
.fee_payer
.as_ref()
.map(|kp| kp.pubkey())
.or_else(|| self.signers.first().map(|kp| kp.pubkey()))
.unwrap_or_default();
let mut all_signers = self.signers;
if let Some(ref fp) = self.fee_payer {
if !all_signers.iter().any(|k| k.pubkey() == fp.pubkey()) {
all_signers.insert(0, fp.insecure_clone());
}
}
let fee_payer = all_signers
.first()
.context("At least one signer required")?
.insecure_clone();
let __t_pre = std::time::Instant::now();
self.ctx.dirty_tracker.record_tx(ixs, &fee_payer_pubkey);
crate::SEND_BATCH_PRE_NS.with(|c| c.set(c.get() + __t_pre.elapsed().as_nanos() as u64));
let __t_svm = std::time::Instant::now();
let result = send_instruction(
&mut self.ctx.svm,
self.instruction,
&all_signers,
&fee_payer,
self.ctx.sigverify,
)?;
crate::SEND_BATCH_SVM_NS.with(|c| c.set(c.get() + __t_svm.elapsed().as_nanos() as u64));
let __t_post = std::time::Instant::now();
let outcome = tx_result_to_outcome(result);
crate::increment_action_count();
if outcome.is_success() {
crate::increment_action_success_count();
}
crate::SEND_BATCH_POST_NS.with(|c| c.set(c.get() + __t_post.elapsed().as_nanos() as u64));
Ok(outcome)
}
pub fn add_transaction(self) -> Result<()> {
self.ctx.pending_instructions.push(self.instruction);
self.ctx.pending_signers.extend(self.signers);
Ok(())
}
}
pub fn send_instruction(
svm: &mut litesvm::LiteSVM,
instruction: Instruction,
signers: &[Keypair],
payer: &Keypair,
sigverify: bool,
) -> Result<litesvm::types::TransactionResult> {
let signer_refs: Vec<&Keypair> = signers.iter().collect();
send_transaction(svm, vec![instruction], &signer_refs, payer, sigverify)
}
pub fn send_transaction(
svm: &mut litesvm::LiteSVM,
instructions: Vec<Instruction>,
signers: &[&Keypair],
payer: &Keypair,
sigverify: bool,
) -> Result<litesvm::types::TransactionResult> {
let debug = std::env::var("FUZZ_DEBUG").is_ok();
let __t0 = std::time::Instant::now();
let blockhash = if sigverify {
svm.expire_blockhash();
svm.latest_blockhash()
} else {
svm.latest_blockhash()
};
let __t_blockhash = __t0.elapsed().as_nanos() as u64;
let __t1 = std::time::Instant::now();
let message = Message::new_with_blockhash(&instructions, Some(&payer.pubkey()), &blockhash);
let tx = if sigverify {
let mut keypairs = signers.to_vec();
if !keypairs.iter().any(|k| k.pubkey() == payer.pubkey()) {
keypairs.push(payer);
}
VersionedTransaction::try_new(VersionedMessage::Legacy(message), &keypairs)?
} else {
let num_sigs = message.header.num_required_signatures as usize;
let signatures = vec![solana_signature::Signature::default(); num_sigs];
VersionedTransaction {
signatures,
message: VersionedMessage::Legacy(message),
}
};
let __t_sign = __t1.elapsed().as_nanos() as u64;
let __t2 = std::time::Instant::now();
let result = svm.send_transaction(tx);
let __t_exec = __t2.elapsed().as_nanos() as u64;
crate::SEND_TX_BLOCKHASH_NS.with(|c| c.set(c.get() + __t_blockhash));
crate::SEND_TX_SIGN_NS.with(|c| c.set(c.get() + __t_sign));
crate::SEND_TX_EXEC_NS.with(|c| c.set(c.get() + __t_exec));
if debug {
match &result {
Ok(meta) => {
eprintln!(
"[TX] SUCCESS - compute_units={}",
meta.compute_units_consumed
);
}
Err(failed) => {
eprintln!("[TX] FAILED - error: {:?}", failed.err);
for log in &failed.meta.logs {
eprintln!("[TX] {}", log);
}
}
}
}
Ok(result)
}