2021-01-29 08:02:34 -08:00
|
|
|
//! `anchor_client` provides an RPC client to send transactions and fetch
|
|
|
|
//! deserialized accounts from Solana programs written in `anchor_lang`.
|
|
|
|
|
|
|
|
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};
|
|
|
|
use anchor_lang::solana_program::program_error::ProgramError;
|
|
|
|
use anchor_lang::solana_program::pubkey::Pubkey;
|
2021-05-06 14:48:22 -07:00
|
|
|
use anchor_lang::solana_program::system_program;
|
2022-01-12 07:14:25 -08:00
|
|
|
use anchor_lang::{AccountDeserialize, Discriminator, InstructionData, ToAccountMetas};
|
2021-03-24 20:19:29 -07:00
|
|
|
use regex::Regex;
|
2022-01-12 07:14:25 -08:00
|
|
|
use solana_account_decoder::UiAccountEncoding;
|
2021-01-29 08:02:34 -08:00
|
|
|
use solana_client::client_error::ClientError as SolanaClientError;
|
2021-03-24 20:19:29 -07:00
|
|
|
use solana_client::pubsub_client::{PubsubClient, PubsubClientError, PubsubClientSubscription};
|
2021-01-29 08:02:34 -08:00
|
|
|
use solana_client::rpc_client::RpcClient;
|
2022-01-12 07:14:25 -08:00
|
|
|
use solana_client::rpc_config::{
|
|
|
|
RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTransactionLogsConfig,
|
|
|
|
RpcTransactionLogsFilter,
|
|
|
|
};
|
|
|
|
use solana_client::rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType};
|
2021-03-24 20:19:29 -07:00
|
|
|
use solana_client::rpc_response::{Response as RpcResponse, RpcLogsResponse};
|
2022-01-12 07:14:25 -08:00
|
|
|
use solana_sdk::account::Account;
|
|
|
|
use solana_sdk::bs58;
|
2021-01-29 08:02:34 -08:00
|
|
|
use solana_sdk::commitment_config::CommitmentConfig;
|
2021-12-20 11:41:21 -08:00
|
|
|
use solana_sdk::signature::{Signature, Signer};
|
2021-01-29 08:02:34 -08:00
|
|
|
use solana_sdk::transaction::Transaction;
|
|
|
|
use std::convert::Into;
|
2022-01-12 07:14:25 -08:00
|
|
|
use std::iter::Map;
|
2021-12-20 11:41:21 -08:00
|
|
|
use std::rc::Rc;
|
2022-01-12 07:14:25 -08:00
|
|
|
use std::vec::IntoIter;
|
2021-01-29 08:02:34 -08:00
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
pub use anchor_lang;
|
2021-03-24 20:19:29 -07:00
|
|
|
pub use cluster::Cluster;
|
2021-01-29 08:02:34 -08:00
|
|
|
pub use solana_client;
|
|
|
|
pub use solana_sdk;
|
|
|
|
|
2021-03-24 20:19:29 -07:00
|
|
|
mod cluster;
|
|
|
|
|
2022-03-16 11:28:19 -07:00
|
|
|
const PROGRAM_LOG: &str = "Program log: ";
|
|
|
|
const PROGRAM_DATA: &str = "Program data: ";
|
|
|
|
|
2021-03-24 20:19:29 -07:00
|
|
|
/// EventHandle unsubscribes from a program event stream on drop.
|
|
|
|
pub type EventHandle = PubsubClientSubscription<RpcResponse<RpcLogsResponse>>;
|
|
|
|
|
2021-01-29 08:02:34 -08:00
|
|
|
/// Client defines the base configuration for building RPC clients to
|
2022-04-04 11:00:18 -07:00
|
|
|
/// communicate with Anchor programs running on a Solana cluster. It's
|
2021-01-29 08:02:34 -08:00
|
|
|
/// primary use is to build a `Program` client via the `program` method.
|
|
|
|
pub struct Client {
|
|
|
|
cfg: Config,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
2021-12-20 11:41:21 -08:00
|
|
|
pub fn new(cluster: Cluster, payer: Rc<dyn Signer>) -> Self {
|
2021-01-29 08:02:34 -08:00
|
|
|
Self {
|
|
|
|
cfg: Config {
|
2021-03-24 20:19:29 -07:00
|
|
|
cluster,
|
2021-01-29 08:02:34 -08:00
|
|
|
payer,
|
|
|
|
options: None,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-20 11:41:21 -08:00
|
|
|
pub fn new_with_options(
|
|
|
|
cluster: Cluster,
|
|
|
|
payer: Rc<dyn Signer>,
|
|
|
|
options: CommitmentConfig,
|
|
|
|
) -> Self {
|
2021-01-29 08:02:34 -08:00
|
|
|
Self {
|
|
|
|
cfg: Config {
|
2021-03-24 20:19:29 -07:00
|
|
|
cluster,
|
2021-01-29 08:02:34 -08:00
|
|
|
payer,
|
|
|
|
options: Some(options),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn program(&self, program_id: Pubkey) -> Program {
|
|
|
|
Program {
|
|
|
|
program_id,
|
|
|
|
cfg: Config {
|
|
|
|
cluster: self.cfg.cluster.clone(),
|
2021-02-15 21:52:54 -08:00
|
|
|
options: self.cfg.options,
|
2021-12-20 11:41:21 -08:00
|
|
|
payer: self.cfg.payer.clone(),
|
2021-01-29 08:02:34 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internal configuration for a client.
|
2021-12-15 10:20:04 -08:00
|
|
|
#[derive(Debug)]
|
2021-01-29 08:02:34 -08:00
|
|
|
struct Config {
|
2021-03-24 20:19:29 -07:00
|
|
|
cluster: Cluster,
|
2021-12-20 11:41:21 -08:00
|
|
|
payer: Rc<dyn Signer>,
|
2021-01-29 08:02:34 -08:00
|
|
|
options: Option<CommitmentConfig>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Program is the primary client handle to be used to build and send requests.
|
2021-12-15 10:20:04 -08:00
|
|
|
#[derive(Debug)]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub struct Program {
|
|
|
|
program_id: Pubkey,
|
|
|
|
cfg: Config,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Program {
|
|
|
|
pub fn payer(&self) -> Pubkey {
|
|
|
|
self.cfg.payer.pubkey()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a request builder.
|
|
|
|
pub fn request(&self) -> RequestBuilder {
|
2021-05-06 14:48:22 -07:00
|
|
|
RequestBuilder::from(
|
2021-01-29 08:02:34 -08:00
|
|
|
self.program_id,
|
2021-06-27 17:09:46 -07:00
|
|
|
self.cfg.cluster.url(),
|
2021-12-20 11:41:21 -08:00
|
|
|
self.cfg.payer.clone(),
|
2021-02-15 21:52:54 -08:00
|
|
|
self.cfg.options,
|
2021-05-06 14:48:22 -07:00
|
|
|
RequestNamespace::Global,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a request builder for program state.
|
|
|
|
pub fn state_request(&self) -> RequestBuilder {
|
|
|
|
RequestBuilder::from(
|
|
|
|
self.program_id,
|
2021-06-27 17:09:46 -07:00
|
|
|
self.cfg.cluster.url(),
|
2021-12-20 11:41:21 -08:00
|
|
|
self.cfg.payer.clone(),
|
2021-05-06 14:48:22 -07:00
|
|
|
self.cfg.options,
|
|
|
|
RequestNamespace::State { new: false },
|
2021-01-29 08:02:34 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the account at the given address.
|
|
|
|
pub fn account<T: AccountDeserialize>(&self, address: Pubkey) -> Result<T, ClientError> {
|
|
|
|
let rpc_client = RpcClient::new_with_commitment(
|
2021-03-24 20:19:29 -07:00
|
|
|
self.cfg.cluster.url().to_string(),
|
2021-02-15 21:52:54 -08:00
|
|
|
self.cfg.options.unwrap_or_default(),
|
2021-01-29 08:02:34 -08:00
|
|
|
);
|
|
|
|
let account = rpc_client
|
2021-02-26 01:31:48 -08:00
|
|
|
.get_account_with_commitment(&address, CommitmentConfig::processed())?
|
2021-01-29 08:02:34 -08:00
|
|
|
.value
|
|
|
|
.ok_or(ClientError::AccountNotFound)?;
|
|
|
|
let mut data: &[u8] = &account.data;
|
|
|
|
T::try_deserialize(&mut data).map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
2022-01-12 07:14:25 -08:00
|
|
|
/// Returns all program accounts of the given type matching the given filters
|
|
|
|
pub fn accounts<T: AccountDeserialize + Discriminator>(
|
|
|
|
&self,
|
|
|
|
filters: Vec<RpcFilterType>,
|
|
|
|
) -> Result<Vec<(Pubkey, T)>, 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<T: AccountDeserialize + Discriminator>(
|
|
|
|
&self,
|
|
|
|
filters: Vec<RpcFilterType>,
|
|
|
|
) -> Result<ProgramAccountsIterator<T>, 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]))?))
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-06 14:48:22 -07:00
|
|
|
pub fn state<T: AccountDeserialize>(&self) -> Result<T, ClientError> {
|
|
|
|
self.account(anchor_lang::__private::state::address(&self.program_id))
|
|
|
|
}
|
|
|
|
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn rpc(&self) -> RpcClient {
|
|
|
|
RpcClient::new_with_commitment(
|
2021-03-24 20:19:29 -07:00
|
|
|
self.cfg.cluster.url().to_string(),
|
2021-02-15 21:52:54 -08:00
|
|
|
self.cfg.options.unwrap_or_default(),
|
2021-01-29 08:02:34 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn id(&self) -> Pubkey {
|
|
|
|
self.program_id
|
|
|
|
}
|
2021-03-24 20:19:29 -07:00
|
|
|
|
2021-04-10 17:47:14 -07:00
|
|
|
pub fn on<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
|
2021-03-24 20:19:29 -07:00
|
|
|
&self,
|
2021-06-27 17:09:46 -07:00
|
|
|
f: impl Fn(&EventContext, T) + Send + 'static,
|
2021-03-24 20:19:29 -07:00
|
|
|
) -> Result<EventHandle, ClientError> {
|
|
|
|
let addresses = vec![self.program_id.to_string()];
|
|
|
|
let filter = RpcTransactionLogsFilter::Mentions(addresses);
|
|
|
|
let ws_url = self.cfg.cluster.ws_url().to_string();
|
|
|
|
let cfg = RpcTransactionLogsConfig {
|
|
|
|
commitment: self.cfg.options,
|
|
|
|
};
|
|
|
|
let self_program_str = self.program_id.to_string();
|
2021-06-27 17:09:46 -07:00
|
|
|
let (client, receiver) = PubsubClient::logs_subscribe(&ws_url, filter, cfg)?;
|
2021-03-24 20:19:29 -07:00
|
|
|
std::thread::spawn(move || {
|
|
|
|
loop {
|
|
|
|
match receiver.recv() {
|
|
|
|
Ok(logs) => {
|
|
|
|
let ctx = EventContext {
|
|
|
|
signature: logs.value.signature.parse().unwrap(),
|
|
|
|
slot: logs.context.slot,
|
|
|
|
};
|
|
|
|
let mut logs = &logs.value.logs[..];
|
2021-06-27 17:09:46 -07:00
|
|
|
if !logs.is_empty() {
|
2021-03-24 20:19:29 -07:00
|
|
|
if let Ok(mut execution) = Execution::new(&mut logs) {
|
|
|
|
for l in logs {
|
|
|
|
// Parse the log.
|
|
|
|
let (event, new_program, did_pop) = {
|
|
|
|
if self_program_str == execution.program() {
|
2021-06-27 17:09:46 -07:00
|
|
|
handle_program_log(&self_program_str, l).unwrap_or_else(
|
|
|
|
|e| {
|
2021-11-14 06:46:18 -08:00
|
|
|
println!("Unable to parse log: {}", e);
|
2021-03-24 20:19:29 -07:00
|
|
|
std::process::exit(1);
|
2021-06-27 17:09:46 -07:00
|
|
|
},
|
|
|
|
)
|
2021-03-24 20:19:29 -07:00
|
|
|
} else {
|
|
|
|
let (program, did_pop) =
|
2021-06-27 17:09:46 -07:00
|
|
|
handle_system_log(&self_program_str, l);
|
2021-03-24 20:19:29 -07:00
|
|
|
(None, program, did_pop)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// Emit the event.
|
|
|
|
if let Some(e) = event {
|
|
|
|
f(&ctx, e);
|
|
|
|
}
|
|
|
|
// Switch program context on CPI.
|
|
|
|
if let Some(new_program) = new_program {
|
|
|
|
execution.push(new_program);
|
|
|
|
}
|
|
|
|
// Program returned.
|
|
|
|
if did_pop {
|
|
|
|
execution.pop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_err) => {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
Ok(client)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-12 07:14:25 -08:00
|
|
|
/// 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<T> {
|
|
|
|
inner: Map<IntoIter<(Pubkey, Account)>, AccountConverterFunction<T>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Function type that accepts solana accounts and returns deserialized anchor accounts
|
|
|
|
type AccountConverterFunction<T> = fn((Pubkey, Account)) -> Result<(Pubkey, T), ClientError>;
|
|
|
|
|
|
|
|
impl<T> Iterator for ProgramAccountsIterator<T> {
|
|
|
|
type Item = Result<(Pubkey, T), ClientError>;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
self.inner.next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 17:47:14 -07:00
|
|
|
fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
|
2021-03-24 20:19:29 -07:00
|
|
|
self_program_str: &str,
|
|
|
|
l: &str,
|
|
|
|
) -> Result<(Option<T>, Option<String>, bool), ClientError> {
|
|
|
|
// Log emitted from the current program.
|
2022-03-16 11:28:19 -07:00
|
|
|
if let Some(log) = l
|
|
|
|
.strip_prefix(PROGRAM_LOG)
|
|
|
|
.or_else(|| l.strip_prefix(PROGRAM_DATA))
|
|
|
|
{
|
2021-11-28 20:38:51 -08:00
|
|
|
let borsh_bytes = match anchor_lang::__private::base64::decode(&log) {
|
|
|
|
Ok(borsh_bytes) => borsh_bytes,
|
|
|
|
_ => {
|
|
|
|
#[cfg(feature = "debug")]
|
|
|
|
println!("Could not base64 decode log: {}", log);
|
|
|
|
return Ok((None, None, false));
|
|
|
|
}
|
|
|
|
};
|
2021-03-24 20:19:29 -07:00
|
|
|
|
|
|
|
let mut slice: &[u8] = &borsh_bytes[..];
|
|
|
|
let disc: [u8; 8] = {
|
|
|
|
let mut disc = [0; 8];
|
|
|
|
disc.copy_from_slice(&borsh_bytes[..8]);
|
|
|
|
slice = &slice[8..];
|
|
|
|
disc
|
|
|
|
};
|
|
|
|
let mut event = None;
|
|
|
|
if disc == T::discriminator() {
|
|
|
|
let e: T = anchor_lang::AnchorDeserialize::deserialize(&mut slice)
|
|
|
|
.map_err(|e| ClientError::LogParseError(e.to_string()))?;
|
|
|
|
event = Some(e);
|
|
|
|
}
|
|
|
|
Ok((event, None, false))
|
|
|
|
}
|
|
|
|
// System log.
|
|
|
|
else {
|
2021-06-27 17:09:46 -07:00
|
|
|
let (program, did_pop) = handle_system_log(self_program_str, l);
|
2021-03-24 20:19:29 -07:00
|
|
|
Ok((None, program, did_pop))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_system_log(this_program_str: &str, log: &str) -> (Option<String>, bool) {
|
|
|
|
if log.starts_with(&format!("Program {} log:", this_program_str)) {
|
|
|
|
(Some(this_program_str.to_string()), false)
|
|
|
|
} else if log.contains("invoke") {
|
|
|
|
(Some("cpi".to_string()), false) // Any string will do.
|
|
|
|
} else {
|
|
|
|
let re = Regex::new(r"^Program (.*) success*$").unwrap();
|
|
|
|
if re.is_match(log) {
|
|
|
|
(None, true)
|
|
|
|
} else {
|
|
|
|
(None, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Execution {
|
|
|
|
stack: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Execution {
|
|
|
|
pub fn new(logs: &mut &[String]) -> Result<Self, ClientError> {
|
|
|
|
let l = &logs[0];
|
|
|
|
*logs = &logs[1..];
|
|
|
|
|
|
|
|
let re = Regex::new(r"^Program (.*) invoke.*$").unwrap();
|
|
|
|
let c = re
|
|
|
|
.captures(l)
|
2021-06-27 17:09:46 -07:00
|
|
|
.ok_or_else(|| ClientError::LogParseError(l.to_string()))?;
|
2021-03-24 20:19:29 -07:00
|
|
|
let program = c
|
|
|
|
.get(1)
|
2021-06-27 17:09:46 -07:00
|
|
|
.ok_or_else(|| ClientError::LogParseError(l.to_string()))?
|
2021-03-24 20:19:29 -07:00
|
|
|
.as_str()
|
|
|
|
.to_string();
|
|
|
|
Ok(Self {
|
|
|
|
stack: vec![program],
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn program(&self) -> String {
|
2021-06-27 17:09:46 -07:00
|
|
|
assert!(!self.stack.is_empty());
|
2021-03-24 20:19:29 -07:00
|
|
|
self.stack[self.stack.len() - 1].clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn push(&mut self, new_program: String) {
|
|
|
|
self.stack.push(new_program);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pop(&mut self) {
|
2021-06-27 17:09:46 -07:00
|
|
|
assert!(!self.stack.is_empty());
|
2021-03-24 20:19:29 -07:00
|
|
|
self.stack.pop().unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct EventContext {
|
|
|
|
pub signature: Signature,
|
|
|
|
pub slot: u64,
|
2021-01-29 08:02:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum ClientError {
|
|
|
|
#[error("Account not found")]
|
|
|
|
AccountNotFound,
|
|
|
|
#[error("{0}")]
|
2022-02-20 14:28:24 -08:00
|
|
|
AnchorError(#[from] anchor_lang::error::Error),
|
|
|
|
#[error("{0}")]
|
2021-01-29 08:02:34 -08:00
|
|
|
ProgramError(#[from] ProgramError),
|
|
|
|
#[error("{0}")]
|
|
|
|
SolanaClientError(#[from] SolanaClientError),
|
2021-03-24 20:19:29 -07:00
|
|
|
#[error("{0}")]
|
|
|
|
SolanaClientPubsubError(#[from] PubsubClientError),
|
|
|
|
#[error("Unable to parse log: {0}")]
|
|
|
|
LogParseError(String),
|
2021-01-29 08:02:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// `RequestBuilder` provides a builder interface to create and send
|
|
|
|
/// transactions to a cluster.
|
|
|
|
pub struct RequestBuilder<'a> {
|
|
|
|
cluster: String,
|
|
|
|
program_id: Pubkey,
|
|
|
|
accounts: Vec<AccountMeta>,
|
|
|
|
options: CommitmentConfig,
|
|
|
|
instructions: Vec<Instruction>,
|
2021-12-20 11:41:21 -08:00
|
|
|
payer: Rc<dyn Signer>,
|
2021-01-29 08:02:34 -08:00
|
|
|
// Serialized instruction data for the target RPC.
|
|
|
|
instruction_data: Option<Vec<u8>>,
|
|
|
|
signers: Vec<&'a dyn Signer>,
|
2021-05-06 14:48:22 -07:00
|
|
|
// True if the user is sending a state instruction.
|
|
|
|
namespace: RequestNamespace,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(PartialEq)]
|
|
|
|
pub enum RequestNamespace {
|
|
|
|
Global,
|
|
|
|
State {
|
|
|
|
// True if the request is to the state's new ctor.
|
|
|
|
new: bool,
|
|
|
|
},
|
|
|
|
Interface,
|
2021-01-29 08:02:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> RequestBuilder<'a> {
|
2021-05-06 14:48:22 -07:00
|
|
|
pub fn from(
|
2021-01-29 08:02:34 -08:00
|
|
|
program_id: Pubkey,
|
|
|
|
cluster: &str,
|
2021-12-20 11:41:21 -08:00
|
|
|
payer: Rc<dyn Signer>,
|
2021-01-29 08:02:34 -08:00
|
|
|
options: Option<CommitmentConfig>,
|
2021-05-06 14:48:22 -07:00
|
|
|
namespace: RequestNamespace,
|
2021-01-29 08:02:34 -08:00
|
|
|
) -> Self {
|
|
|
|
Self {
|
|
|
|
program_id,
|
|
|
|
payer,
|
|
|
|
cluster: cluster.to_string(),
|
|
|
|
accounts: Vec::new(),
|
2021-02-15 21:52:54 -08:00
|
|
|
options: options.unwrap_or_default(),
|
2021-01-29 08:02:34 -08:00
|
|
|
instructions: Vec::new(),
|
|
|
|
instruction_data: None,
|
|
|
|
signers: Vec::new(),
|
2021-05-06 14:48:22 -07:00
|
|
|
namespace,
|
2021-01-29 08:02:34 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-12-20 11:41:21 -08:00
|
|
|
pub fn payer(mut self, payer: Rc<dyn Signer>) -> Self {
|
2021-01-29 08:02:34 -08:00
|
|
|
self.payer = payer;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn cluster(mut self, url: &str) -> Self {
|
|
|
|
self.cluster = url.to_string();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn instruction(mut self, ix: Instruction) -> Self {
|
|
|
|
self.instructions.push(ix);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn program(mut self, program_id: Pubkey) -> Self {
|
|
|
|
self.program_id = program_id;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn accounts(mut self, accounts: impl ToAccountMetas) -> Self {
|
|
|
|
let mut metas = accounts.to_account_metas(None);
|
|
|
|
self.accounts.append(&mut metas);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn options(mut self, options: CommitmentConfig) -> Self {
|
|
|
|
self.options = options;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-02-06 00:28:33 -08:00
|
|
|
pub fn args(mut self, args: impl InstructionData) -> Self {
|
|
|
|
self.instruction_data = Some(args.data());
|
2021-01-29 08:02:34 -08:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-05-06 14:48:22 -07:00
|
|
|
/// Invokes the `#[state]`'s `new` constructor.
|
2021-06-27 17:09:46 -07:00
|
|
|
#[allow(clippy::wrong_self_convention)]
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-05-06 14:48:22 -07:00
|
|
|
pub fn new(mut self, args: impl InstructionData) -> Self {
|
|
|
|
assert!(self.namespace == RequestNamespace::State { new: false });
|
|
|
|
self.namespace = RequestNamespace::State { new: true };
|
|
|
|
self.instruction_data = Some(args.data());
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-12-18 15:25:04 -08:00
|
|
|
#[must_use]
|
2021-01-29 08:02:34 -08:00
|
|
|
pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
|
|
|
|
self.signers.push(signer);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-11-05 06:09:41 -07:00
|
|
|
pub fn instructions(&self) -> Result<Vec<Instruction>, ClientError> {
|
|
|
|
let mut accounts = match self.namespace {
|
|
|
|
RequestNamespace::State { new } => match new {
|
|
|
|
false => vec![AccountMeta::new(
|
|
|
|
anchor_lang::__private::state::address(&self.program_id),
|
|
|
|
false,
|
|
|
|
)],
|
|
|
|
true => vec![
|
|
|
|
AccountMeta::new_readonly(self.payer.pubkey(), true),
|
|
|
|
AccountMeta::new(
|
2021-05-06 14:48:22 -07:00
|
|
|
anchor_lang::__private::state::address(&self.program_id),
|
|
|
|
false,
|
2021-11-05 06:09:41 -07:00
|
|
|
),
|
|
|
|
AccountMeta::new_readonly(
|
|
|
|
Pubkey::find_program_address(&[], &self.program_id).0,
|
|
|
|
false,
|
|
|
|
),
|
|
|
|
AccountMeta::new_readonly(system_program::ID, false),
|
|
|
|
AccountMeta::new_readonly(self.program_id, false),
|
|
|
|
],
|
|
|
|
},
|
|
|
|
_ => Vec::new(),
|
2021-05-06 14:48:22 -07:00
|
|
|
};
|
2021-11-05 06:09:41 -07:00
|
|
|
accounts.extend_from_slice(&self.accounts);
|
|
|
|
|
|
|
|
let mut instructions = self.instructions.clone();
|
|
|
|
if let Some(ix_data) = &self.instruction_data {
|
2021-01-29 08:02:34 -08:00
|
|
|
instructions.push(Instruction {
|
|
|
|
program_id: self.program_id,
|
2021-11-05 06:09:41 -07:00
|
|
|
data: ix_data.clone(),
|
2021-05-06 14:48:22 -07:00
|
|
|
accounts,
|
2021-01-29 08:02:34 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-11-05 06:09:41 -07:00
|
|
|
Ok(instructions)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn send(self) -> Result<Signature, ClientError> {
|
|
|
|
let instructions = self.instructions()?;
|
|
|
|
|
2021-01-29 08:02:34 -08:00
|
|
|
let mut signers = self.signers;
|
2021-12-20 11:41:21 -08:00
|
|
|
signers.push(&*self.payer);
|
2021-01-29 08:02:34 -08:00
|
|
|
|
|
|
|
let rpc_client = RpcClient::new_with_commitment(self.cluster, self.options);
|
|
|
|
|
|
|
|
let tx = {
|
2022-03-26 17:28:55 -07:00
|
|
|
let latest_hash = rpc_client.get_latest_blockhash()?;
|
2021-01-29 08:02:34 -08:00
|
|
|
Transaction::new_signed_with_payer(
|
|
|
|
&instructions,
|
|
|
|
Some(&self.payer.pubkey()),
|
|
|
|
&signers,
|
2022-03-26 17:28:55 -07:00
|
|
|
latest_hash,
|
2021-01-29 08:02:34 -08:00
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
rpc_client
|
|
|
|
.send_and_confirm_transaction(&tx)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 20:19:29 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
|
|
fn new_execution() {
|
|
|
|
let mut logs: &[String] =
|
|
|
|
&["Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw invoke [1]".to_string()];
|
|
|
|
let exe = Execution::new(&mut logs).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
exe.stack[0],
|
|
|
|
"7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw".to_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn handle_system_log_pop() {
|
|
|
|
let log = "Program 7Y8VDzehoewALqJfyxZYMgYCnMTCDhWuGfJKUvjYWATw success";
|
|
|
|
let (program, did_pop) = handle_system_log("asdf", log);
|
|
|
|
assert_eq!(program, None);
|
2021-06-27 17:09:46 -07:00
|
|
|
assert!(did_pop);
|
2021-03-24 20:19:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn handle_system_log_no_pop() {
|
|
|
|
let log = "Program 7swsTUiQ6KUK4uFYquQKg4epFRsBnvbrTf2fZQCa2sTJ qwer";
|
|
|
|
let (program, did_pop) = handle_system_log("asdf", log);
|
|
|
|
assert_eq!(program, None);
|
2021-06-27 17:09:46 -07:00
|
|
|
assert!(!did_pop);
|
2021-03-24 20:19:29 -07:00
|
|
|
}
|
|
|
|
}
|