anchor_client/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3//! An RPC client to interact with Solana programs written in [`anchor_lang`].
4//!
5//! # Examples
6//!
7//! A simple example that creates a client, sends a transaction and fetches an account:
8//!
9//! ```ignore
10//! use std::rc::Rc;
11//!
12//! use anchor_client::{
13//!     solana_sdk::{
14//!         signature::{read_keypair_file, Keypair},
15//!         signer::Signer,
16//!         system_program,
17//!     },
18//!     Client, Cluster,
19//! };
20//! use my_program::{accounts, instruction, MyAccount};
21//!
22//! fn main() -> Result<(), Box<dyn std::error::Error>> {
23//!     // Create client
24//!     let payer = read_keypair_file("keypair.json")?;
25//!     let client = Client::new(Cluster::Localnet, Rc::new(payer));
26//!
27//!     // Create program
28//!     let program = client.program(my_program::ID)?;
29//!
30//!     // Send transaction
31//!     let my_account_kp = Keypair::new();
32//!     program
33//!         .request()
34//!         .accounts(accounts::Initialize {
35//!             my_account: my_account_kp.pubkey(),
36//!             payer: program.payer(),
37//!             system_program: system_program::ID,
38//!         })
39//!         .args(instruction::Initialize { field: 42 })
40//!         .signer(&my_account_kp)
41//!         .send()?;
42//!
43//!     // Fetch account
44//!     let my_account: MyAccount = program.account(my_account_kp.pubkey())?;
45//!     assert_eq!(my_account.field, 42);
46//!
47//!     Ok(())
48//! }
49//! ```
50//!
51//! More examples can be found in [here].
52//!
53//! [here]: https://github.com/coral-xyz/anchor/tree/v0.31.1/client/example/src
54//!
55//! # Features
56//!
57//! ## `async`
58//!
59//! The client is blocking by default. To enable asynchronous client, add `async` feature:
60//!
61//! ```toml
62//! anchor-client = { version = "0.31.1 ", features = ["async"] }
63//! ````
64//!
65//! ## `mock`
66//!
67//! This feature allows passing in a custom RPC client when creating program instances, which is
68//! useful for mocking RPC responses, e.g. via [`RpcClient::new_mock`].
69//!
70//! [`RpcClient::new_mock`]: https://docs.rs/solana-client/2.1.0/solana_client/rpc_client/struct.RpcClient.html#method.new_mock
71
72use 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>;
130/// Client defines the base configuration for building RPC clients to
131/// communicate with Anchor programs running on a Solana cluster. It's
132/// primary use is to build a `Program` client via the `program` method.
133pub 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
178/// Auxiliary data structure to align the types of the Solana CLI utils with Anchor client.
179/// Client<C> implementation requires <C: Clone + Deref<Target = impl Signer>> which does not comply with Box<dyn Signer>
180/// that's used when loaded Signer from keypair file. This struct is used to wrap the usage.
181pub 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// Internal configuration for a client.
209#[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
234/// Program is the primary client handle to be used to build and send requests.
235pub 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
361/// Iterator with items of type (Pubkey, T). Used to lazily deserialize account structs.
362/// Wrapper type hides the inner type from usages so the implementation can be changed.
363pub struct ProgramAccountsIterator<T> {
364    inner: Map<IntoIter<(Pubkey, Account)>, AccountConverterFunction<T>>,
365}
366
367/// Function type that accepts solana accounts and returns deserialized anchor accounts
368type 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    // Log emitted from the current program.
387    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    // System log.
411    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        // `Invoke [1]` instructions are pushed to the stack in `parse_logs_response`,
422        // so this ensures we only push CPIs to the stack at this stage
423    } else if log.contains("invoke") && !log.ends_with("[1]") {
424        (Some("cpi".to_string()), false) // Any string will do.
425    } 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
507/// `RequestBuilder` provides a builder interface to create and send
508/// transactions to a cluster.
509pub 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
524// Shared implementation for all RequestBuilders
525impl<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    /// Set the accounts to pass to the instruction.
551    ///
552    /// `accounts` argument can be:
553    ///
554    /// - Any type that implements [`ToAccountMetas`] trait
555    /// - A vector of [`AccountMeta`]s (for remaining accounts)
556    ///
557    /// Note that the given accounts are appended to the previous list of accounts instead of
558    /// overriding the existing ones (if any).
559    ///
560    /// # Example
561    ///
562    /// ```ignore
563    /// program
564    ///     .request()
565    ///     // Regular accounts
566    ///     .accounts(accounts::Initialize {
567    ///         my_account: my_account_kp.pubkey(),
568    ///         payer: program.payer(),
569    ///         system_program: system_program::ID,
570    ///     })
571    ///     // Remaining accounts
572    ///     .accounts(vec![AccountMeta {
573    ///         pubkey: remaining,
574    ///         is_signer: true,
575    ///         is_writable: true,
576    ///     }])
577    ///     .args(instruction::Initialize { field: 42 })
578    ///     .send()?;
579    /// ```
580    #[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            // Create a new peekable iterator so that we can peek at the next log whilst iterating
681            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                // Parse the log.
686                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                // Emit the event.
695                if let Some(e) = event {
696                    events.push(e);
697                }
698                // Switch program context on CPI.
699                if let Some(new_program) = new_program {
700                    execution.push(new_program);
701                }
702                // Program returned.
703                if did_pop {
704                    execution.pop();
705
706                    // If the current iteration popped then it means there was a
707                    //`Program x success` log. If the next log in the iteration is
708                    // of depth [1] then we're not within a CPI and this is a new instruction.
709                    //
710                    // We need to ensure that the `Execution` instance is updated with
711                    // the next program ID, or else `execution.program()` will cause
712                    // a panic during the next iteration.
713                    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                            // Within this if block, there will always be a regex match.
718                            // Therefore it's safe to unwrap and the captured program ID
719                            // at index 1 can also be safely unwrapped.
720                            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    // Creating a mock struct that implements `anchor_lang::events`
735    // for type inference in `test_logs`
736    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        // Mock logs received within an `RpcResponse`. These are based on a Jupiter transaction.
772        let logs = vec![
773          "Program VeryCoolProgram invoke [1]", // Outer instruction #1 starts
774          "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", // Outer instruction #1 ends
781          "Program EvenCoolerProgram invoke [1]", // Outer instruction #2 starts
782          "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", // Outer instruction #2 ends
844          "Program ComputeBudget111111111111111111111111111111 invoke [1]",
845          "Program ComputeBudget111111111111111111111111111111 success",
846          "Program ComputeBudget111111111111111111111111111111 invoke [1]",
847          "Program ComputeBudget111111111111111111111111111111 success"];
848
849        // Converting to Vec<String> as expected in `RpcLogsResponse`
850        let logs: Vec<String> = logs.iter().map(|&l| l.to_string()).collect();
851
852        let program_id_str = "VeryCoolProgram";
853
854        // No events returned here. Just ensuring that the function doesn't panic
855        // due an incorrectly emptied stack.
856        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}