1use std::{fmt::Debug, marker::Send};
2
3use anchor_lang::pubkey;
4use async_trait::async_trait;
5use borsh::BorshDeserialize;
6use light_client::{
7 indexer::{Indexer, TreeInfo},
8 rpc::{LightClientConfig, Rpc, RpcError},
9};
10use light_compressed_account::TreeType;
11use light_event::{
12 error::ParseIndexerEventError,
13 event::{BatchPublicTransactionEvent, PublicTransactionEvent},
14 parse::event_from_light_transaction,
15};
16use solana_rpc_client_api::config::RpcSendTransactionConfig;
17use solana_sdk::{
18 account::Account,
19 address_lookup_table::AddressLookupTableAccount,
20 clock::{Clock, Slot},
21 hash::Hash,
22 instruction::Instruction,
23 pubkey::Pubkey,
24 rent::Rent,
25 signature::{Keypair, Signature},
26 transaction::Transaction,
27};
28use solana_transaction_status_client_types::TransactionStatus;
29
30use crate::{
31 indexer::{TestIndexer, TestIndexerExtensions},
32 litesvm_extensions::LiteSvmExtensions,
33 program_test::LightProgramTest,
34};
35
36#[async_trait]
37impl Rpc for LightProgramTest {
38 async fn new(_config: LightClientConfig) -> Result<Self, RpcError>
39 where
40 Self: Sized,
41 {
42 Err(RpcError::CustomError(
43 "LightProgramTest::new is not supported in program-test context".into(),
44 ))
45 }
46
47 fn get_payer(&self) -> &Keypair {
48 &self.payer
49 }
50
51 fn get_url(&self) -> String {
52 "get_url doesn't make sense for LightProgramTest".to_string()
53 }
54
55 async fn health(&self) -> Result<(), RpcError> {
56 Ok(())
57 }
58
59 async fn get_program_accounts(
60 &self,
61 program_id: &Pubkey,
62 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
63 Ok(self.context.get_program_accounts(program_id))
64 }
65
66 async fn get_program_accounts_with_discriminator(
67 &self,
68 program_id: &Pubkey,
69 discriminator: &[u8],
70 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
71 let all_accounts = self.context.get_program_accounts(program_id);
72 Ok(all_accounts
73 .into_iter()
74 .filter(|(_, account)| {
75 account.data.len() >= discriminator.len()
76 && &account.data[..discriminator.len()] == discriminator
77 })
78 .collect())
79 }
80
81 async fn confirm_transaction(&self, _transaction: Signature) -> Result<bool, RpcError> {
82 Ok(true)
83 }
84
85 async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
86 Ok(self.context.get_account(&address))
87 }
88
89 async fn get_multiple_accounts(
90 &self,
91 addresses: &[Pubkey],
92 ) -> Result<Vec<Option<Account>>, RpcError> {
93 Ok(addresses
94 .iter()
95 .map(|address| self.context.get_account(address))
96 .collect())
97 }
98
99 async fn get_minimum_balance_for_rent_exemption(
100 &self,
101 data_len: usize,
102 ) -> Result<u64, RpcError> {
103 let rent = self.context.get_sysvar::<Rent>();
104
105 Ok(rent.minimum_balance(data_len))
106 }
107
108 async fn airdrop_lamports(
109 &mut self,
110 to: &Pubkey,
111 lamports: u64,
112 ) -> Result<Signature, RpcError> {
113 let res = self.context.airdrop(to, lamports).map_err(|e| e.err)?;
114 Ok(res.signature)
115 }
116
117 async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
118 Ok(self.context.get_balance(pubkey).unwrap())
119 }
120
121 async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
122 let slot = self.get_slot().await?;
123 let hash = self.context.latest_blockhash();
124 Ok((hash, slot))
125 }
126
127 async fn get_slot(&self) -> Result<u64, RpcError> {
128 Ok(self.context.get_sysvar::<Clock>().slot)
129 }
130
131 async fn get_transaction_slot(&self, _signature: &Signature) -> Result<u64, RpcError> {
132 unimplemented!();
133 }
134
135 async fn get_signature_statuses(
136 &self,
137 _signatures: &[Signature],
138 ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
139 Err(RpcError::CustomError(
140 "get_signature_statuses is unimplemented for LightProgramTest".to_string(),
141 ))
142 }
143
144 async fn send_transaction(&self, _transaction: &Transaction) -> Result<Signature, RpcError> {
145 Err(RpcError::CustomError(
146 "send_transaction is unimplemented for ProgramTestConnection".to_string(),
147 ))
148 }
149
150 async fn send_transaction_with_config(
151 &self,
152 _transaction: &Transaction,
153 _config: RpcSendTransactionConfig,
154 ) -> Result<Signature, RpcError> {
155 Err(RpcError::CustomError(
156 "send_transaction_with_config is unimplemented for ProgramTestConnection".to_string(),
157 ))
158 }
159
160 async fn process_transaction(
161 &mut self,
162 transaction: Transaction,
163 ) -> Result<Signature, RpcError> {
164 let sig = *transaction.signatures.first().unwrap();
165 if self.indexer.is_some() {
166 self._send_transaction_with_batched_event(transaction)
168 .await?;
169 } else {
170 let pre_context_snapshot = self.context.clone();
172
173 self.transaction_counter += 1;
175 let _res = self.context.send_transaction(transaction).map_err(|x| {
176 if self.config.log_failed_tx {
177 println!("{}", x.meta.pretty_logs());
178 }
179
180 RpcError::TransactionError(x.err)
181 })?;
182
183 self.maybe_print_logs(_res.pretty_logs());
184
185 self.pre_context = Some(pre_context_snapshot);
187 }
188 Ok(sig)
189 }
190
191 async fn process_transaction_with_context(
192 &mut self,
193 transaction: Transaction,
194 ) -> Result<(Signature, Slot), RpcError> {
195 let sig = *transaction.signatures.first().unwrap();
196
197 let pre_context_snapshot = self.context.clone();
199
200 self.transaction_counter += 1;
201 let _res = self.context.send_transaction(transaction).map_err(|x| {
202 if self.config.log_failed_tx {
203 println!("{}", x.meta.pretty_logs());
204 }
205 RpcError::TransactionError(x.err)
206 })?;
207
208 let slot = self.context.get_sysvar::<Clock>().slot;
209 self.maybe_print_logs(_res.pretty_logs());
210
211 self.pre_context = Some(pre_context_snapshot);
213
214 Ok((sig, slot))
215 }
216
217 async fn create_and_send_transaction_with_event<T>(
218 &mut self,
219 instructions: &[Instruction],
220 payer: &Pubkey,
221 signers: &[&Keypair],
222 ) -> Result<Option<(T, Signature, u64)>, RpcError>
223 where
224 T: BorshDeserialize + Send + Debug,
225 {
226 self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
227 .await
228 }
229
230 async fn create_and_send_transaction_with_batched_event(
231 &mut self,
232 instructions: &[Instruction],
233 payer: &Pubkey,
234 signers: &[&Keypair],
235 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
236 self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
237 .await
238 }
239
240 async fn create_and_send_transaction_with_public_event(
241 &mut self,
242 instruction: &[Instruction],
243 payer: &Pubkey,
244 signers: &[&Keypair],
245 ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
246 let event = self
247 ._create_and_send_transaction_with_batched_event(instruction, payer, signers)
248 .await?;
249 let event = event.map(|e| (e.0[0].event.clone(), e.1, e.2));
250
251 Ok(event)
252 }
253
254 fn indexer(&self) -> Result<&impl Indexer, RpcError> {
255 self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
256 }
257
258 fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
259 self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
260 }
261
262 async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
264 #[cfg(not(feature = "v2"))]
265 return Ok(self
266 .test_accounts
267 .v1_state_trees
268 .iter()
269 .copied()
270 .map(|tree| tree.into())
271 .collect());
272 #[cfg(feature = "v2")]
273 return Ok(self
274 .test_accounts
275 .v2_state_trees
276 .iter()
277 .map(|tree| (*tree).into())
278 .collect());
279 }
280
281 fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
283 #[cfg(not(feature = "v2"))]
284 return self
285 .test_accounts
286 .v1_state_trees
287 .iter()
288 .copied()
289 .map(|tree| tree.into())
290 .collect();
291 #[cfg(feature = "v2")]
292 return self
293 .test_accounts
294 .v2_state_trees
295 .iter()
296 .map(|tree| (*tree).into())
297 .collect();
298 }
299
300 fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
303 use rand::Rng;
304 let mut rng = rand::thread_rng();
305 #[cfg(not(feature = "v2"))]
306 {
307 if self.test_accounts.v1_state_trees.is_empty() {
308 return Err(RpcError::NoStateTreesAvailable);
309 }
310 Ok(self.test_accounts.v1_state_trees
311 [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
312 .into())
313 }
314 #[cfg(feature = "v2")]
315 {
316 if self.test_accounts.v2_state_trees.is_empty() {
317 return Err(RpcError::NoStateTreesAvailable);
318 }
319 Ok(self.test_accounts.v2_state_trees
320 [rng.gen_range(0..self.test_accounts.v2_state_trees.len())]
321 .into())
322 }
323 }
324
325 fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
328 use rand::Rng;
329 let mut rng = rand::thread_rng();
330 if self.test_accounts.v1_state_trees.is_empty() {
331 return Err(RpcError::NoStateTreesAvailable);
332 }
333 Ok(self.test_accounts.v1_state_trees
334 [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
335 .into())
336 }
337
338 fn get_address_tree_v1(&self) -> TreeInfo {
339 TreeInfo {
340 tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
341 queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
342 cpi_context: None,
343 next_tree_info: None,
344 tree_type: TreeType::AddressV1,
345 }
346 }
347
348 fn get_address_tree_v2(&self) -> TreeInfo {
349 TreeInfo {
350 tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
351 queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
352 cpi_context: None,
353 next_tree_info: None,
354 tree_type: TreeType::AddressV2,
355 }
356 }
357
358 async fn create_and_send_versioned_transaction<'a>(
359 &'a mut self,
360 _instructions: &'a [Instruction],
361 _payer: &'a Pubkey,
362 _signers: &'a [&'a Keypair],
363 _address_lookup_tables: &'a [AddressLookupTableAccount],
364 ) -> Result<Signature, RpcError> {
365 unimplemented!(
366 "create_and_send_versioned_transaction is unimplemented for LightProgramTest"
367 );
368 }
369}
370
371impl LightProgramTest {
372 fn maybe_print_logs(&self, logs: impl std::fmt::Display) {
373 if crate::logging::should_use_enhanced_logging(&self.config) {
375 return;
377 }
378
379 if !self.config.no_logs && cfg!(debug_assertions) && std::env::var("RUST_BACKTRACE").is_ok()
381 {
382 println!("{}", logs);
383 }
384 }
385
386 async fn _send_transaction_with_batched_event(
387 &mut self,
388 transaction: Transaction,
389 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
390 let mut vec = Vec::new();
391
392 let signature = transaction.signatures[0];
393 let transaction_for_logging = transaction.clone(); let pre_states = crate::logging::capture_account_states(&self.context, &transaction);
397 let pre_context_snapshot = self.context.clone();
399
400 let simulation_result = self.context.simulate_transaction(transaction.clone());
404
405 self.transaction_counter += 1;
407 let transaction_result = self.context.send_transaction(transaction.clone());
408 let slot = self.context.get_sysvar::<Clock>().slot;
409
410 let post_states =
412 crate::logging::capture_account_states(&self.context, &transaction_for_logging);
413
414 if crate::logging::should_use_enhanced_logging(&self.config) {
416 crate::logging::log_transaction_enhanced(
417 &self.config,
418 &transaction_for_logging,
419 &transaction_result,
420 &signature,
421 slot,
422 self.transaction_counter,
423 Some(&pre_states),
424 Some(&post_states),
425 );
426 }
427
428 let _res = transaction_result.as_ref().map_err(|x| {
430 if self.config.log_failed_tx {
432 crate::logging::log_transaction_enhanced_with_console(
433 &self.config,
434 &transaction_for_logging,
435 &transaction_result,
436 &signature,
437 slot,
438 self.transaction_counter,
439 true, Some(&pre_states),
441 Some(&post_states),
442 );
443 }
444 RpcError::TransactionError(x.err.clone())
445 })?;
446
447 if !self.config.no_logs && std::env::var("RUST_BACKTRACE").is_ok() {
449 if crate::logging::should_use_enhanced_logging(&self.config) {
450 crate::logging::log_transaction_enhanced_with_console(
452 &self.config,
453 &transaction_for_logging,
454 &transaction_result,
455 &signature,
456 slot,
457 self.transaction_counter,
458 true, Some(&pre_states),
460 Some(&post_states),
461 );
462
463 } else {
469 self.maybe_print_logs(_res.pretty_logs());
471 }
472 }
473
474 let simulation_result = simulation_result.unwrap();
475 let event = simulation_result
477 .meta
478 .inner_instructions
479 .iter()
480 .flatten()
481 .find_map(|inner_instruction| {
482 PublicTransactionEvent::try_from_slice(&inner_instruction.instruction.data).ok()
483 });
484 let event = if let Some(event) = event {
485 Some(vec![BatchPublicTransactionEvent {
486 event,
487 ..Default::default()
488 }])
489 } else {
490 let mut vec_accounts = Vec::<Vec<Pubkey>>::new();
492 let mut program_ids = Vec::new();
493
494 transaction.message.instructions.iter().for_each(|i| {
495 program_ids.push(transaction.message.account_keys[i.program_id_index as usize]);
496 vec.push(i.data.clone());
497 vec_accounts.push(
498 i.accounts
499 .iter()
500 .map(|x| transaction.message.account_keys[*x as usize])
501 .collect(),
502 );
503 });
504 simulation_result
505 .meta
506 .inner_instructions
507 .iter()
508 .flatten()
509 .find_map(|inner_instruction| {
510 vec.push(inner_instruction.instruction.data.clone());
511 program_ids.push(
512 transaction.message.account_keys
513 [inner_instruction.instruction.program_id_index as usize],
514 );
515 vec_accounts.push(
516 inner_instruction
517 .instruction
518 .accounts
519 .iter()
520 .map(|x| transaction.message.account_keys[*x as usize])
521 .collect(),
522 );
523 None::<PublicTransactionEvent>
524 });
525
526 event_from_light_transaction(
527 &program_ids.iter().map(|x| (*x).into()).collect::<Vec<_>>(),
528 vec.as_slice(),
529 vec_accounts
530 .iter()
531 .map(|inner_vec| inner_vec.iter().map(|x| (*x).into()).collect())
532 .collect(),
533 )
534 .or(Ok::<
535 Option<Vec<BatchPublicTransactionEvent>>,
536 ParseIndexerEventError,
537 >(None))?
538 };
539 if self.config.log_light_protocol_events {
540 println!("event:\n {:?}", event);
541 }
542 let event = event.map(|e| (e, signature, slot));
543
544 if let Some(indexer) = self.indexer.as_mut() {
545 if let Some(events) = event.as_ref() {
546 for event in events.0.iter() {
547 <TestIndexer as TestIndexerExtensions>::add_compressed_accounts_with_token_data(
548 indexer,
549 slot,
550 &event.event,
551 );
552 }
553 }
554 }
555
556 self.pre_context = Some(pre_context_snapshot);
558
559 Ok(event)
560 }
561
562 async fn _create_and_send_transaction_with_event<T>(
563 &mut self,
564 instruction: &[Instruction],
565 payer: &Pubkey,
566 signers: &[&Keypair],
567 ) -> Result<Option<(T, Signature, Slot)>, RpcError>
568 where
569 T: BorshDeserialize + Send + Debug,
570 {
571 let transaction = Transaction::new_signed_with_payer(
572 instruction,
573 Some(payer),
574 signers,
575 self.context.latest_blockhash(),
576 );
577
578 let signature = transaction.signatures[0];
579
580 let pre_context_snapshot = self.context.clone();
582
583 let simulation_result = self
587 .context
588 .simulate_transaction(transaction.clone())
589 .map_err(|x| RpcError::from(x.err))?;
590
591 let event = simulation_result
592 .meta
593 .inner_instructions
594 .iter()
595 .flatten()
596 .find_map(|inner_instruction| {
597 T::try_from_slice(&inner_instruction.instruction.data).ok()
598 });
599 self.transaction_counter += 1;
601 let _res = self.context.send_transaction(transaction).map_err(|x| {
602 if self.config.log_failed_tx {
603 println!("{}", x.meta.pretty_logs());
604 }
605 RpcError::TransactionError(x.err)
606 })?;
607 self.maybe_print_logs(_res.pretty_logs());
608
609 self.pre_context = Some(pre_context_snapshot);
611
612 let slot = self.get_slot().await?;
613 let result = event.map(|event| (event, signature, slot));
614 Ok(result)
615 }
616
617 async fn _create_and_send_transaction_with_batched_event(
618 &mut self,
619 instruction: &[Instruction],
620 payer: &Pubkey,
621 signers: &[&Keypair],
622 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
623 let transaction = Transaction::new_signed_with_payer(
624 instruction,
625 Some(payer),
626 signers,
627 self.context.latest_blockhash(),
628 );
629
630 self._send_transaction_with_batched_event(transaction).await
631 }
632}