1use std::{fmt::Debug, marker::Send};
2
3use anchor_lang::pubkey;
4use async_trait::async_trait;
5use borsh::BorshDeserialize;
6use light_client::{
7 indexer::{CompressedAccount, CompressedTokenAccount, Context, Indexer, Response, TreeInfo},
8 interface::{AccountInterface, MintInterface, MintState, TokenAccountInterface},
9 rpc::{LightClientConfig, Rpc, RpcError},
10};
11use light_compressed_account::TreeType;
12use light_event::{
13 error::ParseIndexerEventError,
14 event::{BatchPublicTransactionEvent, PublicTransactionEvent},
15 parse::event_from_light_transaction,
16};
17use solana_rpc_client_api::config::RpcSendTransactionConfig;
18use solana_sdk::{
19 account::Account,
20 address_lookup_table::AddressLookupTableAccount,
21 clock::{Clock, Slot},
22 hash::Hash,
23 instruction::Instruction,
24 pubkey::Pubkey,
25 rent::Rent,
26 signature::{Keypair, Signature},
27 transaction::Transaction,
28};
29use solana_transaction_status_client_types::TransactionStatus;
30
31use crate::{
32 indexer::{TestIndexer, TestIndexerExtensions},
33 litesvm_extensions::LiteSvmExtensions,
34 program_test::LightProgramTest,
35};
36
37#[async_trait]
38impl Rpc for LightProgramTest {
39 async fn new(_config: LightClientConfig) -> Result<Self, RpcError>
40 where
41 Self: Sized,
42 {
43 Err(RpcError::CustomError(
44 "LightProgramTest::new is not supported in program-test context".into(),
45 ))
46 }
47
48 fn get_payer(&self) -> &Keypair {
49 &self.payer
50 }
51
52 fn get_url(&self) -> String {
53 "get_url doesn't make sense for LightProgramTest".to_string()
54 }
55
56 async fn health(&self) -> Result<(), RpcError> {
57 Ok(())
58 }
59
60 async fn get_program_accounts(
61 &self,
62 program_id: &Pubkey,
63 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
64 Ok(self.context.get_program_accounts(program_id))
65 }
66
67 async fn get_program_accounts_with_discriminator(
68 &self,
69 program_id: &Pubkey,
70 discriminator: &[u8],
71 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
72 let all_accounts = self.context.get_program_accounts(program_id);
73 Ok(all_accounts
74 .into_iter()
75 .filter(|(_, account)| {
76 account.data.len() >= discriminator.len()
77 && &account.data[..discriminator.len()] == discriminator
78 })
79 .collect())
80 }
81
82 async fn confirm_transaction(&self, _transaction: Signature) -> Result<bool, RpcError> {
83 Ok(true)
84 }
85
86 async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
87 Ok(self.context.get_account(&address))
88 }
89
90 async fn get_multiple_accounts(
91 &self,
92 addresses: &[Pubkey],
93 ) -> Result<Vec<Option<Account>>, RpcError> {
94 Ok(addresses
95 .iter()
96 .map(|address| self.context.get_account(address))
97 .collect())
98 }
99
100 async fn get_minimum_balance_for_rent_exemption(
101 &self,
102 data_len: usize,
103 ) -> Result<u64, RpcError> {
104 let rent = self.context.get_sysvar::<Rent>();
105
106 Ok(rent.minimum_balance(data_len))
107 }
108
109 async fn airdrop_lamports(
110 &mut self,
111 to: &Pubkey,
112 lamports: u64,
113 ) -> Result<Signature, RpcError> {
114 let res = self.context.airdrop(to, lamports).map_err(|e| e.err)?;
115 Ok(res.signature)
116 }
117
118 async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
119 Ok(self.context.get_balance(pubkey).unwrap())
120 }
121
122 async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
123 let slot = self.get_slot().await?;
124 let hash = self.context.latest_blockhash();
125 Ok((hash, slot))
126 }
127
128 async fn get_slot(&self) -> Result<u64, RpcError> {
129 Ok(self.context.get_sysvar::<Clock>().slot)
130 }
131
132 async fn get_transaction_slot(&self, _signature: &Signature) -> Result<u64, RpcError> {
133 unimplemented!();
134 }
135
136 async fn get_signature_statuses(
137 &self,
138 _signatures: &[Signature],
139 ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
140 Err(RpcError::CustomError(
141 "get_signature_statuses is unimplemented for LightProgramTest".to_string(),
142 ))
143 }
144
145 async fn send_transaction(&self, _transaction: &Transaction) -> Result<Signature, RpcError> {
146 Err(RpcError::CustomError(
147 "send_transaction is unimplemented for ProgramTestConnection".to_string(),
148 ))
149 }
150
151 async fn send_transaction_with_config(
152 &self,
153 _transaction: &Transaction,
154 _config: RpcSendTransactionConfig,
155 ) -> Result<Signature, RpcError> {
156 Err(RpcError::CustomError(
157 "send_transaction_with_config is unimplemented for ProgramTestConnection".to_string(),
158 ))
159 }
160
161 async fn process_transaction(
162 &mut self,
163 transaction: Transaction,
164 ) -> Result<Signature, RpcError> {
165 let sig = *transaction.signatures.first().unwrap();
166 if self.indexer.is_some() {
167 self._send_transaction_with_batched_event(transaction)
169 .await?;
170 } else {
171 let pre_context_snapshot = self.context.clone();
173
174 self.transaction_counter += 1;
176 let _res = self.context.send_transaction(transaction).map_err(|x| {
177 if self.config.log_failed_tx {
178 println!("{}", x.meta.pretty_logs());
179 }
180
181 RpcError::TransactionError(x.err)
182 })?;
183
184 self.maybe_print_logs(_res.pretty_logs());
185
186 self.pre_context = Some(pre_context_snapshot);
188 }
189 Ok(sig)
190 }
191
192 async fn process_transaction_with_context(
193 &mut self,
194 transaction: Transaction,
195 ) -> Result<(Signature, Slot), RpcError> {
196 let sig = *transaction.signatures.first().unwrap();
197
198 let pre_context_snapshot = self.context.clone();
200
201 self.transaction_counter += 1;
202 let _res = self.context.send_transaction(transaction).map_err(|x| {
203 if self.config.log_failed_tx {
204 println!("{}", x.meta.pretty_logs());
205 }
206 RpcError::TransactionError(x.err)
207 })?;
208
209 let slot = self.context.get_sysvar::<Clock>().slot;
210 self.maybe_print_logs(_res.pretty_logs());
211
212 self.pre_context = Some(pre_context_snapshot);
214
215 Ok((sig, slot))
216 }
217
218 async fn create_and_send_transaction_with_event<T>(
219 &mut self,
220 instructions: &[Instruction],
221 payer: &Pubkey,
222 signers: &[&Keypair],
223 ) -> Result<Option<(T, Signature, u64)>, RpcError>
224 where
225 T: BorshDeserialize + Send + Debug,
226 {
227 self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
228 .await
229 }
230
231 async fn create_and_send_transaction_with_batched_event(
232 &mut self,
233 instructions: &[Instruction],
234 payer: &Pubkey,
235 signers: &[&Keypair],
236 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
237 self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
238 .await
239 }
240
241 async fn create_and_send_transaction_with_public_event(
242 &mut self,
243 instruction: &[Instruction],
244 payer: &Pubkey,
245 signers: &[&Keypair],
246 ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
247 let event = self
248 ._create_and_send_transaction_with_batched_event(instruction, payer, signers)
249 .await?;
250 let event = event.map(|e| (e.0[0].event.clone(), e.1, e.2));
251
252 Ok(event)
253 }
254
255 fn indexer(&self) -> Result<&impl Indexer, RpcError> {
256 self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
257 }
258
259 fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
260 self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
261 }
262
263 async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
265 #[cfg(not(feature = "v2"))]
266 return Ok(self
267 .test_accounts
268 .v1_state_trees
269 .iter()
270 .copied()
271 .map(|tree| tree.into())
272 .collect());
273 #[cfg(feature = "v2")]
274 return Ok(self
275 .test_accounts
276 .v2_state_trees
277 .iter()
278 .map(|tree| (*tree).into())
279 .collect());
280 }
281
282 fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
284 #[cfg(not(feature = "v2"))]
285 return self
286 .test_accounts
287 .v1_state_trees
288 .iter()
289 .copied()
290 .map(|tree| tree.into())
291 .collect();
292 #[cfg(feature = "v2")]
293 return self
294 .test_accounts
295 .v2_state_trees
296 .iter()
297 .map(|tree| (*tree).into())
298 .collect();
299 }
300
301 fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
304 use rand::Rng;
305 let mut rng = rand::thread_rng();
306 #[cfg(not(feature = "v2"))]
307 {
308 if self.test_accounts.v1_state_trees.is_empty() {
309 return Err(RpcError::NoStateTreesAvailable);
310 }
311 Ok(self.test_accounts.v1_state_trees
312 [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
313 .into())
314 }
315 #[cfg(feature = "v2")]
316 {
317 if self.test_accounts.v2_state_trees.is_empty() {
318 return Err(RpcError::NoStateTreesAvailable);
319 }
320 Ok(self.test_accounts.v2_state_trees
321 [rng.gen_range(0..self.test_accounts.v2_state_trees.len())]
322 .into())
323 }
324 }
325
326 fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
329 use rand::Rng;
330 let mut rng = rand::thread_rng();
331 if self.test_accounts.v1_state_trees.is_empty() {
332 return Err(RpcError::NoStateTreesAvailable);
333 }
334 Ok(self.test_accounts.v1_state_trees
335 [rng.gen_range(0..self.test_accounts.v1_state_trees.len())]
336 .into())
337 }
338
339 fn get_address_tree_v1(&self) -> TreeInfo {
340 TreeInfo {
341 tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
342 queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
343 cpi_context: None,
344 next_tree_info: None,
345 tree_type: TreeType::AddressV1,
346 }
347 }
348
349 fn get_address_tree_v2(&self) -> TreeInfo {
350 TreeInfo {
351 tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
352 queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
353 cpi_context: None,
354 next_tree_info: None,
355 tree_type: TreeType::AddressV2,
356 }
357 }
358
359 async fn create_and_send_versioned_transaction<'a>(
360 &'a mut self,
361 _instructions: &'a [Instruction],
362 _payer: &'a Pubkey,
363 _signers: &'a [&'a Keypair],
364 _address_lookup_tables: &'a [AddressLookupTableAccount],
365 ) -> Result<Signature, RpcError> {
366 unimplemented!(
367 "create_and_send_versioned_transaction is unimplemented for LightProgramTest"
368 );
369 }
370
371 async fn get_account_interface(
372 &self,
373 address: &Pubkey,
374 _config: Option<light_client::indexer::IndexerRpcConfig>,
375 ) -> Result<Response<Option<AccountInterface>>, RpcError> {
376 let slot = self.context.get_sysvar::<Clock>().slot;
377
378 if let Some(account) = self.context.get_account(address) {
380 if account.lamports > 0 {
381 return Ok(Response {
382 context: Context { slot },
383 value: Some(AccountInterface::hot(*address, account)),
384 });
385 }
386 }
387
388 if let Some(indexer) = self.indexer.as_ref() {
390 if let Some(compressed_with_ctx) =
392 indexer.find_compressed_account_by_onchain_pubkey(&address.to_bytes())
393 {
394 let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into();
395 let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err(
396 |e| {
397 RpcError::CustomError(format!(
398 "CompressedAccountWithMerkleContext conversion failed for address {}: {:?}",
399 address, e
400 ))
401 },
402 )?;
403
404 return Ok(Response {
405 context: Context { slot },
406 value: Some(AccountInterface::cold(*address, compressed, owner)),
407 });
408 }
409
410 if let Some(compressed_with_ctx) =
412 indexer.find_compressed_account_by_pda_seed(&address.to_bytes())
413 {
414 let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into();
415 let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err(
416 |e| {
417 RpcError::CustomError(format!(
418 "CompressedAccountWithMerkleContext conversion failed for PDA seed {}: {:?}",
419 address, e
420 ))
421 },
422 )?;
423
424 return Ok(Response {
425 context: Context { slot },
426 value: Some(AccountInterface::cold(*address, compressed, owner)),
427 });
428 }
429 }
430
431 Ok(Response {
432 context: Context { slot },
433 value: None,
434 })
435 }
436
437 async fn get_token_account_interface(
438 &self,
439 address: &Pubkey,
440 _config: Option<light_client::indexer::IndexerRpcConfig>,
441 ) -> Result<Response<Option<TokenAccountInterface>>, RpcError> {
442 use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID;
443
444 let light_token_program_id: Pubkey = LIGHT_TOKEN_PROGRAM_ID.into();
445 let slot = self.context.get_sysvar::<Clock>().slot;
446
447 if let Some(account) = self.context.get_account(address) {
449 if account.lamports > 0 && account.owner == light_token_program_id {
450 match TokenAccountInterface::hot(*address, account) {
451 Ok(iface) => {
452 return Ok(Response {
453 context: Context { slot },
454 value: Some(iface),
455 });
456 }
457 Err(_) => {
458 }
460 }
461 }
462 }
463
464 if let Some(indexer) = self.indexer.as_ref() {
466 let token_acc = indexer
468 .find_token_account_by_onchain_pubkey(&address.to_bytes())
469 .or_else(|| {
470 indexer.find_token_account_by_pda_seed(&address.to_bytes())
472 });
473
474 if let Some(token_acc) = token_acc {
475 let compressed_account: CompressedAccount = token_acc
477 .compressed_account
478 .clone()
479 .try_into()
480 .map_err(|e| RpcError::CustomError(format!("conversion error: {:?}", e)))?;
481
482 let compressed_token = CompressedTokenAccount {
483 token: token_acc.token_data.clone(),
484 account: compressed_account,
485 };
486
487 return Ok(Response {
488 context: Context { slot },
489 value: Some(TokenAccountInterface::cold(
490 *address,
491 compressed_token,
492 *address, light_token_program_id,
494 )),
495 });
496 }
497
498 let result = indexer
500 .get_compressed_token_accounts_by_owner(address, None, None)
501 .await
502 .map_err(|e| RpcError::CustomError(format!("indexer error: {}", e)))?;
503
504 let items = result.value.items;
505 if items.len() > 1 {
506 return Err(RpcError::CustomError(format!(
507 "Ambiguous lookup: found {} compressed token accounts for address {}. \
508 Use get_compressed_token_accounts_by_owner for multiple accounts.",
509 items.len(),
510 address
511 )));
512 }
513
514 if let Some(token_acc) = items.into_iter().next() {
515 let key = token_acc
516 .account
517 .address
518 .map(Pubkey::new_from_array)
519 .unwrap_or(*address);
520 return Ok(Response {
521 context: Context { slot },
522 value: Some(TokenAccountInterface::cold(
523 key,
524 token_acc,
525 *address, light_token_program_id,
527 )),
528 });
529 }
530 }
531
532 Ok(Response {
533 context: Context { slot },
534 value: None,
535 })
536 }
537
538 async fn get_associated_token_account_interface(
539 &self,
540 owner: &Pubkey,
541 mint: &Pubkey,
542 _config: Option<light_client::indexer::IndexerRpcConfig>,
543 ) -> Result<Response<Option<TokenAccountInterface>>, RpcError> {
544 use light_client::indexer::GetCompressedTokenAccountsByOwnerOrDelegateOptions;
545 use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID;
546 use light_token::instruction::derive_token_ata;
547
548 let ata = derive_token_ata(owner, mint);
549 let light_token_program_id: Pubkey = LIGHT_TOKEN_PROGRAM_ID.into();
550 let slot = self.context.get_sysvar::<Clock>().slot;
551
552 if let Some(account) = self.context.get_account(&ata) {
556 if account.lamports > 0 && account.owner == light_token_program_id {
557 match TokenAccountInterface::hot(ata, account) {
558 Ok(iface) => {
559 return Ok(Response {
560 context: Context { slot },
561 value: Some(iface),
562 });
563 }
564 Err(_) => {
565 }
567 }
568 }
569 }
570
571 if let Some(indexer) = self.indexer.as_ref() {
575 let options = Some(GetCompressedTokenAccountsByOwnerOrDelegateOptions {
576 mint: Some(*mint),
577 ..Default::default()
578 });
579 let result = indexer
580 .get_compressed_token_accounts_by_owner(&ata, options, None)
581 .await
582 .map_err(|e| RpcError::CustomError(format!("indexer error: {}", e)))?;
583
584 let items = result.value.items;
585 if items.len() > 1 {
586 return Err(RpcError::CustomError(format!(
587 "Ambiguous lookup: found {} compressed token accounts for ATA {} (owner: {}, mint: {}). \
588 Use get_compressed_token_accounts_by_owner for multiple accounts.",
589 items.len(),
590 ata,
591 owner,
592 mint
593 )));
594 }
595
596 if let Some(token_acc) = items.into_iter().next() {
597 return Ok(Response {
598 context: Context { slot },
599 value: Some(TokenAccountInterface::cold(
600 ata, token_acc,
602 *owner, light_token_program_id,
604 )),
605 });
606 }
607 }
608
609 Ok(Response {
610 context: Context { slot },
611 value: None,
612 })
613 }
614
615 async fn get_multiple_account_interfaces(
616 &self,
617 addresses: Vec<&Pubkey>,
618 _config: Option<light_client::indexer::IndexerRpcConfig>,
619 ) -> Result<Response<Vec<Option<AccountInterface>>>, RpcError> {
620 let slot = self.context.get_sysvar::<Clock>().slot;
621 let mut results: Vec<Option<AccountInterface>> = vec![None; addresses.len()];
622
623 let owned_addresses: Vec<Pubkey> = addresses.iter().map(|a| **a).collect();
625 let on_chain_accounts: Vec<Option<Account>> = owned_addresses
626 .iter()
627 .map(|addr| self.context.get_account(addr))
628 .collect();
629
630 let mut cold_lookup_indices: Vec<usize> = Vec::new();
632 let mut cold_lookup_pubkeys: Vec<[u8; 32]> = Vec::new();
633
634 for (i, (address, maybe_account)) in addresses
635 .iter()
636 .zip(on_chain_accounts.into_iter())
637 .enumerate()
638 {
639 if let Some(account) = maybe_account {
640 if account.lamports > 0 {
641 results[i] = Some(AccountInterface::hot(**address, account));
642 continue;
643 }
644 }
645 cold_lookup_indices.push(i);
647 cold_lookup_pubkeys.push(address.to_bytes());
648 }
649
650 if !cold_lookup_pubkeys.is_empty() {
652 if let Some(indexer) = self.indexer.as_ref() {
653 let cold_results = indexer
654 .find_multiple_compressed_accounts_by_onchain_pubkeys(&cold_lookup_pubkeys);
655
656 for (lookup_idx, maybe_compressed) in cold_results.into_iter().enumerate() {
657 let original_idx = cold_lookup_indices[lookup_idx];
658 if let Some(compressed_with_ctx) = maybe_compressed {
659 let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into();
660 let compressed: CompressedAccount =
661 compressed_with_ctx.clone().try_into().map_err(|e| {
662 RpcError::CustomError(format!("conversion error: {:?}", e))
663 })?;
664
665 results[original_idx] = Some(AccountInterface::cold(
666 *addresses[original_idx],
667 compressed,
668 owner,
669 ));
670 }
671 }
672 }
673 }
674
675 Ok(Response {
676 context: Context { slot },
677 value: results,
678 })
679 }
680
681 async fn get_mint_interface(
682 &self,
683 address: &Pubkey,
684 config: Option<light_client::indexer::IndexerRpcConfig>,
685 ) -> Result<Response<Option<MintInterface>>, RpcError> {
686 use borsh::BorshDeserialize;
687 use light_compressed_account::address::derive_address;
688 use light_token_interface::{state::Mint, MINT_ADDRESS_TREE};
689
690 let slot = self.context.get_sysvar::<Clock>().slot;
691 let address_tree = Pubkey::new_from_array(MINT_ADDRESS_TREE);
692 let compressed_address = derive_address(
693 &address.to_bytes(),
694 &address_tree.to_bytes(),
695 &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
696 );
697
698 if let Some(account) = self.context.get_account(address) {
700 if account.lamports > 0 {
701 return Ok(Response {
702 context: Context { slot },
703 value: Some(MintInterface {
704 mint: *address,
705 address_tree,
706 compressed_address,
707 state: MintState::Hot { account },
708 }),
709 });
710 }
711 }
712
713 let indexer = self
715 .indexer
716 .as_ref()
717 .ok_or_else(|| RpcError::CustomError("Indexer not initialized".to_string()))?;
718
719 let resp = indexer
720 .get_compressed_account(compressed_address, config)
721 .await
722 .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?;
723
724 let value = match resp.value {
725 Some(compressed) => {
726 let mint_data = compressed
728 .data
729 .as_ref()
730 .and_then(|d| {
731 if d.data.is_empty() {
732 None
733 } else {
734 Mint::try_from_slice(&d.data).ok()
735 }
736 })
737 .ok_or_else(|| {
738 RpcError::CustomError(
739 "Missing or invalid mint data in compressed account".into(),
740 )
741 })?;
742
743 Some(MintInterface {
744 mint: *address,
745 address_tree,
746 compressed_address,
747 state: MintState::Cold {
748 compressed,
749 mint_data,
750 },
751 })
752 }
753 None => None,
754 };
755
756 Ok(Response {
757 context: Context { slot },
758 value,
759 })
760 }
761}
762
763impl LightProgramTest {
764 fn maybe_print_logs(&self, logs: impl std::fmt::Display) {
765 if crate::logging::should_use_enhanced_logging(&self.config) {
767 return;
769 }
770
771 if !self.config.no_logs && cfg!(debug_assertions) && std::env::var("RUST_BACKTRACE").is_ok()
773 {
774 println!("{}", logs);
775 }
776 }
777
778 async fn _send_transaction_with_batched_event(
779 &mut self,
780 transaction: Transaction,
781 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
782 let mut vec = Vec::new();
783
784 let signature = transaction.signatures[0];
785 let transaction_for_logging = transaction.clone(); let pre_states = crate::logging::capture_account_states(&self.context, &transaction);
789 let pre_context_snapshot = self.context.clone();
791
792 let simulation_result = self.context.simulate_transaction(transaction.clone());
796
797 self.transaction_counter += 1;
799 let transaction_result = self.context.send_transaction(transaction.clone());
800 let slot = self.context.get_sysvar::<Clock>().slot;
801
802 let post_states =
804 crate::logging::capture_account_states(&self.context, &transaction_for_logging);
805
806 if crate::logging::should_use_enhanced_logging(&self.config) {
808 crate::logging::log_transaction_enhanced(
809 &self.config,
810 &transaction_for_logging,
811 &transaction_result,
812 &signature,
813 slot,
814 self.transaction_counter,
815 Some(&pre_states),
816 Some(&post_states),
817 );
818 }
819
820 let _res = transaction_result.as_ref().map_err(|x| {
822 if self.config.log_failed_tx {
824 crate::logging::log_transaction_enhanced_with_console(
825 &self.config,
826 &transaction_for_logging,
827 &transaction_result,
828 &signature,
829 slot,
830 self.transaction_counter,
831 true, Some(&pre_states),
833 Some(&post_states),
834 );
835 }
836 RpcError::TransactionError(x.err.clone())
837 })?;
838
839 if !self.config.no_logs && std::env::var("RUST_BACKTRACE").is_ok() {
841 if crate::logging::should_use_enhanced_logging(&self.config) {
842 crate::logging::log_transaction_enhanced_with_console(
844 &self.config,
845 &transaction_for_logging,
846 &transaction_result,
847 &signature,
848 slot,
849 self.transaction_counter,
850 true, Some(&pre_states),
852 Some(&post_states),
853 );
854
855 } else {
861 self.maybe_print_logs(_res.pretty_logs());
863 }
864 }
865
866 let simulation_result = simulation_result.unwrap();
867 let event = simulation_result
869 .meta
870 .inner_instructions
871 .iter()
872 .flatten()
873 .find_map(|inner_instruction| {
874 PublicTransactionEvent::try_from_slice(&inner_instruction.instruction.data).ok()
875 });
876 let event = if let Some(event) = event {
877 Some(vec![BatchPublicTransactionEvent {
878 event,
879 ..Default::default()
880 }])
881 } else {
882 let mut vec_accounts = Vec::<Vec<Pubkey>>::new();
884 let mut program_ids = Vec::new();
885
886 transaction.message.instructions.iter().for_each(|i| {
887 program_ids.push(transaction.message.account_keys[i.program_id_index as usize]);
888 vec.push(i.data.clone());
889 vec_accounts.push(
890 i.accounts
891 .iter()
892 .map(|x| transaction.message.account_keys[*x as usize])
893 .collect(),
894 );
895 });
896 simulation_result
897 .meta
898 .inner_instructions
899 .iter()
900 .flatten()
901 .find_map(|inner_instruction| {
902 vec.push(inner_instruction.instruction.data.clone());
903 program_ids.push(
904 transaction.message.account_keys
905 [inner_instruction.instruction.program_id_index as usize],
906 );
907 vec_accounts.push(
908 inner_instruction
909 .instruction
910 .accounts
911 .iter()
912 .map(|x| transaction.message.account_keys[*x as usize])
913 .collect(),
914 );
915 None::<PublicTransactionEvent>
916 });
917
918 event_from_light_transaction(
919 &program_ids.iter().map(|x| (*x).into()).collect::<Vec<_>>(),
920 vec.as_slice(),
921 vec_accounts
922 .iter()
923 .map(|inner_vec| inner_vec.iter().map(|x| (*x).into()).collect())
924 .collect(),
925 )
926 .or(Ok::<
927 Option<Vec<BatchPublicTransactionEvent>>,
928 ParseIndexerEventError,
929 >(None))?
930 };
931 if self.config.log_light_protocol_events {
932 println!("event:\n {:?}", event);
933 }
934 let event = event.map(|e| (e, signature, slot));
935
936 if let Some(indexer) = self.indexer.as_mut() {
937 if let Some(events) = event.as_ref() {
938 for event in events.0.iter() {
939 <TestIndexer as TestIndexerExtensions>::add_compressed_accounts_with_token_data(
940 indexer,
941 slot,
942 &event.event,
943 );
944 }
945 }
946 }
947
948 self.pre_context = Some(pre_context_snapshot);
950
951 Ok(event)
952 }
953
954 async fn _create_and_send_transaction_with_event<T>(
955 &mut self,
956 instruction: &[Instruction],
957 payer: &Pubkey,
958 signers: &[&Keypair],
959 ) -> Result<Option<(T, Signature, Slot)>, RpcError>
960 where
961 T: BorshDeserialize + Send + Debug,
962 {
963 let transaction = Transaction::new_signed_with_payer(
964 instruction,
965 Some(payer),
966 signers,
967 self.context.latest_blockhash(),
968 );
969
970 let signature = transaction.signatures[0];
971
972 let pre_context_snapshot = self.context.clone();
974
975 let simulation_result = self
979 .context
980 .simulate_transaction(transaction.clone())
981 .map_err(|x| RpcError::from(x.err))?;
982
983 let event = simulation_result
984 .meta
985 .inner_instructions
986 .iter()
987 .flatten()
988 .find_map(|inner_instruction| {
989 T::try_from_slice(&inner_instruction.instruction.data).ok()
990 });
991 self.transaction_counter += 1;
993 let _res = self.context.send_transaction(transaction).map_err(|x| {
994 if self.config.log_failed_tx {
995 println!("{}", x.meta.pretty_logs());
996 }
997 RpcError::TransactionError(x.err)
998 })?;
999 self.maybe_print_logs(_res.pretty_logs());
1000
1001 self.pre_context = Some(pre_context_snapshot);
1003
1004 let slot = self.get_slot().await?;
1005 let result = event.map(|event| (event, signature, slot));
1006 Ok(result)
1007 }
1008
1009 async fn _create_and_send_transaction_with_batched_event(
1010 &mut self,
1011 instruction: &[Instruction],
1012 payer: &Pubkey,
1013 signers: &[&Keypair],
1014 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
1015 let transaction = Transaction::new_signed_with_payer(
1016 instruction,
1017 Some(payer),
1018 signers,
1019 self.context.latest_blockhash(),
1020 );
1021
1022 self._send_transaction_with_batched_event(transaction).await
1023 }
1024}