From a8f820c391f4a7f0a48c26f97b70c3dae3492026 Mon Sep 17 00:00:00 2001 From: Drew Nutter Date: Wed, 12 Jan 2022 12:14:25 -0300 Subject: [PATCH] client: add typed getProgramAccounts support (#1297) --- Cargo.lock | 1 + client/Cargo.toml | 1 + client/src/lib.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f86efe21..b9c19c57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,7 @@ dependencies = [ "anyhow", "regex", "serde", + "solana-account-decoder", "solana-client", "solana-sdk", "thiserror", diff --git a/client/Cargo.toml b/client/Cargo.toml index 6a9414d3..f4572c8c 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -16,5 +16,6 @@ regex = "1.4.5" serde = { version = "1.0.122", features = ["derive"] } solana-client = "1.7.2" solana-sdk = "1.7.2" +solana-account-decoder = "1.7.2" thiserror = "1.0.20" url = "2.2.2" diff --git a/client/src/lib.rs b/client/src/lib.rs index 33e11049..65da3c5f 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -5,18 +5,27 @@ use anchor_lang::solana_program::instruction::{AccountMeta, Instruction}; use anchor_lang::solana_program::program_error::ProgramError; use anchor_lang::solana_program::pubkey::Pubkey; use anchor_lang::solana_program::system_program; -use anchor_lang::{AccountDeserialize, InstructionData, ToAccountMetas}; +use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas}; use regex::Regex; +use solana_account_decoder::UiAccountEncoding; use solana_client::client_error::ClientError as SolanaClientError; use solana_client::pubsub_client::{PubsubClient, PubsubClientError, PubsubClientSubscription}; use solana_client::rpc_client::RpcClient; -use solana_client::rpc_config::{RpcTransactionLogsConfig, RpcTransactionLogsFilter}; +use solana_client::rpc_config::{ + RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTransactionLogsConfig, + RpcTransactionLogsFilter, +}; +use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}; use solana_client::rpc_response::{Response as RpcResponse, RpcLogsResponse}; +use solana_sdk::account::Account; +use solana_sdk::bs58; use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::signature::{Signature, Signer}; use solana_sdk::transaction::Transaction; use std::convert::Into; +use std::iter::Map; use std::rc::Rc; +use std::vec::IntoIter; use thiserror::Error; pub use anchor_lang; @@ -129,6 +138,45 @@ impl Program { T::try_deserialize(&mut data).map_err(Into::into) } + /// Returns all program accounts of the given type matching the given filters + pub fn accounts( + &self, + filters: Vec, + ) -> Result, ClientError> { + self.accounts_lazy(filters)?.collect() + } + + /// Returns all program accounts of the given type matching the given filters as an iterator + /// Deserialization is executed lazily + pub fn accounts_lazy( + &self, + filters: Vec, + ) -> Result, ClientError> { + let account_type_filter = RpcFilterType::Memcmp(Memcmp { + offset: 0, + bytes: MemcmpEncodedBytes::Base58(bs58::encode(T::discriminator()).into_string()), + encoding: None, + }); + let config = RpcProgramAccountsConfig { + filters: Some([vec![account_type_filter], filters].concat()), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + data_slice: None, + commitment: None, + }, + with_context: None, + }; + Ok(ProgramAccountsIterator { + inner: self + .rpc() + .get_program_accounts_with_config(&self.id(), config)? + .into_iter() + .map(|(key, account)| { + Ok((key, T::try_deserialize(&mut (&account.data as &[u8]))?)) + }), + }) + } + pub fn state(&self) -> Result { self.account(anchor_lang::__private::state::address(&self.program_id)) } @@ -209,6 +257,23 @@ impl Program { } } +/// Iterator with items of type (Pubkey, T). Used to lazily deserialize account structs. +/// Wrapper type hides the inner type from usages so the implementation can be changed. +pub struct ProgramAccountsIterator { + inner: Map, AccountConverterFunction>, +} + +/// Function type that accepts solana accounts and returns deserialized anchor accounts +type AccountConverterFunction = fn((Pubkey, Account)) -> Result<(Pubkey, T), ClientError>; + +impl Iterator for ProgramAccountsIterator { + type Item = Result<(Pubkey, T), ClientError>; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + fn handle_program_log( self_program_str: &str, l: &str,