scg: Swap out coder for Pack trait for instructions
This commit is contained in:
parent
0e354a5465
commit
cade37335f
|
@ -16,15 +16,9 @@ use syn::parse_macro_input;
|
||||||
//
|
//
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn solana_client_gen(
|
pub fn solana_client_gen(
|
||||||
args: proc_macro::TokenStream,
|
_args: proc_macro::TokenStream,
|
||||||
input: proc_macro::TokenStream,
|
input: proc_macro::TokenStream,
|
||||||
) -> proc_macro::TokenStream {
|
) -> proc_macro::TokenStream {
|
||||||
// The one and only argument of the macro should be the Coder struct.
|
|
||||||
let coder_struct = match args.to_string().as_ref() {
|
|
||||||
"" => None,
|
|
||||||
_ => Some(parse_macro_input!(args as syn::TypePath)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interpet token stream as the instruction `mod`.
|
// Interpet token stream as the instruction `mod`.
|
||||||
let instruction_mod = parse_macro_input!(input as syn::ItemMod);
|
let instruction_mod = parse_macro_input!(input as syn::ItemMod);
|
||||||
|
|
||||||
|
@ -58,7 +52,7 @@ pub fn solana_client_gen(
|
||||||
//
|
//
|
||||||
// Parse the instruction enum and generate code from each enum variant.
|
// Parse the instruction enum and generate code from each enum variant.
|
||||||
let (client_methods, instruction_methods, decode_and_dispatch_tree) =
|
let (client_methods, instruction_methods, decode_and_dispatch_tree) =
|
||||||
enum_to_methods(&instruction_enum_item, coder_struct);
|
enum_to_methods(&instruction_enum_item);
|
||||||
|
|
||||||
// Now recreate the highest level instruction `mod`, but with our new
|
// Now recreate the highest level instruction `mod`, but with our new
|
||||||
// instruction_methods inside.
|
// instruction_methods inside.
|
||||||
|
@ -85,113 +79,112 @@ pub fn solana_client_gen(
|
||||||
|
|
||||||
let client = quote! {
|
let client = quote! {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use solana_client_gen::solana_sdk::instruction::InstructionError;
|
||||||
|
use solana_client_gen::solana_sdk::transaction::TransactionError;
|
||||||
|
use solana_client_gen::solana_client::client_error::ClientErrorKind as RpcClientErrorKind;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
#[error("Invalid keypair filename")]
|
#[error("Invalid keypair filename")]
|
||||||
InvalidKeyPairFile(String),
|
InvalidKeyPairFile(String),
|
||||||
#[error("Error invoking rpc")]
|
#[error("Error invoking rpc")]
|
||||||
RpcError(#[from] solana_client::client_error::ClientError),
|
RpcError(#[from] solana_client::client_error::ClientError),
|
||||||
#[error("Raw error")]
|
#[error("Raw error")]
|
||||||
RawError(String),
|
RawError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
use solana_client_gen::solana_sdk::instruction::InstructionError;
|
impl ClientError {
|
||||||
use solana_client_gen::solana_sdk::transaction::TransactionError;
|
// error_code returns Some(error_code) returned by the on chain program
|
||||||
use solana_client_gen::solana_client::client_error::ClientErrorKind as RpcClientErrorKind;
|
// and none if the error resulted from somewhere else.
|
||||||
|
//
|
||||||
impl ClientError {
|
// TODO: there's gotta be a cleaner way of unpacking this.
|
||||||
// error_code returns Some(error_code) returned by the on chain program
|
pub fn error_code(&self) -> Option<u32> {
|
||||||
// and none if the error resulted from somewhere else.
|
match self {
|
||||||
//
|
ClientError::RpcError(e) => match e.kind() {
|
||||||
// TODO: there's gotta be a cleaner way of unpacking this.
|
RpcClientErrorKind::TransactionError(e) => match e {
|
||||||
pub fn error_code(&self) -> Option<u32> {
|
TransactionError::InstructionError(_, instr_error) => match instr_error {
|
||||||
match self {
|
InstructionError::Custom(error_code) => {
|
||||||
ClientError::RpcError(e) => match e.kind() {
|
Some(*error_code)
|
||||||
RpcClientErrorKind::TransactionError(e) => match e {
|
}
|
||||||
TransactionError::InstructionError(_, instr_error) => match instr_error {
|
_ => None,
|
||||||
InstructionError::Custom(error_code) => {
|
},
|
||||||
Some(*error_code)
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
}
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RequestOptions {
|
pub struct RequestOptions {
|
||||||
pub commitment: CommitmentConfig,
|
pub commitment: CommitmentConfig,
|
||||||
pub tx: RpcSendTransactionConfig,
|
pub tx: RpcSendTransactionConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is the RPC client generated to talk to a program running
|
// Client is the RPC client generated to talk to a program running
|
||||||
// on a configured Solana cluster.
|
// on a configured Solana cluster.
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
program_id: Pubkey,
|
|
||||||
payer: Keypair,
|
|
||||||
rpc: RpcClient,
|
|
||||||
opts: RequestOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client {
|
|
||||||
pub fn new(
|
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
payer: Keypair,
|
payer: Keypair,
|
||||||
url: &str,
|
rpc: RpcClient,
|
||||||
given_opts: Option<RequestOptions>,
|
opts: RequestOptions,
|
||||||
) -> Self {
|
}
|
||||||
let rpc = RpcClient::new(url.to_string());
|
|
||||||
let opts = match given_opts {
|
impl Client {
|
||||||
Some(opts) => opts,
|
pub fn new(
|
||||||
// Use these default options if None are given.
|
program_id: Pubkey,
|
||||||
None => RequestOptions {
|
payer: Keypair,
|
||||||
commitment: CommitmentConfig::single(),
|
url: &str,
|
||||||
tx: RpcSendTransactionConfig {
|
given_opts: Option<RequestOptions>,
|
||||||
skip_preflight: true,
|
) -> Self {
|
||||||
preflight_commitment: None,
|
let rpc = RpcClient::new(url.to_string());
|
||||||
|
let opts = match given_opts {
|
||||||
|
Some(opts) => opts,
|
||||||
|
// Use these default options if None are given.
|
||||||
|
None => RequestOptions {
|
||||||
|
commitment: CommitmentConfig::single(),
|
||||||
|
tx: RpcSendTransactionConfig {
|
||||||
|
skip_preflight: true,
|
||||||
|
preflight_commitment: None,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
};
|
Self {
|
||||||
Self {
|
program_id,
|
||||||
program_id,
|
payer,
|
||||||
payer,
|
rpc,
|
||||||
rpc,
|
opts,
|
||||||
opts,
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_keypair_file(program_id: Pubkey, filename: &str, url: &str) -> Result<Self, ClientError> {
|
pub fn from_keypair_file(program_id: Pubkey, filename: &str, url: &str) -> Result<Self, ClientError> {
|
||||||
let kp = solana_sdk::signature::read_keypair_file(filename)
|
let kp = solana_sdk::signature::read_keypair_file(filename)
|
||||||
.map_err(|_| ClientError::InvalidKeyPairFile(filename.to_string()))?;
|
.map_err(|_| ClientError::InvalidKeyPairFile(filename.to_string()))?;
|
||||||
Ok(Self::new(program_id, kp, url, None))
|
Ok(Self::new(program_id, kp, url, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder method to set the default options for each RPC request.
|
// Builder method to set the default options for each RPC request.
|
||||||
pub fn with_options(mut self, opts: RequestOptions) -> Self {
|
pub fn with_options(mut self, opts: RequestOptions) -> Self {
|
||||||
self.opts = opts;
|
self.opts = opts;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rpc(&self) -> &RpcClient {
|
pub fn rpc(&self) -> &RpcClient {
|
||||||
&self.rpc
|
&self.rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn payer(&self) -> &Keypair {
|
pub fn payer(&self) -> &Keypair {
|
||||||
&self.payer
|
&self.payer
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn program(&self) -> &Pubkey {
|
pub fn program(&self) -> &Pubkey {
|
||||||
&self.program_id
|
&self.program_id
|
||||||
}
|
}
|
||||||
|
|
||||||
#client_methods
|
#client_methods
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate the entire client module.
|
// Generate the entire client module.
|
||||||
|
@ -277,25 +270,14 @@ pub fn solana_client_gen(
|
||||||
// * Client RPC methods for each instruction variant.
|
// * Client RPC methods for each instruction variant.
|
||||||
// * Instruction methods for generating instances of solana_sdk::instruction::Instruction.
|
// * Instruction methods for generating instances of solana_sdk::instruction::Instruction.
|
||||||
// * Decode and dispatch tree, i.e., the code to execute on entry to the program.
|
// * Decode and dispatch tree, i.e., the code to execute on entry to the program.
|
||||||
// * Coder struct for serialization.
|
|
||||||
//
|
//
|
||||||
fn enum_to_methods(
|
fn enum_to_methods(
|
||||||
instruction_enum: &syn::ItemEnum,
|
instruction_enum: &syn::ItemEnum,
|
||||||
coder_struct_opt: Option<syn::TypePath>,
|
|
||||||
) -> (
|
) -> (
|
||||||
proc_macro2::TokenStream,
|
proc_macro2::TokenStream,
|
||||||
proc_macro2::TokenStream,
|
proc_macro2::TokenStream,
|
||||||
proc_macro2::TokenStream,
|
proc_macro2::TokenStream,
|
||||||
) {
|
) {
|
||||||
let coder_struct = match &coder_struct_opt {
|
|
||||||
None => quote! {
|
|
||||||
solana_client_gen::coder::DefaultCoder
|
|
||||||
},
|
|
||||||
Some(cs) => quote! {
|
|
||||||
#cs
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// When combined together, all the dispatch arms are used on
|
// When combined together, all the dispatch arms are used on
|
||||||
// program entry, to define a `match` statement to interpret an
|
// program entry, to define a `match` statement to interpret an
|
||||||
// instruction variant, and dispatch the request to the program's
|
// instruction variant, and dispatch the request to the program's
|
||||||
|
@ -403,7 +385,13 @@ fn enum_to_methods(
|
||||||
let instruction = #instruction_enum;
|
let instruction = #instruction_enum;
|
||||||
|
|
||||||
// Serialize.
|
// Serialize.
|
||||||
let data = #coder_struct::to_bytes(instruction);
|
let size = instruction
|
||||||
|
.size()
|
||||||
|
.expect("instructions must be serializable")
|
||||||
|
as usize;
|
||||||
|
let mut data = vec![0u8; size];
|
||||||
|
#instruction_enum_ident::pack(instruction, &mut data)
|
||||||
|
.expect("instruction must be serializable");
|
||||||
Instruction {
|
Instruction {
|
||||||
program_id,
|
program_id,
|
||||||
data,
|
data,
|
||||||
|
@ -655,7 +643,7 @@ fn enum_to_methods(
|
||||||
|
|
||||||
let decode_and_dispatch_tree = quote! {
|
let decode_and_dispatch_tree = quote! {
|
||||||
// Decode.
|
// Decode.
|
||||||
let instruction: #instruction_enum_ident = Coder::from_bytes(instruction_data)
|
let instruction = #instruction_enum_ident::unpack(instruction_data)
|
||||||
.map_err(|_| ProgramError::InvalidInstructionData);
|
.map_err(|_| ProgramError::InvalidInstructionData);
|
||||||
// Dispatch.
|
// Dispatch.
|
||||||
match instruction {
|
match instruction {
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// InstructionCoder is the trait that must be implemented to user
|
|
||||||
/// custom serialization with the main macro. If a coder is not
|
|
||||||
/// provided, the `DefaultCoder` will be used.
|
|
||||||
pub trait InstructionCoder<'a, T: ?Sized + Serialize + Deserialize<'a>> {
|
|
||||||
fn to_bytes(i: T) -> Vec<u8>;
|
|
||||||
fn from_bytes(data: &'a [u8]) -> Result<T, ()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DefaultCoder;
|
|
||||||
impl<'a, T: ?Sized + serde::Serialize + serde::Deserialize<'a>> InstructionCoder<'a, T>
|
|
||||||
for DefaultCoder
|
|
||||||
{
|
|
||||||
fn to_bytes(i: T) -> Vec<u8> {
|
|
||||||
serum_common::pack::to_bytes(&i).expect("instruction must be serializable")
|
|
||||||
}
|
|
||||||
fn from_bytes(data: &'a [u8]) -> Result<T, ()> {
|
|
||||||
serum_common::pack::from_bytes(data).map_err(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -129,23 +129,10 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Using a custom coder.
|
//! # Serialization
|
||||||
//!
|
//!
|
||||||
//! It's assumed instructions implement serde's `Serialize` and `Deserialize`.
|
//! Instructions used with this macro must implement the
|
||||||
//!
|
//! `serum_common::pack::Pack` trait, where serialization should be defined.
|
||||||
//! By default, a default coder will be used to serialize instructions before
|
|
||||||
//! sending them to Solana. If you want to use a custom coder, inject it
|
|
||||||
//! into the macro like this
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! #[solana_client_gen(crate::mod::Coder)]
|
|
||||||
//! mod instruction {
|
|
||||||
//! ...
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Where `Coder` is a user defined struct that implements the
|
|
||||||
//! `InstructionCoder` interface.
|
|
||||||
//!
|
//!
|
||||||
//! # Limitations
|
//! # Limitations
|
||||||
//!
|
//!
|
||||||
|
@ -167,8 +154,6 @@ pub mod prelude {
|
||||||
pub use solana_sdk::instruction::{AccountMeta, Instruction};
|
pub use solana_sdk::instruction::{AccountMeta, Instruction};
|
||||||
pub use solana_sdk::pubkey::Pubkey;
|
pub use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
pub use crate::coder::InstructionCoder;
|
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub use codegen::solana_client_gen;
|
pub use codegen::solana_client_gen;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
|
@ -195,8 +180,6 @@ pub mod prelude {
|
||||||
pub use thiserror::Error;
|
pub use thiserror::Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod coder;
|
|
||||||
|
|
||||||
// Re-export.
|
// Re-export.
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
pub use solana_client;
|
pub use solana_client;
|
||||||
|
|
Loading…
Reference in New Issue