Merge pull request #51 from project-serum/armani/clientrs
This commit is contained in:
commit
53e0a58648
|
@ -120,6 +120,16 @@ dependencies = [
|
|||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"solana-client",
|
||||
"solana-sdk",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-derive-accounts"
|
||||
version = "0.0.0-alpha.0"
|
||||
|
|
|
@ -25,6 +25,7 @@ thiserror = "1.0.20"
|
|||
[workspace]
|
||||
members = [
|
||||
"cli",
|
||||
"client",
|
||||
"syn",
|
||||
"attribute/*",
|
||||
"derive/*",
|
||||
|
|
|
@ -500,7 +500,9 @@ fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
|
|||
}
|
||||
|
||||
// Run migration script.
|
||||
migrate(&url)?;
|
||||
if Path::new("migrations/deploy.js").exists() {
|
||||
migrate(&url)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "anchor-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Armani Ferrante <armaniferrante@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../" }
|
||||
solana-client = "1.5.0"
|
||||
solana-sdk = "1.5.0"
|
||||
thiserror = "1.0.20"
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "example"
|
||||
version = "0.1.0"
|
||||
authors = ["Armani Ferrante <armaniferrante@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
anchor-client = { path = "../" }
|
||||
basic-2 = { path = "../../examples/tutorial/basic-2/programs/basic-2", features = ["no-entrypoint"] }
|
||||
composite = { path = "../../examples/composite/programs/composite", features = ["no-entrypoint"] }
|
||||
shellexpand = "2.1.0"
|
||||
anyhow = "1.0.32"
|
||||
rand = "0.7.3"
|
|
@ -0,0 +1,158 @@
|
|||
use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
|
||||
use anchor_client::solana_sdk::signature::read_keypair_file;
|
||||
use anchor_client::solana_sdk::signature::{Keypair, Signer};
|
||||
use anchor_client::solana_sdk::system_instruction;
|
||||
use anchor_client::solana_sdk::sysvar;
|
||||
use anchor_client::Client;
|
||||
use anyhow::Result;
|
||||
// The `accounts` and `instructions` modules are generated by the framework.
|
||||
use basic_2::accounts::CreateAuthor;
|
||||
use basic_2::instruction::Basic2Instruction;
|
||||
use basic_2::Author;
|
||||
// The `accounts` and `instructions` modules are generated by the framework.
|
||||
use composite::accounts::{Bar, CompositeUpdate, Foo, Initialize};
|
||||
use composite::instruction::CompositeInstruction;
|
||||
use composite::{DummyA, DummyB};
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
// Wallet and cluster params.
|
||||
let payer = read_keypair_file(&shellexpand::tilde("~/.config/solana/id.json"))
|
||||
.expect("Example requires a keypair file");
|
||||
let url = "http://localhost:8899";
|
||||
let opts = CommitmentConfig::recent();
|
||||
|
||||
// Client.
|
||||
let client = Client::new_with_options(url, payer, opts);
|
||||
|
||||
// Run tests.
|
||||
composite(&client)?;
|
||||
basic_2(&client)?;
|
||||
|
||||
// Success.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Runs a client for examples/tutorial/composite.
|
||||
//
|
||||
// Make sure to run a localnet with the program deploy to run this example.
|
||||
fn composite(client: &Client) -> Result<()> {
|
||||
// Deployed program to execute.
|
||||
let pid = "75TykCe6b1oBa8JWVvfkXsFbZydgqi3QfRjgBEJJwy2g"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
// Program client.
|
||||
let program = client.program(pid);
|
||||
|
||||
// `Initialize` parameters.
|
||||
let dummy_a = Keypair::generate(&mut OsRng);
|
||||
let dummy_b = Keypair::generate(&mut OsRng);
|
||||
|
||||
// Build and send a transaction.
|
||||
program
|
||||
.request()
|
||||
.instruction(system_instruction::create_account(
|
||||
&program.payer(),
|
||||
&dummy_a.pubkey(),
|
||||
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
||||
500,
|
||||
&program.id(),
|
||||
))
|
||||
.instruction(system_instruction::create_account(
|
||||
&program.payer(),
|
||||
&dummy_b.pubkey(),
|
||||
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
||||
500,
|
||||
&program.id(),
|
||||
))
|
||||
.signer(&dummy_a)
|
||||
.signer(&dummy_b)
|
||||
.accounts(Initialize {
|
||||
dummy_a: dummy_a.pubkey(),
|
||||
dummy_b: dummy_b.pubkey(),
|
||||
rent: sysvar::rent::ID,
|
||||
})
|
||||
.args(CompositeInstruction::Initialize)
|
||||
.send()?;
|
||||
|
||||
// Assert the transaction worked.
|
||||
let dummy_a_account: DummyA = program.account(dummy_a.pubkey())?;
|
||||
let dummy_b_account: DummyB = program.account(dummy_b.pubkey())?;
|
||||
assert_eq!(dummy_a_account.data, 0);
|
||||
assert_eq!(dummy_b_account.data, 0);
|
||||
|
||||
// Build and send another transaction, using composite account parameters.
|
||||
program
|
||||
.request()
|
||||
.accounts(CompositeUpdate {
|
||||
foo: Foo {
|
||||
dummy_a: dummy_a.pubkey(),
|
||||
},
|
||||
bar: Bar {
|
||||
dummy_b: dummy_b.pubkey(),
|
||||
},
|
||||
})
|
||||
.args(CompositeInstruction::CompositeUpdate {
|
||||
dummy_a: 1234,
|
||||
dummy_b: 4321,
|
||||
})
|
||||
.send()?;
|
||||
|
||||
// Assert the transaction worked.
|
||||
let dummy_a_account: DummyA = program.account(dummy_a.pubkey())?;
|
||||
let dummy_b_account: DummyB = program.account(dummy_b.pubkey())?;
|
||||
assert_eq!(dummy_a_account.data, 1234);
|
||||
assert_eq!(dummy_b_account.data, 4321);
|
||||
|
||||
println!("Success!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Runs a client for examples/tutorial/basic-2.
|
||||
//
|
||||
// Make sure to run a localnet with the program deploy to run this example.
|
||||
fn basic_2(client: &Client) -> Result<()> {
|
||||
// Deployed program to execute.
|
||||
let program_id = "FU3yvTEGTFUdMa6qAjVyKfNcDU6hb4yXbPhz8f5iFyvE"
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let program = client.program(program_id);
|
||||
|
||||
// `CreateAuthor` parameters.
|
||||
let author = Keypair::generate(&mut OsRng);
|
||||
let authority = program.payer();
|
||||
|
||||
// Build and send a transaction.
|
||||
program
|
||||
.request()
|
||||
.instruction(system_instruction::create_account(
|
||||
&authority,
|
||||
&author.pubkey(),
|
||||
program.rpc().get_minimum_balance_for_rent_exemption(500)?,
|
||||
500,
|
||||
&program_id,
|
||||
))
|
||||
.signer(&author)
|
||||
.accounts(CreateAuthor {
|
||||
author: author.pubkey(),
|
||||
rent: sysvar::rent::ID,
|
||||
})
|
||||
.args(Basic2Instruction::CreateAuthor {
|
||||
authority,
|
||||
name: "My Book Name".to_string(),
|
||||
})
|
||||
.send()?;
|
||||
|
||||
let author_account: Author = program.account(author.pubkey())?;
|
||||
|
||||
assert_eq!(author_account.authority, authority);
|
||||
assert_eq!(author_account.name, "My Book Name".to_string());
|
||||
|
||||
println!("Success!");
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
//! `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;
|
||||
use anchor_lang::{AccountDeserialize, AnchorSerialize, ToAccountMetas};
|
||||
use solana_client::client_error::ClientError as SolanaClientError;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::commitment_config::CommitmentConfig;
|
||||
use solana_sdk::signature::{Keypair, Signature, Signer};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::convert::Into;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use anchor_lang;
|
||||
pub use solana_client;
|
||||
pub use solana_sdk;
|
||||
|
||||
/// Client defines the base configuration for building RPC clients to
|
||||
/// communitcate with Anchor programs running on a Solana cluster. It's
|
||||
/// primary use is to build a `Program` client via the `program` method.
|
||||
pub struct Client {
|
||||
cfg: Config,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(cluster: &str, payer: Keypair) -> Self {
|
||||
Self {
|
||||
cfg: Config {
|
||||
cluster: cluster.to_string(),
|
||||
payer,
|
||||
options: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_options(cluster: &str, payer: Keypair, options: CommitmentConfig) -> Self {
|
||||
Self {
|
||||
cfg: Config {
|
||||
cluster: cluster.to_string(),
|
||||
payer,
|
||||
options: Some(options),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn program(&self, program_id: Pubkey) -> Program {
|
||||
Program {
|
||||
program_id,
|
||||
cfg: Config {
|
||||
cluster: self.cfg.cluster.clone(),
|
||||
options: self.cfg.options.clone(),
|
||||
payer: Keypair::from_bytes(&self.cfg.payer.to_bytes()).unwrap(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal configuration for a client.
|
||||
struct Config {
|
||||
cluster: String,
|
||||
payer: Keypair,
|
||||
options: Option<CommitmentConfig>,
|
||||
}
|
||||
|
||||
/// Program is the primary client handle to be used to build and send requests.
|
||||
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 {
|
||||
RequestBuilder::new(
|
||||
self.program_id,
|
||||
&self.cfg.cluster,
|
||||
Keypair::from_bytes(&self.cfg.payer.to_bytes()).unwrap(),
|
||||
self.cfg.options.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// 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(
|
||||
self.cfg.cluster.clone(),
|
||||
self.cfg.options.unwrap_or(Default::default()),
|
||||
);
|
||||
let account = rpc_client
|
||||
.get_account_with_commitment(&address, CommitmentConfig::recent())?
|
||||
.value
|
||||
.ok_or(ClientError::AccountNotFound)?;
|
||||
let mut data: &[u8] = &account.data;
|
||||
T::try_deserialize(&mut data).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn rpc(&self) -> RpcClient {
|
||||
RpcClient::new_with_commitment(
|
||||
self.cfg.cluster.clone(),
|
||||
self.cfg.options.unwrap_or(Default::default()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Pubkey {
|
||||
self.program_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientError {
|
||||
#[error("Account not found")]
|
||||
AccountNotFound,
|
||||
#[error("{0}")]
|
||||
ProgramError(#[from] ProgramError),
|
||||
#[error("{0}")]
|
||||
SolanaClientError(#[from] SolanaClientError),
|
||||
}
|
||||
|
||||
/// `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>,
|
||||
payer: Keypair,
|
||||
// Serialized instruction data for the target RPC.
|
||||
instruction_data: Option<Vec<u8>>,
|
||||
signers: Vec<&'a dyn Signer>,
|
||||
}
|
||||
|
||||
impl<'a> RequestBuilder<'a> {
|
||||
pub fn new(
|
||||
program_id: Pubkey,
|
||||
cluster: &str,
|
||||
payer: Keypair,
|
||||
options: Option<CommitmentConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
program_id,
|
||||
payer,
|
||||
cluster: cluster.to_string(),
|
||||
accounts: Vec::new(),
|
||||
options: options.unwrap_or(Default::default()),
|
||||
instructions: Vec::new(),
|
||||
instruction_data: None,
|
||||
signers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payer(mut self, payer: Keypair) -> Self {
|
||||
self.payer = payer;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cluster(mut self, url: &str) -> Self {
|
||||
self.cluster = url.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn instruction(mut self, ix: Instruction) -> Self {
|
||||
self.instructions.push(ix);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn program(mut self, program_id: Pubkey) -> Self {
|
||||
self.program_id = program_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn accounts(mut self, accounts: impl ToAccountMetas) -> Self {
|
||||
let mut metas = accounts.to_account_metas(None);
|
||||
self.accounts.append(&mut metas);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn options(mut self, options: CommitmentConfig) -> Self {
|
||||
self.options = options;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn args(mut self, args: impl AnchorSerialize) -> Self {
|
||||
let data = args.try_to_vec().expect("Should always serialize");
|
||||
self.instruction_data = Some(data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn signer(mut self, signer: &'a dyn Signer) -> Self {
|
||||
self.signers.push(signer);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn send(self) -> Result<Signature, ClientError> {
|
||||
let mut instructions = self.instructions;
|
||||
if let Some(ix_data) = self.instruction_data {
|
||||
instructions.push(Instruction {
|
||||
program_id: self.program_id,
|
||||
data: ix_data,
|
||||
accounts: self.accounts,
|
||||
});
|
||||
}
|
||||
|
||||
let mut signers = self.signers;
|
||||
signers.push(&self.payer);
|
||||
|
||||
let rpc_client = RpcClient::new_with_commitment(self.cluster, self.options);
|
||||
|
||||
let tx = {
|
||||
let (recent_hash, _fee_calc) = rpc_client.get_recent_blockhash()?;
|
||||
Transaction::new_signed_with_payer(
|
||||
&instructions,
|
||||
Some(&self.payer.pubkey()),
|
||||
&signers,
|
||||
recent_hash,
|
||||
)
|
||||
};
|
||||
|
||||
rpc_client
|
||||
.send_and_confirm_transaction(&tx)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
|
||||
Field, Ty,
|
||||
};
|
||||
use heck::SnakeCase;
|
||||
use quote::quote;
|
||||
|
||||
pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
||||
|
@ -138,7 +139,115 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
}
|
||||
};
|
||||
|
||||
let account_mod_name: proc_macro2::TokenStream = format!(
|
||||
"__client_accounts_{}",
|
||||
accs.ident.to_string().to_snake_case()
|
||||
)
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &AccountField| match f {
|
||||
AccountField::AccountsStruct(s) => {
|
||||
let name = &s.ident;
|
||||
let symbol: proc_macro2::TokenStream = format!(
|
||||
"__client_accounts_{0}::{1}",
|
||||
s.symbol.to_snake_case(),
|
||||
s.symbol,
|
||||
)
|
||||
.parse()
|
||||
.unwrap();
|
||||
quote! {
|
||||
pub #name: #symbol
|
||||
}
|
||||
}
|
||||
AccountField::Field(f) => {
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
pub #name: anchor_lang::solana_program::pubkey::Pubkey
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f: &AccountField| match f {
|
||||
AccountField::AccountsStruct(s) => {
|
||||
let name = &s.ident;
|
||||
quote! {
|
||||
account_metas.extend(self.#name.to_account_metas(None));
|
||||
}
|
||||
}
|
||||
AccountField::Field(f) => {
|
||||
let is_signer = match f.is_signer {
|
||||
false => quote! {false},
|
||||
true => quote! {true},
|
||||
};
|
||||
let meta = match f.is_mut {
|
||||
false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
|
||||
true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
|
||||
};
|
||||
let name = &f.ident;
|
||||
quote! {
|
||||
account_metas.push(#meta(self.#name, #is_signer));
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Re-export all composite account structs (i.e. other structs deriving
|
||||
// accounts embedded into this struct. Required because, these embedded
|
||||
// structs are *not* visible from the #[program] macro, which is responsible
|
||||
// for generating the `accounts` mod, which aggregates all the the generated
|
||||
// accounts used for structs.
|
||||
let re_exports: Vec<proc_macro2::TokenStream> = accs
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|f: &AccountField| match f {
|
||||
AccountField::AccountsStruct(s) => Some(s),
|
||||
AccountField::Field(_) => None,
|
||||
})
|
||||
.map(|f: &CompositeField| {
|
||||
let symbol: proc_macro2::TokenStream = format!(
|
||||
"__client_accounts_{0}::{1}",
|
||||
f.symbol.to_snake_case(),
|
||||
f.symbol,
|
||||
)
|
||||
.parse()
|
||||
.unwrap();
|
||||
quote! {
|
||||
pub use #symbol;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
|
||||
mod #account_mod_name {
|
||||
use super::*;
|
||||
use anchor_lang::prelude::borsh;
|
||||
#(#re_exports)*
|
||||
|
||||
#[derive(anchor_lang::AnchorSerialize)]
|
||||
pub struct #name {
|
||||
#(#account_struct_fields),*
|
||||
}
|
||||
|
||||
impl anchor_lang::ToAccountMetas for #name {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
|
||||
let mut account_metas = vec![];
|
||||
|
||||
#(#account_struct_metas)*
|
||||
|
||||
account_metas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
||||
#[inline(never)]
|
||||
fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::parser;
|
||||
use crate::{Program, RpcArg, State};
|
||||
use heck::CamelCase;
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use quote::quote;
|
||||
|
||||
pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
||||
|
@ -11,12 +11,13 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
let methods = generate_methods(&program);
|
||||
let instruction = generate_instruction(&program);
|
||||
let cpi = generate_cpi(&program);
|
||||
let accounts = generate_accounts(&program);
|
||||
|
||||
quote! {
|
||||
// Import everything in the mod, in case the user wants to put types
|
||||
// in there.
|
||||
// TODO: remove once we allow segmented paths in `Accounts` structs.
|
||||
use #mod_name::*;
|
||||
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
anchor_lang::solana_program::entrypoint!(entry);
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
|
@ -29,10 +30,10 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
}
|
||||
}
|
||||
let mut data: &[u8] = instruction_data;
|
||||
let ix = __private::instruction::#instruction_name::deserialize(&mut data)
|
||||
let ix = instruction::#instruction_name::deserialize(&mut data)
|
||||
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
|
||||
|
||||
#dispatch
|
||||
#dispatch
|
||||
}
|
||||
|
||||
// Create a private module to not clutter the program's namespace.
|
||||
|
@ -40,10 +41,12 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
use super::*;
|
||||
|
||||
#handlers_non_inlined
|
||||
|
||||
#instruction
|
||||
}
|
||||
|
||||
#accounts
|
||||
|
||||
#instruction
|
||||
|
||||
#methods
|
||||
|
||||
#cpi
|
||||
|
@ -57,7 +60,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
let variant_arm = generate_ctor_variant(program, state);
|
||||
let ctor_args = generate_ctor_args(state);
|
||||
quote! {
|
||||
__private::instruction::#variant_arm => __private::__ctor(program_id, accounts, #(#ctor_args),*),
|
||||
instruction::#variant_arm => __private::__ctor(program_id, accounts, #(#ctor_args),*),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -80,7 +83,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
format!("__{}", name).parse().unwrap()
|
||||
};
|
||||
quote! {
|
||||
__private::instruction::#variant_arm => {
|
||||
instruction::#variant_arm => {
|
||||
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +103,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
);
|
||||
let rpc_name = &rpc.raw_method.sig.ident;
|
||||
quote! {
|
||||
__private::instruction::#variant_arm => {
|
||||
instruction::#variant_arm => {
|
||||
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
|
||||
}
|
||||
}
|
||||
|
@ -594,6 +597,10 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
|
|||
.collect();
|
||||
|
||||
quote! {
|
||||
/// `instruction` is a macro generated module containing the program's
|
||||
/// instruction enum, where each variant is created from each method
|
||||
/// handler in the `#[program]` mod. These should be used directly, when
|
||||
/// specifying instructions on a client.
|
||||
pub mod instruction {
|
||||
use super::*;
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
|
@ -613,6 +620,57 @@ fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
|
|||
)
|
||||
}
|
||||
|
||||
fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
|
||||
let mut accounts = std::collections::HashSet::new();
|
||||
|
||||
// Got through state accounts.
|
||||
if let Some(state) = &program.state {
|
||||
for rpc in &state.methods {
|
||||
let anchor_ident = &rpc.anchor_ident;
|
||||
// TODO: move to fn and share with accounts.rs.
|
||||
let macro_name = format!(
|
||||
"__client_accounts_{}",
|
||||
anchor_ident.to_string().to_snake_case()
|
||||
);
|
||||
accounts.insert(macro_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Go through instruction accounts.
|
||||
for rpc in &program.rpcs {
|
||||
let anchor_ident = &rpc.anchor_ident;
|
||||
// TODO: move to fn and share with accounts.rs.
|
||||
let macro_name = format!(
|
||||
"__client_accounts_{}",
|
||||
anchor_ident.to_string().to_snake_case()
|
||||
);
|
||||
accounts.insert(macro_name);
|
||||
}
|
||||
|
||||
// Build the tokens from all accounts
|
||||
let account_structs: Vec<proc_macro2::TokenStream> = accounts
|
||||
.iter()
|
||||
.map(|macro_name: &String| {
|
||||
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
|
||||
quote! {
|
||||
pub use crate::#macro_name::*;
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: calculate the account size and add it as a constant field to
|
||||
// each struct here. This is convenient for Rust clients.
|
||||
|
||||
quote! {
|
||||
/// `accounts` is a macro generated module, providing a set of structs
|
||||
/// mirroring the structs deriving `Accounts`, where each field is
|
||||
/// a `Pubkey`. This is useful for specifying accounts for a client.
|
||||
pub mod accounts {
|
||||
#(#account_structs)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
|
||||
let cpi_methods: Vec<proc_macro2::TokenStream> = program
|
||||
.rpcs
|
||||
|
@ -634,7 +692,7 @@ fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
|
|||
#(#args),*
|
||||
) -> ProgramResult {
|
||||
let ix = {
|
||||
let ix = __private::instruction::#ix_variant;
|
||||
let ix = instruction::#ix_variant;
|
||||
let data = AnchorSerialize::try_to_vec(&ix)
|
||||
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||
let accounts = ctx.accounts.to_account_metas(None);
|
||||
|
|
Loading…
Reference in New Issue