use crate::AnchorContext;
use litesvm::LiteSVM;
use solana_program::pubkey::Pubkey;
use std::collections::HashMap;
pub struct AnchorLiteSVM {
svm: LiteSVM,
#[cfg(test)]
pub(crate) programs: Vec<(Pubkey, Vec<u8>)>,
#[cfg(not(test))]
programs: Vec<(Pubkey, Vec<u8>)>,
#[cfg(test)]
pub(crate) primary_program_id: Option<Pubkey>,
#[cfg(not(test))]
primary_program_id: Option<Pubkey>,
}
impl AnchorLiteSVM {
pub fn new() -> Self {
Self {
svm: LiteSVM::new(),
programs: Vec::new(),
primary_program_id: None,
}
}
pub fn deploy_program(mut self, program_id: Pubkey, program_bytes: &[u8]) -> Self {
if self.primary_program_id.is_none() {
self.primary_program_id = Some(program_id);
}
self.programs.push((program_id, program_bytes.to_vec()));
self
}
pub fn deploy_programs(mut self, programs: Vec<(Pubkey, Vec<u8>)>) -> Self {
if self.primary_program_id.is_none() && !programs.is_empty() {
self.primary_program_id = Some(programs[0].0);
}
self.programs.extend(programs);
self
}
pub fn with_primary_program(mut self, program_id: Pubkey) -> Self {
self.primary_program_id = Some(program_id);
self
}
pub fn build(mut self) -> AnchorContext {
assert!(
!self.programs.is_empty(),
"At least one program must be deployed"
);
let primary_program_id = self
.primary_program_id
.expect("Primary program ID should be set");
for (program_id, program_bytes) in self.programs {
self.svm.add_program(program_id, &program_bytes);
}
AnchorContext::new(self.svm, primary_program_id)
}
pub fn build_with_program(program_id: Pubkey, program_bytes: &[u8]) -> AnchorContext {
Self::new()
.deploy_program(program_id, program_bytes)
.build()
}
pub fn from_programs(
programs: HashMap<Pubkey, Vec<u8>>,
primary_program_id: Option<Pubkey>,
) -> AnchorContext {
let mut builder = Self::new();
let programs_vec: Vec<(Pubkey, Vec<u8>)> = programs.into_iter().collect();
if let Some(primary) = primary_program_id {
builder = builder.with_primary_program(primary);
}
builder.deploy_programs(programs_vec).build()
}
}
impl Default for AnchorLiteSVM {
fn default() -> Self {
Self::new()
}
}
pub trait ProgramTestExt {
fn test_with(self, program_bytes: &[u8]) -> AnchorContext;
}
impl ProgramTestExt for Pubkey {
fn test_with(self, program_bytes: &[u8]) -> AnchorContext {
AnchorLiteSVM::build_with_program(self, program_bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_single_program() {
let program_id = Pubkey::new_unique();
let program_bytes = vec![0u8; 100];
let builder = AnchorLiteSVM::new()
.deploy_program(program_id, &program_bytes);
assert_eq!(builder.programs.len(), 1);
assert_eq!(builder.primary_program_id, Some(program_id));
}
#[test]
fn test_multiple_programs() {
let program1_id = Pubkey::new_unique();
let program2_id = Pubkey::new_unique();
let program1_bytes = vec![0u8; 100];
let program2_bytes = vec![0u8; 200];
let builder = AnchorLiteSVM::new()
.deploy_program(program1_id, &program1_bytes)
.deploy_program(program2_id, &program2_bytes);
assert_eq!(builder.programs.len(), 2);
assert_eq!(builder.primary_program_id, Some(program1_id));
}
#[test]
fn test_with_primary_program() {
let program1_id = Pubkey::new_unique();
let program2_id = Pubkey::new_unique();
let program1_bytes = vec![0u8; 100];
let program2_bytes = vec![0u8; 200];
let builder = AnchorLiteSVM::new()
.deploy_program(program1_id, &program1_bytes)
.deploy_program(program2_id, &program2_bytes)
.with_primary_program(program2_id);
assert_eq!(builder.primary_program_id, Some(program2_id));
}
#[test]
#[should_panic(expected = "At least one program must be deployed")]
fn test_build_without_programs() {
AnchorLiteSVM::new().build();
}
}