light_program_test/program_test/
light_program_test.rs1use std::fmt::{self, Debug, Formatter};
2
3#[cfg(feature = "devenv")]
4use account_compression::{AddressMerkleTreeAccount, QueueAccount};
5use light_client::{
6 indexer::{AddressMerkleTreeAccounts, StateMerkleTreeAccounts},
7 rpc::{merkle_tree::MerkleTreeExt, RpcError},
8};
9#[cfg(feature = "devenv")]
10use light_compressed_account::hash_to_bn254_field_size_be;
11use light_prover_client::prover::spawn_prover;
12use litesvm::LiteSVM;
13#[cfg(feature = "devenv")]
14use solana_account::WritableAccount;
15use solana_sdk::signature::{Keypair, Signer};
16
17#[cfg(feature = "devenv")]
18use crate::accounts::initialize::initialize_accounts;
19#[cfg(feature = "devenv")]
20use crate::program_test::TestRpc;
21use crate::{
22 accounts::{test_accounts::TestAccounts, test_keypairs::TestKeypairs},
23 indexer::TestIndexer,
24 utils::setup_light_programs::setup_light_programs,
25 ProgramTestConfig,
26};
27
28pub struct LightProgramTest {
29 pub config: ProgramTestConfig,
30 pub context: LiteSVM,
31 pub pre_context: Option<LiteSVM>,
32 pub indexer: Option<TestIndexer>,
33 pub test_accounts: TestAccounts,
34 pub payer: Keypair,
35 pub transaction_counter: usize,
36}
37
38impl LightProgramTest {
39 pub fn get_pre_transaction_account(
58 &self,
59 pubkey: &solana_sdk::pubkey::Pubkey,
60 ) -> Option<solana_sdk::account::Account> {
61 self.pre_context
62 .as_ref()
63 .and_then(|ctx| ctx.get_account(pubkey))
64 }
65
66 pub async fn new(config: ProgramTestConfig) -> Result<LightProgramTest, RpcError> {
67 let mut context = setup_light_programs(config.additional_programs.clone())?;
68 let payer = Keypair::new();
69 context
70 .airdrop(&payer.pubkey(), 100_000_000_000_000)
71 .expect("Payer airdrop failed.");
72 let mut context = Self {
73 context,
74 pre_context: None,
75 indexer: None,
76 test_accounts: TestAccounts::get_program_test_test_accounts(),
77 payer,
78 config: config.clone(),
79 transaction_counter: 0,
80 };
81 let keypairs = TestKeypairs::program_test_default();
82
83 context
84 .context
85 .airdrop(&keypairs.governance_authority.pubkey(), 100_000_000_000_000)
86 .expect("governance_authority airdrop failed.");
87 context
88 .context
89 .airdrop(&keypairs.forester.pubkey(), 10_000_000_000)
90 .expect("forester airdrop failed.");
91
92 #[cfg(feature = "devenv")]
93 {
94 if !config.skip_protocol_init {
95 let restore_logs = context.config.no_logs;
96 if context.config.skip_startup_logs {
97 context.config.no_logs = true;
98 }
99 initialize_accounts(&mut context, &config, &keypairs).await?;
100 crate::accounts::compressible_config::create_compressible_config(&mut context)
101 .await?;
102 if context.config.skip_startup_logs {
103 context.config.no_logs = restore_logs;
104 }
105 let batch_size = config
106 .v2_state_tree_config
107 .as_ref()
108 .map(|config| config.output_queue_batch_size as usize);
109 let test_accounts = context.test_accounts.clone();
110 context.add_indexer(&test_accounts, batch_size).await?;
111
112 {
114 let address_mt = context.test_accounts.v1_address_trees[0].merkle_tree;
115 let address_queue_pubkey = context.test_accounts.v1_address_trees[0].queue;
116 let mut account = context
117 .context
118 .get_account(&keypairs.address_merkle_tree.pubkey())
119 .unwrap();
120 let merkle_tree_account = bytemuck::from_bytes_mut::<AddressMerkleTreeAccount>(
121 &mut account.data_as_mut_slice()[8..AddressMerkleTreeAccount::LEN],
122 );
123 merkle_tree_account.metadata.associated_queue = address_queue_pubkey.into();
124 context.set_account(address_mt, account);
125
126 let mut account = context
127 .context
128 .get_account(&keypairs.address_merkle_tree_queue.pubkey())
129 .unwrap();
130 let queue_account = bytemuck::from_bytes_mut::<QueueAccount>(
131 &mut account.data_as_mut_slice()[8..QueueAccount::LEN],
132 );
133 queue_account.metadata.associated_merkle_tree = address_mt.into();
134 context.set_account(address_queue_pubkey, account);
135 }
136 }
137 {
139 let tree_account = context
140 .context
141 .get_account(&keypairs.state_merkle_tree.pubkey());
142 let queue_account = context
143 .context
144 .get_account(&keypairs.nullifier_queue.pubkey());
145 let cpi_account = context
146 .context
147 .get_account(&keypairs.cpi_context_account.pubkey());
148
149 if let (Some(tree_acc), Some(queue_acc), Some(cpi_acc)) =
150 (tree_account, queue_account, cpi_account)
151 {
152 for i in 0..context.test_accounts.v1_state_trees.len() {
153 let state_mt = context.test_accounts.v1_state_trees[i].merkle_tree;
154 let nullifier_queue_pubkey =
155 context.test_accounts.v1_state_trees[i].nullifier_queue;
156 let cpi_context_pubkey =
157 context.test_accounts.v1_state_trees[i].cpi_context;
158
159 let mut tree_account_data = tree_acc.clone();
161 {
162 let merkle_tree_account = bytemuck::from_bytes_mut::<
163 account_compression::StateMerkleTreeAccount,
164 >(
165 &mut tree_account_data.data_as_mut_slice()
166 [8..account_compression::StateMerkleTreeAccount::LEN],
167 );
168 merkle_tree_account.metadata.associated_queue =
169 nullifier_queue_pubkey.into();
170 }
171 context.set_account(state_mt, tree_account_data);
172
173 let mut queue_account_data = queue_acc.clone();
175 {
176 let queue_account = bytemuck::from_bytes_mut::<QueueAccount>(
177 &mut queue_account_data.data_as_mut_slice()[8..QueueAccount::LEN],
178 );
179 queue_account.metadata.associated_merkle_tree = state_mt.into();
180 }
181 context.set_account(nullifier_queue_pubkey, queue_account_data);
182
183 let mut cpi_account_data = cpi_acc.clone();
185 {
186 let associated_merkle_tree_offset = 8 + 32; let associated_queue_offset = 8 + 32 + 32; cpi_account_data.data_as_mut_slice()
189 [associated_merkle_tree_offset..associated_merkle_tree_offset + 32]
190 .copy_from_slice(&state_mt.to_bytes());
191 cpi_account_data.data_as_mut_slice()
192 [associated_queue_offset..associated_queue_offset + 32]
193 .copy_from_slice(&nullifier_queue_pubkey.to_bytes());
194 }
195 context.set_account(cpi_context_pubkey, cpi_account_data);
196 }
197 }
198 }
199 {
200 let address_mt = context.test_accounts.v2_address_trees[0];
201 let account = context
202 .context
203 .get_account(&keypairs.batch_address_merkle_tree.pubkey());
204 if let Some(account) = account {
205 context.set_account(address_mt, account);
206 }
207 }
208 {
210 let tree_account = context
211 .context
212 .get_account(&keypairs.batched_state_merkle_tree.pubkey());
213 let queue_account = context
214 .context
215 .get_account(&keypairs.batched_output_queue.pubkey());
216 let cpi_account = context
217 .context
218 .get_account(&keypairs.batched_cpi_context.pubkey());
219
220 if let (Some(tree_acc), Some(queue_acc), Some(cpi_acc)) =
221 (tree_account, queue_account, cpi_account)
222 {
223 use light_batched_merkle_tree::{
224 merkle_tree::BatchedMerkleTreeAccount, queue::BatchedQueueAccount,
225 };
226
227 for i in 0..context.test_accounts.v2_state_trees.len() {
228 let merkle_tree_pubkey =
229 context.test_accounts.v2_state_trees[i].merkle_tree;
230 let output_queue_pubkey =
231 context.test_accounts.v2_state_trees[i].output_queue;
232 let cpi_context_pubkey =
233 context.test_accounts.v2_state_trees[i].cpi_context;
234
235 let mut tree_account_data = tree_acc.clone();
237 {
238 let mut tree = BatchedMerkleTreeAccount::state_from_bytes(
239 tree_account_data.data_as_mut_slice(),
240 &merkle_tree_pubkey.into(),
241 )
242 .unwrap();
243 let metadata = tree.get_metadata_mut();
244 metadata.metadata.associated_queue = output_queue_pubkey.into();
245 metadata.hashed_pubkey =
246 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
247 }
248 context.set_account(merkle_tree_pubkey, tree_account_data);
249
250 let mut queue_account_data = queue_acc.clone();
252 {
253 let mut queue = BatchedQueueAccount::output_from_bytes(
254 queue_account_data.data_as_mut_slice(),
255 )
256 .unwrap();
257 let metadata = queue.get_metadata_mut();
258 metadata.metadata.associated_merkle_tree = merkle_tree_pubkey.into();
259 metadata.hashed_merkle_tree_pubkey =
260 hash_to_bn254_field_size_be(&merkle_tree_pubkey.to_bytes());
261 metadata.hashed_queue_pubkey =
262 hash_to_bn254_field_size_be(&output_queue_pubkey.to_bytes());
263 }
264 context.set_account(output_queue_pubkey, queue_account_data);
265
266 let mut cpi_account_data = cpi_acc.clone();
268 {
269 let associated_merkle_tree_offset = 8 + 32; let associated_queue_offset = 8 + 32 + 32; cpi_account_data.data_as_mut_slice()
272 [associated_merkle_tree_offset..associated_merkle_tree_offset + 32]
273 .copy_from_slice(&merkle_tree_pubkey.to_bytes());
274 cpi_account_data.data_as_mut_slice()
275 [associated_queue_offset..associated_queue_offset + 32]
276 .copy_from_slice(&output_queue_pubkey.to_bytes());
277 }
278 context.set_account(cpi_context_pubkey, cpi_account_data);
279 }
280 }
281 }
282 }
283
284 #[cfg(not(feature = "devenv"))]
285 {
286 use crate::utils::load_accounts::load_all_accounts_from_dir;
288
289 let accounts = load_all_accounts_from_dir()?;
290
291 const BATCH_SIZE_OFFSET: usize = 240;
294 let mut batch_sizes = Vec::new();
295
296 for v2_tree in &context.test_accounts.v2_state_trees {
297 if let Some(queue_account) = accounts.get(&v2_tree.output_queue) {
298 if queue_account.data.len() >= BATCH_SIZE_OFFSET + 8 {
299 let bytes: [u8; 8] = queue_account.data
300 [BATCH_SIZE_OFFSET..BATCH_SIZE_OFFSET + 8]
301 .try_into()
302 .map_err(|_| {
303 RpcError::CustomError("Failed to read batch_size bytes".to_string())
304 })?;
305 batch_sizes.push(u64::from_le_bytes(bytes) as usize);
306 }
307 }
308 }
309
310 if !batch_sizes.is_empty() && !batch_sizes.windows(2).all(|w| w[0] == w[1]) {
312 return Err(RpcError::CustomError(format!(
313 "Inconsistent batch_sizes found across output queues: {:?}",
314 batch_sizes
315 )));
316 }
317
318 let batch_size = batch_sizes.first().copied().unwrap_or(0);
319
320 for (pubkey, account) in accounts {
321 context.context.set_account(pubkey, account).map_err(|e| {
322 RpcError::CustomError(format!("Failed to set account {}: {}", pubkey, e))
323 })?;
324 }
325
326 let test_accounts = context.test_accounts.clone();
328 context
329 .add_indexer(&test_accounts, Some(batch_size))
330 .await?;
331 }
332
333 context.transaction_counter = 0;
335
336 #[cfg(feature = "devenv")]
337 {
338 spawn_prover().await;
339 }
340 #[cfg(not(feature = "devenv"))]
341 if config.with_prover {
342 spawn_prover().await;
343 }
344
345 Ok(context)
346 }
347
348 pub fn indexer(&self) -> Result<&TestIndexer, RpcError> {
349 self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
350 }
351
352 pub fn indexer_mut(&mut self) -> Result<&mut TestIndexer, RpcError> {
353 self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
354 }
355
356 pub fn test_accounts(&self) -> &TestAccounts {
357 &self.test_accounts
358 }
359
360 pub fn get_state_merkle_tree_account(&self) -> StateMerkleTreeAccounts {
362 self.test_accounts.v1_state_trees[0]
363 }
364
365 pub fn get_address_merkle_tree(&self) -> AddressMerkleTreeAccounts {
366 self.test_accounts.v1_address_trees[0]
367 }
368
369 pub async fn add_indexer(
370 &mut self,
371 test_accounts: &TestAccounts,
372 batch_size: Option<usize>,
373 ) -> Result<(), RpcError> {
374 let indexer = TestIndexer::init_from_acounts(
375 &self.payer,
376 test_accounts,
377 batch_size.unwrap_or_default(),
378 )
379 .await;
380 self.indexer = Some(indexer);
381 Ok(())
382 }
383
384 pub fn clone_indexer(&self) -> Result<TestIndexer, RpcError> {
385 Ok((*self
386 .indexer
387 .as_ref()
388 .ok_or(RpcError::IndexerNotInitialized)?)
389 .clone())
390 }
391}
392
393impl MerkleTreeExt for LightProgramTest {}
394
395impl Debug for LightProgramTest {
396 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
397 f.debug_struct("LightProgramTest")
398 .field("context", &"ProgramTestContext")
399 .field("indexer", &self.indexer)
400 .field("test_accounts", &self.test_accounts)
401 .finish()
402 }
403}