lib.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. #![cfg_attr(docsrs, feature(doc_auto_cfg))]
  2. //! An RPC client to interact with Solana programs written in [`anchor_lang`].
  3. //!
  4. //! # Examples
  5. //!
  6. //! A simple example that creates a client, sends a transaction and fetches an account:
  7. //!
  8. //! ```ignore
  9. //! use std::rc::Rc;
  10. //!
  11. //! use anchor_client::{
  12. //! solana_sdk::{
  13. //! signature::{read_keypair_file, Keypair},
  14. //! signer::Signer,
  15. //! system_program,
  16. //! },
  17. //! Client, Cluster,
  18. //! };
  19. //! use my_program::{accounts, instruction, MyAccount};
  20. //!
  21. //! fn main() -> Result<(), Box<dyn std::error::Error>> {
  22. //! // Create client
  23. //! let payer = read_keypair_file("keypair.json")?;
  24. //! let client = Client::new(Cluster::Localnet, Rc::new(payer));
  25. //!
  26. //! // Create program
  27. //! let program = client.program(my_program::ID)?;
  28. //!
  29. //! // Send transaction
  30. //! let my_account_kp = Keypair::new();
  31. //! program
  32. //! .request()
  33. //! .accounts(accounts::Initialize {
  34. //! my_account: my_account_kp.pubkey(),
  35. //! payer: program.payer(),
  36. //! system_program: system_program::ID,
  37. //! })
  38. //! .args(instruction::Initialize { field: 42 })
  39. //! .signer(&my_account_kp)
  40. //! .send()?;
  41. //!
  42. //! // Fetch account
  43. //! let my_account: MyAccount = program.account(my_account_kp.pubkey())?;
  44. //! assert_eq!(my_account.field, 42);
  45. //!
  46. //! Ok(())
  47. //! }
  48. //! ```
  49. //!
  50. //! More examples can be found in [here].
  51. //!
  52. //! [here]: https://github.com/coral-xyz/anchor/tree/v0.31.1/client/example/src
  53. //!
  54. //! # Features
  55. //!
  56. //! ## `async`
  57. //!
  58. //! The client is blocking by default. To enable asynchronous client, add `async` feature:
  59. //!
  60. //! ```toml
  61. //! anchor-client = { version = "0.31.1 ", features = ["async"] }
  62. //! ````
  63. //!
  64. //! ## `mock`
  65. //!
  66. //! This feature allows passing in a custom RPC client when creating program instances, which is
  67. //! useful for mocking RPC responses, e.g. via [`RpcClient::new_mock`].
  68. //!
  69. //! [`RpcClient::new_mock`]: https://docs.rs/solana-client/2.1.0/solana_client/rpc_client/struct.RpcClient.html#method.new_mock
  70. use anchor_lang::solana_program::program_error::ProgramError;
  71. use anchor_lang::solana_program::pubkey::Pubkey;
  72. use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
  73. use futures::{Future, StreamExt};
  74. use regex::Regex;
  75. use solana_account_decoder::UiAccountEncoding;
  76. use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient;
  77. use solana_client::rpc_config::{
  78. RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcSendTransactionConfig,
  79. RpcTransactionLogsConfig, RpcTransactionLogsFilter,
  80. };
  81. use solana_client::rpc_filter::{Memcmp, RpcFilterType};
  82. use solana_client::{
  83. client_error::ClientError as SolanaClientError,
  84. nonblocking::pubsub_client::{PubsubClient, PubsubClientError},
  85. rpc_response::{Response as RpcResponse, RpcLogsResponse},
  86. };
  87. use solana_sdk::account::Account;
  88. use solana_sdk::commitment_config::CommitmentConfig;
  89. use solana_sdk::hash::Hash;
  90. use solana_sdk::instruction::{AccountMeta, Instruction};
  91. use solana_sdk::signature::{Signature, Signer};
  92. use solana_sdk::transaction::Transaction;
  93. use std::iter::Map;
  94. use std::marker::PhantomData;
  95. use std::ops::Deref;
  96. use std::pin::Pin;
  97. use std::sync::Arc;
  98. use std::vec::IntoIter;
  99. use thiserror::Error;
  100. use tokio::{
  101. runtime::Handle,
  102. sync::{
  103. mpsc::{unbounded_channel, UnboundedReceiver},
  104. RwLock,
  105. },
  106. task::JoinHandle,
  107. };
  108. pub use anchor_lang;
  109. pub use cluster::Cluster;
  110. #[cfg(feature = "async")]
  111. pub use nonblocking::ThreadSafeSigner;
  112. pub use solana_account_decoder;
  113. pub use solana_client;
  114. pub use solana_sdk;
  115. mod cluster;
  116. #[cfg(not(feature = "async"))]
  117. mod blocking;
  118. #[cfg(feature = "async")]
  119. mod nonblocking;
  120. const PROGRAM_LOG: &str = "Program log: ";
  121. const PROGRAM_DATA: &str = "Program data: ";
  122. type UnsubscribeFn = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>;
  123. /// Client defines the base configuration for building RPC clients to
  124. /// communicate with Anchor programs running on a Solana cluster. It's
  125. /// primary use is to build a `Program` client via the `program` method.
  126. pub struct Client<C> {
  127. cfg: Config<C>,
  128. }
  129. impl<C: Clone + Deref<Target = impl Signer>> Client<C> {
  130. pub fn new(cluster: Cluster, payer: C) -> Self {
  131. Self {
  132. cfg: Config {
  133. cluster,
  134. payer,
  135. options: None,
  136. },
  137. }
  138. }
  139. pub fn new_with_options(cluster: Cluster, payer: C, options: CommitmentConfig) -> Self {
  140. Self {
  141. cfg: Config {
  142. cluster,
  143. payer,
  144. options: Some(options),
  145. },
  146. }
  147. }
  148. pub fn program(
  149. &self,
  150. program_id: Pubkey,
  151. #[cfg(feature = "mock")] rpc_client: AsyncRpcClient,
  152. ) -> Result<Program<C>, ClientError> {
  153. let cfg = Config {
  154. cluster: self.cfg.cluster.clone(),
  155. options: self.cfg.options,
  156. payer: self.cfg.payer.clone(),
  157. };
  158. Program::new(
  159. program_id,
  160. cfg,
  161. #[cfg(feature = "mock")]
  162. rpc_client,
  163. )
  164. }
  165. }
  166. /// Auxiliary data structure to align the types of the Solana CLI utils with Anchor client.
  167. /// Client<C> implementation requires <C: Clone + Deref<Target = impl Signer>> which does not comply with Box<dyn Signer>
  168. /// that's used when loaded Signer from keypair file. This struct is used to wrap the usage.
  169. pub struct DynSigner(pub Arc<dyn Signer>);
  170. impl Signer for DynSigner {
  171. fn pubkey(&self) -> Pubkey {
  172. self.0.pubkey()
  173. }
  174. fn try_pubkey(&self) -> Result<Pubkey, solana_sdk::signer::SignerError> {
  175. self.0.try_pubkey()
  176. }
  177. fn sign_message(&self, message: &[u8]) -> solana_sdk::signature::Signature {
  178. self.0.sign_message(message)
  179. }
  180. fn try_sign_message(
  181. &self,
  182. message: &[u8],
  183. ) -> Result<solana_sdk::signature::Signature, solana_sdk::signer::SignerError> {
  184. self.0.try_sign_message(message)
  185. }
  186. fn is_interactive(&self) -> bool {
  187. self.0.is_interactive()
  188. }
  189. }
  190. // Internal configuration for a client.
  191. #[derive(Debug)]
  192. pub struct Config<C> {
  193. cluster: Cluster,
  194. payer: C,
  195. options: Option<CommitmentConfig>,
  196. }
  197. pub struct EventUnsubscriber<'a> {
  198. handle: JoinHandle<Result<(), ClientError>>,
  199. rx: UnboundedReceiver<UnsubscribeFn>,
  200. #[cfg(not(feature = "async"))]
  201. runtime_handle: &'a Handle,
  202. _lifetime_marker: PhantomData<&'a Handle>,
  203. }
  204. impl EventUnsubscriber<'_> {
  205. async fn unsubscribe_internal(mut self) {
  206. if let Some(unsubscribe) = self.rx.recv().await {
  207. unsubscribe().await;
  208. }
  209. let _ = self.handle.await;
  210. }
  211. }
  212. /// Program is the primary client handle to be used to build and send requests.
  213. pub struct Program<C> {
  214. program_id: Pubkey,
  215. cfg: Config<C>,
  216. sub_client: Arc<RwLock<Option<PubsubClient>>>,
  217. #[cfg(not(feature = "async"))]
  218. rt: tokio::runtime::Runtime,
  219. internal_rpc_client: AsyncRpcClient,
  220. }
  221. impl<C: Deref<Target = impl Signer> + Clone> Program<C> {
  222. pub fn payer(&self) -> Pubkey {
  223. self.cfg.payer.pubkey()
  224. }
  225. pub fn id(&self) -> Pubkey {
  226. self.program_id
  227. }
  228. #[cfg(feature = "mock")]
  229. pub fn internal_rpc(&self) -> &AsyncRpcClient {
  230. &self.internal_rpc_client
  231. }
  232. async fn account_internal<T: AccountDeserialize>(
  233. &self,
  234. address: Pubkey,
  235. ) -> Result<T, ClientError> {
  236. let account = self
  237. .internal_rpc_client
  238. .get_account_with_commitment(&address, CommitmentConfig::processed())
  239. .await
  240. .map_err(Box::new)?
  241. .value
  242. .ok_or(ClientError::AccountNotFound)?;
  243. let mut data: &[u8] = &account.data;
  244. T::try_deserialize(&mut data).map_err(Into::into)
  245. }
  246. async fn accounts_lazy_internal<T: AccountDeserialize + Discriminator>(
  247. &self,
  248. filters: Vec<RpcFilterType>,
  249. ) -> Result<ProgramAccountsIterator<T>, ClientError> {
  250. let account_type_filter =
  251. RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, T::DISCRIMINATOR));
  252. let config = RpcProgramAccountsConfig {
  253. filters: Some([vec![account_type_filter], filters].concat()),
  254. account_config: RpcAccountInfoConfig {
  255. encoding: Some(UiAccountEncoding::Base64),
  256. ..RpcAccountInfoConfig::default()
  257. },
  258. ..RpcProgramAccountsConfig::default()
  259. };
  260. Ok(ProgramAccountsIterator {
  261. inner: self
  262. .internal_rpc_client
  263. .get_program_accounts_with_config(&self.id(), config)
  264. .await
  265. .map_err(Box::new)?
  266. .into_iter()
  267. .map(|(key, account)| {
  268. Ok((key, T::try_deserialize(&mut (&account.data as &[u8]))?))
  269. }),
  270. })
  271. }
  272. async fn init_sub_client_if_needed(&self) -> Result<(), ClientError> {
  273. let lock = &self.sub_client;
  274. let mut client = lock.write().await;
  275. if client.is_none() {
  276. let sub_client = PubsubClient::new(self.cfg.cluster.ws_url())
  277. .await
  278. .map_err(Box::new)?;
  279. *client = Some(sub_client);
  280. }
  281. Ok(())
  282. }
  283. async fn on_internal<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
  284. &self,
  285. f: impl Fn(&EventContext, T) + Send + 'static,
  286. ) -> Result<
  287. (
  288. JoinHandle<Result<(), ClientError>>,
  289. UnboundedReceiver<UnsubscribeFn>,
  290. ),
  291. ClientError,
  292. > {
  293. self.init_sub_client_if_needed().await?;
  294. let (tx, rx) = unbounded_channel::<_>();
  295. let config = RpcTransactionLogsConfig {
  296. commitment: self.cfg.options,
  297. };
  298. let program_id_str = self.program_id.to_string();
  299. let filter = RpcTransactionLogsFilter::Mentions(vec![program_id_str.clone()]);
  300. let lock = Arc::clone(&self.sub_client);
  301. let handle = tokio::spawn(async move {
  302. if let Some(ref client) = *lock.read().await {
  303. let (mut notifications, unsubscribe) = client
  304. .logs_subscribe(filter, config)
  305. .await
  306. .map_err(Box::new)?;
  307. tx.send(unsubscribe).map_err(|e| {
  308. ClientError::SolanaClientPubsubError(Box::new(
  309. PubsubClientError::RequestFailed {
  310. message: "Unsubscribe failed".to_string(),
  311. reason: e.to_string(),
  312. },
  313. ))
  314. })?;
  315. while let Some(logs) = notifications.next().await {
  316. let ctx = EventContext {
  317. signature: logs.value.signature.parse().unwrap(),
  318. slot: logs.context.slot,
  319. };
  320. let events = parse_logs_response(logs, &program_id_str)?;
  321. for e in events {
  322. f(&ctx, e);
  323. }
  324. }
  325. }
  326. Ok::<(), ClientError>(())
  327. });
  328. Ok((handle, rx))
  329. }
  330. }
  331. /// Iterator with items of type (Pubkey, T). Used to lazily deserialize account structs.
  332. /// Wrapper type hides the inner type from usages so the implementation can be changed.
  333. pub struct ProgramAccountsIterator<T> {
  334. inner: Map<IntoIter<(Pubkey, Account)>, AccountConverterFunction<T>>,
  335. }
  336. /// Function type that accepts solana accounts and returns deserialized anchor accounts
  337. type AccountConverterFunction<T> = fn((Pubkey, Account)) -> Result<(Pubkey, T), ClientError>;
  338. impl<T> Iterator for ProgramAccountsIterator<T> {
  339. type Item = Result<(Pubkey, T), ClientError>;
  340. fn next(&mut self) -> Option<Self::Item> {
  341. self.inner.next()
  342. }
  343. }
  344. pub fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
  345. self_program_str: &str,
  346. l: &str,
  347. ) -> Result<(Option<T>, Option<String>, bool), ClientError> {
  348. use anchor_lang::__private::base64;
  349. use base64::engine::general_purpose::STANDARD;
  350. use base64::Engine;
  351. // Log emitted from the current program.
  352. if let Some(log) = l
  353. .strip_prefix(PROGRAM_LOG)
  354. .or_else(|| l.strip_prefix(PROGRAM_DATA))
  355. {
  356. let log_bytes = match STANDARD.decode(log) {
  357. Ok(log_bytes) => log_bytes,
  358. _ => {
  359. #[cfg(feature = "debug")]
  360. println!("Could not base64 decode log: {}", log);
  361. return Ok((None, None, false));
  362. }
  363. };
  364. let event = log_bytes
  365. .starts_with(T::DISCRIMINATOR)
  366. .then(|| {
  367. let mut data = &log_bytes[T::DISCRIMINATOR.len()..];
  368. T::deserialize(&mut data).map_err(|e| ClientError::LogParseError(e.to_string()))
  369. })
  370. .transpose()?;
  371. Ok((event, None, false))
  372. }
  373. // System log.
  374. else {
  375. let (program, did_pop) = handle_system_log(self_program_str, l);
  376. Ok((None, program, did_pop))
  377. }
  378. }
  379. pub fn handle_system_log(this_program_str: &str, log: &str) -> (Option<String>, bool) {
  380. if log.starts_with(&format!("Program {this_program_str} log:")) {
  381. (Some(this_program_str.to_string()), false)
  382. // `Invoke [1]` instructions are pushed to the stack in `parse_logs_response`,
  383. // so this ensures we only push CPIs to the stack at this stage
  384. } else if log.contains("invoke") && !log.ends_with("[1]") {
  385. (Some("cpi".to_string()), false) // Any string will do.
  386. } else {
  387. let re = Regex::new(r"^Program ([1-9A-HJ-NP-Za-km-z]+) success$").unwrap();
  388. if re.is_match(log) {
  389. (None, true)
  390. } else {
  391. (None, false)
  392. }
  393. }
  394. }
  395. pub struct Execution {
  396. stack: Vec<String>,
  397. }
  398. impl Execution {
  399. pub fn new(logs: &mut &[String]) -> Result<Self, ClientError> {
  400. let l = &logs[0];
  401. *logs = &logs[1..];
  402. let re = Regex::new(r"^Program ([1-9A-HJ-NP-Za-km-z]+) invoke \[[\d]+\]$").unwrap();
  403. let c = re
  404. .captures(l)
  405. .ok_or_else(|| ClientError::LogParseError(l.to_string()))?;
  406. let program = c
  407. .get(1)
  408. .ok_or_else(|| ClientError::LogParseError(l.to_string()))?
  409. .as_str()
  410. .to_string();
  411. Ok(Self {
  412. stack: vec![program],
  413. })
  414. }
  415. pub fn program(&self) -> String {
  416. assert!(!self.stack.is_empty());
  417. self.stack[self.stack.len() - 1].clone()
  418. }
  419. pub fn push(&mut self, new_program: String) {
  420. self.stack.push(new_program);
  421. }
  422. pub fn pop(&mut self) {
  423. assert!(!self.stack.is_empty());
  424. self.stack.pop().unwrap();
  425. }
  426. }
  427. #[derive(Debug)]
  428. pub struct EventContext {
  429. pub signature: Signature,
  430. pub slot: u64,
  431. }
  432. #[derive(Debug, Error)]
  433. pub enum ClientError {
  434. #[error("Account not found")]
  435. AccountNotFound,
  436. #[error("{0}")]
  437. AnchorError(#[from] anchor_lang::error::Error),
  438. #[error("{0}")]
  439. ProgramError(#[from] ProgramError),
  440. #[error("{0}")]
  441. SolanaClientError(#[from] Box<SolanaClientError>),
  442. #[error("{0}")]
  443. SolanaClientPubsubError(#[from] Box<PubsubClientError>),
  444. #[error("Unable to parse log: {0}")]
  445. LogParseError(String),
  446. #[error(transparent)]
  447. IOError(#[from] std::io::Error),
  448. }
  449. pub trait AsSigner {
  450. fn as_signer(&self) -> &dyn Signer;
  451. }
  452. impl AsSigner for Box<dyn Signer + '_> {
  453. fn as_signer(&self) -> &dyn Signer {
  454. self.as_ref()
  455. }
  456. }
  457. /// `RequestBuilder` provides a builder interface to create and send
  458. /// transactions to a cluster.
  459. pub struct RequestBuilder<'a, C, S: 'a> {
  460. cluster: String,
  461. program_id: Pubkey,
  462. accounts: Vec<AccountMeta>,
  463. options: CommitmentConfig,
  464. instructions: Vec<Instruction>,
  465. payer: C,
  466. instruction_data: Option<Vec<u8>>,
  467. signers: Vec<S>,
  468. #[cfg(not(feature = "async"))]
  469. handle: &'a Handle,
  470. internal_rpc_client: &'a AsyncRpcClient,
  471. _phantom: PhantomData<&'a ()>,
  472. }
  473. // Shared implementation for all RequestBuilders
  474. impl<C: Deref<Target = impl Signer> + Clone, S: AsSigner> RequestBuilder<'_, C, S> {
  475. #[must_use]
  476. pub fn payer(mut self, payer: C) -> Self {
  477. self.payer = payer;
  478. self
  479. }
  480. #[must_use]
  481. pub fn cluster(mut self, url: &str) -> Self {
  482. self.cluster = url.to_string();
  483. self
  484. }
  485. #[must_use]
  486. pub fn instruction(mut self, ix: Instruction) -> Self {
  487. self.instructions.push(ix);
  488. self
  489. }
  490. #[must_use]
  491. pub fn program(mut self, program_id: Pubkey) -> Self {
  492. self.program_id = program_id;
  493. self
  494. }
  495. /// Set the accounts to pass to the instruction.
  496. ///
  497. /// `accounts` argument can be:
  498. ///
  499. /// - Any type that implements [`ToAccountMetas`] trait
  500. /// - A vector of [`AccountMeta`]s (for remaining accounts)
  501. ///
  502. /// Note that the given accounts are appended to the previous list of accounts instead of
  503. /// overriding the existing ones (if any).
  504. ///
  505. /// # Example
  506. ///
  507. /// ```ignore
  508. /// program
  509. /// .request()
  510. /// // Regular accounts
  511. /// .accounts(accounts::Initialize {
  512. /// my_account: my_account_kp.pubkey(),
  513. /// payer: program.payer(),
  514. /// system_program: system_program::ID,
  515. /// })
  516. /// // Remaining accounts
  517. /// .accounts(vec![AccountMeta {
  518. /// pubkey: remaining,
  519. /// is_signer: true,
  520. /// is_writable: true,
  521. /// }])
  522. /// .args(instruction::Initialize { field: 42 })
  523. /// .send()?;
  524. /// ```
  525. #[must_use]
  526. pub fn accounts(mut self, accounts: impl ToAccountMetas) -> Self {
  527. let mut metas = accounts.to_account_metas(None);
  528. self.accounts.append(&mut metas);
  529. self
  530. }
  531. #[must_use]
  532. pub fn options(mut self, options: CommitmentConfig) -> Self {
  533. self.options = options;
  534. self
  535. }
  536. #[must_use]
  537. pub fn args(mut self, args: impl InstructionData) -> Self {
  538. self.instruction_data = Some(args.data());
  539. self
  540. }
  541. pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
  542. let mut instructions = self.instructions.clone();
  543. if let Some(ix_data) = &self.instruction_data {
  544. instructions.push(Instruction {
  545. program_id: self.program_id,
  546. data: ix_data.clone(),
  547. accounts: self.accounts.clone(),
  548. });
  549. }
  550. Ok(instructions)
  551. }
  552. fn signed_transaction_with_blockhash(
  553. &self,
  554. latest_hash: Hash,
  555. ) -> Result<Transaction, ClientError> {
  556. let instructions = self.instructions()?;
  557. let signers: Vec<&dyn Signer> = self.signers.iter().map(|s| s.as_signer()).collect();
  558. let mut all_signers = signers;
  559. all_signers.push(&*self.payer);
  560. let tx = Transaction::new_signed_with_payer(
  561. &instructions,
  562. Some(&self.payer.pubkey()),
  563. &all_signers,
  564. latest_hash,
  565. );
  566. Ok(tx)
  567. }
  568. pub fn transaction(&self) -> Result<Transaction, ClientError> {
  569. let instructions = &self.instructions;
  570. let tx = Transaction::new_with_payer(instructions, Some(&self.payer.pubkey()));
  571. Ok(tx)
  572. }
  573. async fn signed_transaction_internal(&self) -> Result<Transaction, ClientError> {
  574. let latest_hash = self
  575. .internal_rpc_client
  576. .get_latest_blockhash()
  577. .await
  578. .map_err(Box::new)?;
  579. let tx = self.signed_transaction_with_blockhash(latest_hash)?;
  580. Ok(tx)
  581. }
  582. async fn send_internal(&self) -> Result<Signature, ClientError> {
  583. let latest_hash = self
  584. .internal_rpc_client
  585. .get_latest_blockhash()
  586. .await
  587. .map_err(Box::new)?;
  588. let tx = self.signed_transaction_with_blockhash(latest_hash)?;
  589. self.internal_rpc_client
  590. .send_and_confirm_transaction(&tx)
  591. .await
  592. .map_err(|e| Box::new(e).into())
  593. }
  594. async fn send_with_spinner_and_config_internal(
  595. &self,
  596. config: RpcSendTransactionConfig,
  597. ) -> Result<Signature, ClientError> {
  598. let latest_hash = self
  599. .internal_rpc_client
  600. .get_latest_blockhash()
  601. .await
  602. .map_err(Box::new)?;
  603. let tx = self.signed_transaction_with_blockhash(latest_hash)?;
  604. self.internal_rpc_client
  605. .send_and_confirm_transaction_with_spinner_and_config(
  606. &tx,
  607. self.internal_rpc_client.commitment(),
  608. config,
  609. )
  610. .await
  611. .map_err(|e| Box::new(e).into())
  612. }
  613. }
  614. fn parse_logs_response<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
  615. logs: RpcResponse<RpcLogsResponse>,
  616. program_id_str: &str,
  617. ) -> Result<Vec<T>, ClientError> {
  618. let mut logs = &logs.value.logs[..];
  619. let mut events: Vec<T> = Vec::new();
  620. if !logs.is_empty() {
  621. if let Ok(mut execution) = Execution::new(&mut logs) {
  622. // Create a new peekable iterator so that we can peek at the next log whilst iterating
  623. let mut logs_iter = logs.iter().peekable();
  624. let regex = Regex::new(r"^Program ([1-9A-HJ-NP-Za-km-z]+) invoke \[[\d]+\]$").unwrap();
  625. while let Some(l) = logs_iter.next() {
  626. // Parse the log.
  627. let (event, new_program, did_pop) = {
  628. if program_id_str == execution.program() {
  629. handle_program_log(program_id_str, l)?
  630. } else {
  631. let (program, did_pop) = handle_system_log(program_id_str, l);
  632. (None, program, did_pop)
  633. }
  634. };
  635. // Emit the event.
  636. if let Some(e) = event {
  637. events.push(e);
  638. }
  639. // Switch program context on CPI.
  640. if let Some(new_program) = new_program {
  641. execution.push(new_program);
  642. }
  643. // Program returned.
  644. if did_pop {
  645. execution.pop();
  646. // If the current iteration popped then it means there was a
  647. //`Program x success` log. If the next log in the iteration is
  648. // of depth [1] then we're not within a CPI and this is a new instruction.
  649. //
  650. // We need to ensure that the `Execution` instance is updated with
  651. // the next program ID, or else `execution.program()` will cause
  652. // a panic during the next iteration.
  653. if let Some(&next_log) = logs_iter.peek() {
  654. if next_log.ends_with("invoke [1]") {
  655. let next_instruction =
  656. regex.captures(next_log).unwrap().get(1).unwrap().as_str();
  657. // Within this if block, there will always be a regex match.
  658. // Therefore it's safe to unwrap and the captured program ID
  659. // at index 1 can also be safely unwrapped.
  660. execution.push(next_instruction.to_string());
  661. }
  662. };
  663. }
  664. }
  665. }
  666. }
  667. Ok(events)
  668. }
  669. #[cfg(test)]
  670. mod tests {
  671. use solana_client::rpc_response::RpcResponseContext;
  672. // Creating a mock struct that implements `anchor_lang::events`
  673. // for type inference in `test_logs`
  674. use anchor_lang::prelude::*;
  675. #[derive(Debug, Clone, Copy)]
  676. #[event]
  677. pub struct MockEvent {}
  678. use super::*;
  679. #[test]
  680. fn new_execution() {
  681. let mut logs: &[String] =
  682. &["Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw invoke [1]".to_string()];
  683. let exe = Execution::new(&mut logs).unwrap();
  684. assert_eq!(
  685. exe.stack[0],
  686. "7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw".to_string()
  687. );
  688. }
  689. #[test]
  690. fn handle_system_log_pop() {
  691. let log = "Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw success";
  692. let (program, did_pop) = handle_system_log("asdf", log);
  693. assert_eq!(program, None);
  694. assert!(did_pop);
  695. }
  696. #[test]
  697. fn handle_system_log_no_pop() {
  698. let log = "Program 7swsTUiQ6KUK4uFYquQKg4epFRsBnvbrTf2fZQCa2sTJ qwer";
  699. let (program, did_pop) = handle_system_log("asdf", log);
  700. assert_eq!(program, None);
  701. assert!(!did_pop);
  702. }
  703. #[test]
  704. fn test_parse_logs_response() -> Result<()> {
  705. // Mock logs received within an `RpcResponse`. These are based on a Jupiter transaction.
  706. let logs = vec![
  707. "Program VeryCoolProgram invoke [1]", // Outer instruction #1 starts
  708. "Program log: Instruction: VeryCoolEvent",
  709. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
  710. "Program log: Instruction: Transfer",
  711. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 664387 compute units",
  712. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  713. "Program VeryCoolProgram consumed 42417 of 700000 compute units",
  714. "Program VeryCoolProgram success", // Outer instruction #1 ends
  715. "Program EvenCoolerProgram invoke [1]", // Outer instruction #2 starts
  716. "Program log: Instruction: EvenCoolerEvent",
  717. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
  718. "Program log: Instruction: TransferChecked",
  719. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6200 of 630919 compute units",
  720. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  721. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt invoke [2]",
  722. "Program log: Instruction: Swap",
  723. "Program log: INVARIANT: SWAP",
  724. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  725. "Program log: Instruction: Transfer",
  726. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 539321 compute units",
  727. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  728. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  729. "Program log: Instruction: Transfer",
  730. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 531933 compute units",
  731. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  732. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt consumed 84670 of 610768 compute units",
  733. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt success",
  734. "Program EvenCoolerProgram invoke [2]",
  735. "Program EvenCoolerProgram consumed 2021 of 523272 compute units",
  736. "Program EvenCoolerProgram success",
  737. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt invoke [2]",
  738. "Program log: Instruction: Swap",
  739. "Program log: INVARIANT: SWAP",
  740. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  741. "Program log: Instruction: Transfer",
  742. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 418618 compute units",
  743. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  744. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  745. "Program log: Instruction: Transfer",
  746. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 411230 compute units",
  747. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  748. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt consumed 102212 of 507607 compute units",
  749. "Program HyaB3W9q6XdA5xwpU4XnSZV94htfmbmqJXZcEbRaJutt success",
  750. "Program EvenCoolerProgram invoke [2]",
  751. "Program EvenCoolerProgram consumed 2021 of 402569 compute units",
  752. "Program EvenCoolerProgram success",
  753. "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP invoke [2]",
  754. "Program log: Instruction: Swap",
  755. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  756. "Program log: Instruction: Transfer",
  757. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 371140 compute units",
  758. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  759. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  760. "Program log: Instruction: MintTo",
  761. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4492 of 341800 compute units",
  762. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  763. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]",
  764. "Program log: Instruction: Transfer",
  765. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 334370 compute units",
  766. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  767. "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP consumed 57610 of 386812 compute units",
  768. "Program 9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP success",
  769. "Program EvenCoolerProgram invoke [2]",
  770. "Program EvenCoolerProgram consumed 2021 of 326438 compute units",
  771. "Program EvenCoolerProgram success",
  772. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]",
  773. "Program log: Instruction: TransferChecked",
  774. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6173 of 319725 compute units",
  775. "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
  776. "Program EvenCoolerProgram consumed 345969 of 657583 compute units",
  777. "Program EvenCoolerProgram success", // Outer instruction #2 ends
  778. "Program ComputeBudget111111111111111111111111111111 invoke [1]",
  779. "Program ComputeBudget111111111111111111111111111111 success",
  780. "Program ComputeBudget111111111111111111111111111111 invoke [1]",
  781. "Program ComputeBudget111111111111111111111111111111 success"];
  782. // Converting to Vec<String> as expected in `RpcLogsResponse`
  783. let logs: Vec<String> = logs.iter().map(|&l| l.to_string()).collect();
  784. let program_id_str = "VeryCoolProgram";
  785. // No events returned here. Just ensuring that the function doesn't panic
  786. // due an incorrectly emptied stack.
  787. parse_logs_response::<MockEvent>(
  788. RpcResponse {
  789. context: RpcResponseContext::new(0),
  790. value: RpcLogsResponse {
  791. signature: "".to_string(),
  792. err: None,
  793. logs: logs.to_vec(),
  794. },
  795. },
  796. program_id_str,
  797. )
  798. .unwrap();
  799. Ok(())
  800. }
  801. #[test]
  802. fn test_parse_logs_response_fake_pop() -> Result<()> {
  803. let logs = [
  804. "Program fake111111111111111111111111111111111111112 invoke [1]",
  805. "Program log: i logged success",
  806. "Program log: i logged success",
  807. "Program fake111111111111111111111111111111111111112 consumed 1411 of 200000 compute units",
  808. "Program fake111111111111111111111111111111111111112 success"
  809. ];
  810. // Converting to Vec<String> as expected in `RpcLogsResponse`
  811. let logs: Vec<String> = logs.iter().map(|&l| l.to_string()).collect();
  812. let program_id_str = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
  813. // No events returned here. Just ensuring that the function doesn't panic
  814. // due an incorrectly emptied stack.
  815. parse_logs_response::<MockEvent>(
  816. RpcResponse {
  817. context: RpcResponseContext::new(0),
  818. value: RpcLogsResponse {
  819. signature: "".to_string(),
  820. err: None,
  821. logs: logs.to_vec(),
  822. },
  823. },
  824. program_id_str,
  825. )
  826. .unwrap();
  827. Ok(())
  828. }
  829. }