1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3use anchor_lang::solana_program::program_error::ProgramError;
73use anchor_lang::solana_program::pubkey::Pubkey;
74use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
75use futures::{Future, StreamExt};
76use regex::Regex;
77use solana_account_decoder::UiAccountEncoding;
78use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient;
79use solana_client::rpc_config::{
80 RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig,
81 RpcTransactionLogsConfig, RpcTransactionLogsFilter,
82};
83use solana_client::rpc_filter::{Memcmp, RpcFilterType};
84use solana_client::{
85 client_error::ClientError as SolanaClientError,
86 nonblocking::pubsub_client::{PubsubClient, PubsubClientError},
87 rpc_response::{Response as RpcResponse, RpcLogsResponse},
88};
89use solana_sdk::account::Account;
90use solana_sdk::commitment_config::CommitmentConfig;
91use solana_sdk::hash::Hash;
92use solana_sdk::instruction::{AccountMeta, Instruction};
93use solana_sdk::signature::{Signature, Signer};
94use solana_sdk::transaction::Transaction;
95use std::iter::Map;
96use std::marker::PhantomData;
97use std::ops::Deref;
98use std::pin::Pin;
99use std::sync::Arc;
100use std::vec::IntoIter;
101use thiserror::Error;
102use tokio::{
103 runtime::Handle,
104 sync::{
105 mpsc::{unbounded_channel, UnboundedReceiver},
106 RwLock,
107 },
108 task::JoinHandle,
109};
110
111pub use anchor_lang;
112pub use cluster::Cluster;
113#[cfg(feature = "async")]
114pub use nonblocking::ThreadSafeSigner;
115pub use solana_account_decoder;
116pub use solana_client;
117pub use solana_sdk;
118
119mod cluster;
120
121#[cfg(not(feature = "async"))]
122mod blocking;
123#[cfg(feature = "async")]
124mod nonblocking;
125
126const PROGRAM_LOG: &str = "Program log: ";
127const PROGRAM_DATA: &str = "Program data: ";
128
129type UnsubscribeFn = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>;
130pub struct Client<C> {
134 cfg: Config<C>,
135}
136
137impl<C: Clone + Deref<Target = impl Signer>> Client<C> {
138 pub fn new(cluster: Cluster, payer: C) -> Self {
139 Self {
140 cfg: Config {
141 cluster,
142 payer,
143 options: None,
144 },
145 }
146 }
147
148 pub fn new_with_options(cluster: Cluster, payer: C, options: CommitmentConfig) -> Self {
149 Self {
150 cfg: Config {
151 cluster,
152 payer,
153 options: Some(options),
154 },
155 }
156 }
157
158 pub fn program(
159 &self,
160 program_id: Pubkey,
161 #[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
162 ) -> Result<Program<C>, ClientError> {
163 let cfg = Config {
164 cluster: self.cfg.cluster.clone(),
165 options: self.cfg.options,
166 payer: self.cfg.payer.clone(),
167 };
168
169 Program::new(
170 program_id,
171 cfg,
172 #[cfg(feature = "mock")]
173 rpc_client,
174 )
175 }
176}
177
178pub struct DynSigner(pub Arc<dyn Signer>);
182
183impl Signer for DynSigner {
184 fn pubkey(&self) -> Pubkey {
185 self.0.pubkey()
186 }
187
188 fn try_pubkey(&self) -> Result<Pubkey, solana_sdk::signer::SignerError> {
189 self.0.try_pubkey()
190 }
191
192 fn sign_message(&self, message: &[u8]) -> solana_sdk::signature::Signature {
193 self.0.sign_message(message)
194 }
195
196 fn try_sign_message(
197 &self,
198 message: &[u8],
199 ) -> Result<solana_sdk::signature::Signature, solana_sdk::signer::SignerError> {
200 self.0.try_sign_message(message)
201 }
202
203 fn is_interactive(&self) -> bool {
204 self.0.is_interactive()
205 }
206}
207
208#[derive(Debug)]
210pub struct Config<C> {
211 cluster: Cluster,
212 payer: C,
213 options: Option<CommitmentConfig>,
214}
215
216pub struct EventUnsubscriber<'a> {
217 handle: JoinHandle<Result<(), ClientError>>,
218 rx: UnboundedReceiver<UnsubscribeFn>,
219 #[cfg(not(feature = "async"))]
220 runtime_handle: &'a Handle,
221 _lifetime_marker: PhantomData<&'a Handle>,
222}
223
224impl EventUnsubscriber<'_> {
225 async fn unsubscribe_internal(mut self) {
226 if let Some(unsubscribe) = self.rx.recv().await {
227 unsubscribe().await;
228 }
229
230 let _ = self.handle.await;
231 }
232}
233
234pub struct Program<C> {
236 program_id: Pubkey,
237 cfg: Config<C>,
238 sub_client: Arc<RwLock<Option<PubsubClient>>>,
239 #[cfg(not(feature = "async"))]
240 rt: tokio::runtime::Runtime,
241 internal_rpc_client: AsyncRpcClient,
242}
243
244impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
245 pub fn payer(&self) -> Pubkey {
246 self.cfg.payer.pubkey()
247 }
248
249 pub fn id(&self) -> Pubkey {
250 self.program_id
251 }
252
253 #[cfg(feature = "mock")]
254 pub fn internal_rpc(&self) -> &AsyncRpcClient {
255 &self.internal_rpc_client
256 }
257
258 async fn account_internal<T: AccountDeserialize>(
259 &self,
260 address: Pubkey,
261 ) -> Result<T, ClientError> {
262 let account = self
263 .internal_rpc_client
264 .get_account_with_commitment(&address, CommitmentConfig::processed())
265 .await?
266 .value
267 .ok_or(ClientError::AccountNotFound)?;
268 let mut data: &[u8] = &account.data;
269 T::try_deserialize(&mut data).map_err(Into::into)
270 }
271
272 async fn accounts_lazy_internal<T: AccountDeserialize + Discriminator>(
273 &self,
274 filters: Vec<RpcFilterType>,
275 ) -> Result<ProgramAccountsIterator<T>, ClientError> {
276 let account_type_filter =
277 RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, T::DISCRIMINATOR));
278 let config = RpcProgramAccountsConfig {
279 filters: Some([vec![account_type_filter], filters].concat()),
280 account_config: RpcAccountInfoConfig {
281 encoding: Some(UiAccountEncoding::Base64),
282 ..RpcAccountInfoConfig::default()
283 },
284 ..RpcProgramAccountsConfig::default()
285 };
286
287 Ok(ProgramAccountsIterator {
288 inner: self
289 .internal_rpc_client
290 .get_program_accounts_with_config(&self.id(), config)
291 .await?
292 .into_iter()
293 .map(|(key, account)| {
294 Ok((key, T::try_deserialize(&mut (&account.data as &[u8]))?))
295 }),
296 })
297 }
298
299 async fn init_sub_client_if_needed(&self) -> Result<(), ClientError> {
300 let lock = &self.sub_client;
301 let mut client = lock.write().await;
302
303 if client.is_none() {
304 let sub_client = PubsubClient::new(self.cfg.cluster.ws_url()).await?;
305 *client = Some(sub_client);
306 }
307
308 Ok(())
309 }
310
311 async fn on_internal<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
312 &self,
313 f: impl Fn(&EventContext, T) + Send + 'static,
314 ) -> Result<
315 (
316 JoinHandle<Result<(), ClientError>>,
317 UnboundedReceiver<UnsubscribeFn>,
318 ),
319 ClientError,
320 > {
321 self.init_sub_client_if_needed().await?;
322 let (tx, rx) = unbounded_channel::<_>();
323 let config = RpcTransactionLogsConfig {
324 commitment: self.cfg.options,
325 };
326 let program_id_str = self.program_id.to_string();
327 let filter = RpcTransactionLogsFilter::Mentions(vec![program_id_str.clone()]);
328
329 let lock = Arc::clone(&self.sub_client);
330
331 let handle = tokio::spawn(async move {
332 if let Some(ref client) = *lock.read().await {
333 let (mut notifications, unsubscribe) =
334 client.logs_subscribe(filter, config).await?;
335
336 tx.send(unsubscribe).map_err(|e| {
337 ClientError::SolanaClientPubsubError(PubsubClientError::RequestFailed {
338 message: "Unsubscribe failed".to_string(),
339 reason: e.to_string(),
340 })
341 })?;
342
343 while let Some(logs) = notifications.next().await {
344 let ctx = EventContext {
345 signature: logs.value.signature.parse().unwrap(),
346 slot: logs.context.slot,
347 };
348 let events = parse_logs_response(logs, &program_id_str)?;
349 for e in events {
350 f(&ctx, e);
351 }
352 }
353 }
354 Ok::<(), ClientError>(())
355 });
356
357 Ok((handle, rx))
358 }
359}
360
361pub struct ProgramAccountsIterator<T> {
364 inner: Map<IntoIter<(Pubkey, Account)>, AccountConverterFunction<T>>,
365}
366
367type AccountConverterFunction<T> = fn((Pubkey, Account)) -> Result<(Pubkey, T), ClientError>;
369
370impl<T> Iterator for ProgramAccountsIterator<T> {
371 type Item = Result<(Pubkey, T), ClientError>;
372
373 fn next(&mut self) -> Option<Self::Item> {
374 self.inner.next()
375 }
376}
377
378pub fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
379 self_program_str: &str,
380 l: &str,
381) -> Result<(Option<T>, Option<String>, bool), ClientError> {
382 use anchor_lang::__private::base64;
383 use base64::engine::general_purpose::STANDARD;
384 use base64::Engine;
385
386 if let Some(log) = l
388 .strip_prefix(PROGRAM_LOG)
389 .or_else(|| l.strip_prefix(PROGRAM_DATA))
390 {
391 let log_bytes = match STANDARD.decode(log) {
392 Ok(log_bytes) => log_bytes,
393 _ => {
394 #[cfg(feature = "debug")]
395 println!("Could not base64 decode log: {}", log);
396 return Ok((None, None, false));
397 }
398 };
399
400 let event = log_bytes
401 .starts_with(T::DISCRIMINATOR)
402 .then(|| {
403 let mut data = &log_bytes[T::DISCRIMINATOR.len()..];
404 T::deserialize(&mut data).map_err(|e| ClientError::LogParseError(e.to_string()))
405 })
406 .transpose()?;
407
408 Ok((event, None, false))
409 }
410 else {
412 let (program, did_pop) = handle_system_log(self_program_str, l);
413 Ok((None, program, did_pop))
414 }
415}
416
417pub fn handle_system_log(this_program_str: &str, log: &str) -> (Option<String>, bool) {
418 if log.starts_with(&format!("Program {this_program_str} log:")) {
419 (Some(this_program_str.to_string()), false)
420
421 } else if log.contains("invoke") && !log.ends_with("[1]") {
424 (Some("cpi".to_string()), false) } else {
426 let re = Regex::new(r"^Program (.*) success*$").unwrap();
427 if re.is_match(log) {
428 (None, true)
429 } else {
430 (None, false)
431 }
432 }
433}
434
435pub struct Execution {
436 stack: Vec<String>,
437}
438
439impl Execution {
440 pub fn new(logs: &mut &[String]) -> Result<Self, ClientError> {
441 let l = &logs[0];
442 *logs = &logs[1..];
443
444 let re = Regex::new(r"^Program (.*) invoke.*$").unwrap();
445 let c = re
446 .captures(l)
447 .ok_or_else(|| ClientError::LogParseError(l.to_string()))?;
448 let program = c
449 .get(1)
450 .ok_or_else(|| ClientError::LogParseError(l.to_string()))?
451 .as_str()
452 .to_string();
453 Ok(Self {
454 stack: vec![program],
455 })
456 }
457
458 pub fn program(&self) -> String {
459 assert!(!self.stack.is_empty());
460 self.stack[self.stack.len() - 1].clone()
461 }
462
463 pub fn push(&mut self, new_program: String) {
464 self.stack.push(new_program);
465 }
466
467 pub fn pop(&mut self) {
468 assert!(!self.stack.is_empty());
469 self.stack.pop().unwrap();
470 }
471}
472
473#[derive(Debug)]
474pub struct EventContext {
475 pub signature: Signature,
476 pub slot: u64,
477}
478
479#[derive(Debug, Error)]
480pub enum ClientError {
481 #[error("Account not found")]
482 AccountNotFound,
483 #[error("{0}")]
484 AnchorError(#[from] anchor_lang::error::Error),
485 #[error("{0}")]
486 ProgramError(#[from] ProgramError),
487 #[error("{0}")]
488 SolanaClientError(#[from] SolanaClientError),
489 #[error("{0}")]
490 SolanaClientPubsubError(#[from] PubsubClientError),
491 #[error("Unable to parse log: {0}")]
492 LogParseError(String),
493 #[error(transparent)]
494 IOError(#[from] std::io::Error),
495}
496
497pub trait AsSigner {
498 fn as_signer(&self) -> &dyn Signer;
499}
500
501impl AsSigner for Box<dyn Signer + '_> {
502 fn as_signer(&self) -> &dyn Signer {
503 self.as_ref()
504 }
505}
506
507pub struct RequestBuilder<'a, C, S: 'a> {
510 cluster: String,
511 program_id: Pubkey,
512 accounts: Vec<AccountMeta>,
513 options: CommitmentConfig,
514 instructions: Vec<Instruction>,
515 payer: C,
516 instruction_data: Option<Vec<u8>>,
517 signers: Vec<S>,
518 #[cfg(not(feature = "async"))]
519 handle: &'a Handle,
520 internal_rpc_client: &'a AsyncRpcClient,
521 _phantom: PhantomData<&'a ()>,
522}
523
524impl<C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'_, C, S> {
526 #[must_use]
527 pub fn payer(mut self, payer: C) -> Self {
528 self.payer = payer;
529 self
530 }
531
532 #[must_use]
533 pub fn cluster(mut self, url: &str) -> Self {
534 self.cluster = url.to_string();
535 self
536 }
537
538 #[must_use]
539 pub fn instruction(mut self, ix: Instruction) -> Self {
540 self.instructions.push(ix);
541 self
542 }
543
544 #[must_use]
545 pub fn program(mut self, program_id: Pubkey) -> Self {
546 self.program_id = program_id;
547 self
548 }
549
550 #[must_use]
581 pub fn accounts(mut self, accounts: impl ToAccountMetas) -> Self {
582 let mut metas = accounts.to_account_metas(None);
583 self.accounts.append(&mut metas);
584 self
585 }
586
587 #[must_use]
588 pub fn options(mut self, options: CommitmentConfig) -> Self {
589 self.options = options;
590 self
591 }
592
593 #[must_use]
594 pub fn args(mut self, args: impl InstructionData) -> Self {
595 self.instruction_data = Some(args.data());
596 self
597 }
598
599 pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
600 let mut instructions = self.instructions.clone();
601 if let Some(ix_data) = &self.instruction_data {
602 instructions.push(Instruction {
603 program_id: self.program_id,
604 data: ix_data.clone(),
605 accounts: self.accounts.clone(),
606 });
607 }
608
609 Ok(instructions)
610 }
611
612 fn signed_transaction_with_blockhash(
613 &self,
614 latest_hash: Hash,
615 ) -> Result<Transaction, ClientError> {
616 let instructions = self.instructions()?;
617 let signers: Vec<&dyn Signer> = self.signers.iter().map(|s| s.as_signer()).collect();
618 let mut all_signers = signers;
619 all_signers.push(&*self.payer);
620
621 let tx = Transaction::new_signed_with_payer(
622 &instructions,
623 Some(&self.payer.pubkey()),
624 &all_signers,
625 latest_hash,
626 );
627
628 Ok(tx)
629 }
630
631 pub fn transaction(&self) -> Result<Transaction, ClientError> {
632 let instructions = &self.instructions;
633 let tx = Transaction::new_with_payer(instructions, Some(&self.payer.pubkey()));
634 Ok(tx)
635 }
636
637 async fn signed_transaction_internal(&self) -> Result<Transaction, ClientError> {
638 let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
639
640 let tx = self.signed_transaction_with_blockhash(latest_hash)?;
641 Ok(tx)
642 }
643
644 async fn send_internal(&self) -> Result<Signature, ClientError> {
645 let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
646 let tx = self.signed_transaction_with_blockhash(latest_hash)?;
647
648 self.internal_rpc_client
649 .send_and_confirm_transaction(&tx)
650 .await
651 .map_err(Into::into)
652 }
653
654 async fn send_with_spinner_and_config_internal(
655 &self,
656 config: RpcSendTransactionConfig,
657 ) -> Result<Signature, ClientError> {
658 let latest_hash = self.internal_rpc_client.get_latest_blockhash().await?;
659 let tx = self.signed_transaction_with_blockhash(latest_hash)?;
660
661 self.internal_rpc_client
662 .send_and_confirm_transaction_with_spinner_and_config(
663 &tx,
664 self.internal_rpc_client.commitment(),
665 config,
666 )
667 .await
668 .map_err(Into::into)
669 }
670}
671
672fn parse_logs_response<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
673 logs: RpcResponse<RpcLogsResponse>,
674 program_id_str: &str,
675) -> Result<Vec<T>, ClientError> {
676 let mut logs = &logs.value.logs[..];
677 let mut events: Vec<T> = Vec::new();
678 if !logs.is_empty() {
679 if let Ok(mut execution) = Execution::new(&mut logs) {
680 let mut logs_iter = logs.iter().peekable();
682 let regex = Regex::new(r"^Program (.*) invoke.*$").unwrap();
683
684 while let Some(l) = logs_iter.next() {
685 let (event, new_program, did_pop) = {
687 if program_id_str == execution.program() {
688 handle_program_log(program_id_str, l)?
689 } else {
690 let (program, did_pop) = handle_system_log(program_id_str, l);
691 (None, program, did_pop)
692 }
693 };
694 if let Some(e) = event {
696 events.push(e);
697 }
698 if let Some(new_program) = new_program {
700 execution.push(new_program);
701 }
702 if did_pop {
704 execution.pop();
705
706 if let Some(&next_log) = logs_iter.peek() {
714 if next_log.ends_with("invoke [1]") {
715 let next_instruction =
716 regex.captures(next_log).unwrap().get(1).unwrap().as_str();
717 execution.push(next_instruction.to_string());
721 }
722 };
723 }
724 }
725 }
726 }
727 Ok(events)
728}
729
730#[cfg(test)]
731mod tests {
732 use solana_client::rpc_response::RpcResponseContext;
733
734 use anchor_lang::prelude::*;
737 #[derive(Debug, Clone, Copy)]
738 #[event]
739 pub struct MockEvent {}
740
741 use super::*;
742 #[test]
743 fn new_execution() {
744 let mut logs: &[String] =
745 &["Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw invoke [1]".to_string()];
746 let exe = Execution::new(&mut logs).unwrap();
747 assert_eq!(
748 exe.stack[0],
749 "7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw".to_string()
750 );
751 }
752
753 #[test]
754 fn handle_system_log_pop() {
755 let log = "Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw success";
756 let (program, did_pop) = handle_system_log("asdf", log);
757 assert_eq!(program, None);
758 assert!(did_pop);
759 }
760
761 #[test]
762 fn handle_system_log_no_pop() {
763 let log = "Program 7swsTUiQ6KUK4uFYquQKg4epFRsBnvbrTf2fZQCa2sTJ qwer";
764 let (program, did_pop) = handle_system_log("asdf", log);
765 assert_eq!(program, None);
766 assert!(!did_pop);
767 }
768
769 #[test]
770 fn test_parse_logs_response() -> Result<()> {
771 let logs = vec![
773 "Program VeryCoolProgram invoke [1]", "Program log: Instruction: VeryCoolEvent",
775 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
776 "Program log: Instruction: Transfer",
777 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 664387 compute units",
778 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
779 "Program VeryCoolProgram consumed 42417 of 700000 compute units",
780 "Program VeryCoolProgram success", "Program EvenCoolerProgram invoke [1]", "Program log: Instruction: EvenCoolerEvent",
783 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
784 "Program log: Instruction: TransferChecked",
785 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6200 of 630919 compute units",
786 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
787 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt invoke [2]",
788 "Program log: Instruction: Swap",
789 "Program log: INVARIANT: SWAP",
790 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
791 "Program log: Instruction: Transfer",
792 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 539321 compute units",
793 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
794 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
795 "Program log: Instruction: Transfer",
796 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 531933 compute units",
797 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
798 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt consumed 84670 of 610768 compute units",
799 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt success",
800 "Program EvenCoolerProgram invoke [2]",
801 "Program EvenCoolerProgram consumed 2021 of 523272 compute units",
802 "Program EvenCoolerProgram success",
803 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt invoke [2]",
804 "Program log: Instruction: Swap",
805 "Program log: INVARIANT: SWAP",
806 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
807 "Program log: Instruction: Transfer",
808 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 418618 compute units",
809 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
810 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
811 "Program log: Instruction: Transfer",
812 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 411230 compute units",
813 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
814 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt consumed 102212 of 507607 compute units",
815 "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt success",
816 "Program EvenCoolerProgram invoke [2]",
817 "Program EvenCoolerProgram consumed 2021 of 402569 compute units",
818 "Program EvenCoolerProgram success",
819 "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP invoke [2]",
820 "Program log: Instruction: Swap",
821 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
822 "Program log: Instruction: Transfer",
823 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 371140 compute units",
824 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
825 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
826 "Program log: Instruction: MintTo",
827 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4492 of 341800 compute units",
828 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
829 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
830 "Program log: Instruction: Transfer",
831 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 334370 compute units",
832 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
833 "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP consumed 57610 of 386812 compute units",
834 "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP success",
835 "Program EvenCoolerProgram invoke [2]",
836 "Program EvenCoolerProgram consumed 2021 of 326438 compute units",
837 "Program EvenCoolerProgram success",
838 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
839 "Program log: Instruction: TransferChecked",
840 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 319725 compute units",
841 "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
842 "Program EvenCoolerProgram consumed 345969 of 657583 compute units",
843 "Program EvenCoolerProgram success", "Program ComputeBudget111111111111111111111111111111 invoke [1]",
845 "Program ComputeBudget111111111111111111111111111111 success",
846 "Program ComputeBudget111111111111111111111111111111 invoke [1]",
847 "Program ComputeBudget111111111111111111111111111111 success"];
848
849 let logs: Vec<String> = logs.iter().map(|&l| l.to_string()).collect();
851
852 let program_id_str = "VeryCoolProgram";
853
854 parse_logs_response::<MockEvent>(
857 RpcResponse {
858 context: RpcResponseContext::new(0),
859 value: RpcLogsResponse {
860 signature: "".to_string(),
861 err: None,
862 logs: logs.to_vec(),
863 },
864 },
865 program_id_str,
866 )
867 .unwrap();
868
869 Ok(())
870 }
871}