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]
|
||||
pub fn solana_client_gen(
|
||||
args: proc_macro::TokenStream,
|
||||
_args: proc_macro::TokenStream,
|
||||
input: 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`.
|
||||
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.
|
||||
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
|
||||
// instruction_methods inside.
|
||||
|
@ -85,113 +79,112 @@ pub fn solana_client_gen(
|
|||
|
||||
let client = quote! {
|
||||
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)]
|
||||
pub enum ClientError {
|
||||
#[error("Invalid keypair filename")]
|
||||
InvalidKeyPairFile(String),
|
||||
#[error("Error invoking rpc")]
|
||||
RpcError(#[from] solana_client::client_error::ClientError),
|
||||
#[error("Raw error")]
|
||||
RawError(String),
|
||||
}
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientError {
|
||||
#[error("Invalid keypair filename")]
|
||||
InvalidKeyPairFile(String),
|
||||
#[error("Error invoking rpc")]
|
||||
RpcError(#[from] solana_client::client_error::ClientError),
|
||||
#[error("Raw error")]
|
||||
RawError(String),
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
impl ClientError {
|
||||
// error_code returns Some(error_code) returned by the on chain program
|
||||
// and none if the error resulted from somewhere else.
|
||||
//
|
||||
// TODO: there's gotta be a cleaner way of unpacking this.
|
||||
pub fn error_code(&self) -> Option<u32> {
|
||||
match self {
|
||||
ClientError::RpcError(e) => match e.kind() {
|
||||
RpcClientErrorKind::TransactionError(e) => match e {
|
||||
TransactionError::InstructionError(_, instr_error) => match instr_error {
|
||||
InstructionError::Custom(error_code) => {
|
||||
Some(*error_code)
|
||||
}
|
||||
impl ClientError {
|
||||
// error_code returns Some(error_code) returned by the on chain program
|
||||
// and none if the error resulted from somewhere else.
|
||||
//
|
||||
// TODO: there's gotta be a cleaner way of unpacking this.
|
||||
pub fn error_code(&self) -> Option<u32> {
|
||||
match self {
|
||||
ClientError::RpcError(e) => match e.kind() {
|
||||
RpcClientErrorKind::TransactionError(e) => match e {
|
||||
TransactionError::InstructionError(_, instr_error) => match instr_error {
|
||||
InstructionError::Custom(error_code) => {
|
||||
Some(*error_code)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestOptions {
|
||||
pub commitment: CommitmentConfig,
|
||||
pub tx: RpcSendTransactionConfig,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct RequestOptions {
|
||||
pub commitment: CommitmentConfig,
|
||||
pub tx: RpcSendTransactionConfig,
|
||||
}
|
||||
|
||||
// Client is the RPC client generated to talk to a program running
|
||||
// on a configured Solana cluster.
|
||||
pub struct Client {
|
||||
program_id: Pubkey,
|
||||
payer: Keypair,
|
||||
rpc: RpcClient,
|
||||
opts: RequestOptions,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(
|
||||
// Client is the RPC client generated to talk to a program running
|
||||
// on a configured Solana cluster.
|
||||
pub struct Client {
|
||||
program_id: Pubkey,
|
||||
payer: Keypair,
|
||||
url: &str,
|
||||
given_opts: Option<RequestOptions>,
|
||||
) -> Self {
|
||||
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,
|
||||
rpc: RpcClient,
|
||||
opts: RequestOptions,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(
|
||||
program_id: Pubkey,
|
||||
payer: Keypair,
|
||||
url: &str,
|
||||
given_opts: Option<RequestOptions>,
|
||||
) -> Self {
|
||||
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 {
|
||||
program_id,
|
||||
payer,
|
||||
rpc,
|
||||
opts,
|
||||
};
|
||||
Self {
|
||||
program_id,
|
||||
payer,
|
||||
rpc,
|
||||
opts,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_keypair_file(program_id: Pubkey, filename: &str, url: &str) -> Result<Self, ClientError> {
|
||||
let kp = solana_sdk::signature::read_keypair_file(filename)
|
||||
.map_err(|_| ClientError::InvalidKeyPairFile(filename.to_string()))?;
|
||||
Ok(Self::new(program_id, kp, url, None))
|
||||
}
|
||||
pub fn from_keypair_file(program_id: Pubkey, filename: &str, url: &str) -> Result<Self, ClientError> {
|
||||
let kp = solana_sdk::signature::read_keypair_file(filename)
|
||||
.map_err(|_| ClientError::InvalidKeyPairFile(filename.to_string()))?;
|
||||
Ok(Self::new(program_id, kp, url, None))
|
||||
}
|
||||
|
||||
// Builder method to set the default options for each RPC request.
|
||||
pub fn with_options(mut self, opts: RequestOptions) -> Self {
|
||||
self.opts = opts;
|
||||
self
|
||||
}
|
||||
// Builder method to set the default options for each RPC request.
|
||||
pub fn with_options(mut self, opts: RequestOptions) -> Self {
|
||||
self.opts = opts;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rpc(&self) -> &RpcClient {
|
||||
&self.rpc
|
||||
}
|
||||
pub fn rpc(&self) -> &RpcClient {
|
||||
&self.rpc
|
||||
}
|
||||
|
||||
pub fn payer(&self) -> &Keypair {
|
||||
&self.payer
|
||||
}
|
||||
pub fn payer(&self) -> &Keypair {
|
||||
&self.payer
|
||||
}
|
||||
|
||||
pub fn program(&self) -> &Pubkey {
|
||||
&self.program_id
|
||||
}
|
||||
pub fn program(&self) -> &Pubkey {
|
||||
&self.program_id
|
||||
}
|
||||
|
||||
#client_methods
|
||||
}
|
||||
#client_methods
|
||||
}
|
||||
};
|
||||
|
||||
// Generate the entire client module.
|
||||
|
@ -277,25 +270,14 @@ pub fn solana_client_gen(
|
|||
// * Client RPC methods for each instruction variant.
|
||||
// * 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.
|
||||
// * Coder struct for serialization.
|
||||
//
|
||||
fn enum_to_methods(
|
||||
instruction_enum: &syn::ItemEnum,
|
||||
coder_struct_opt: Option<syn::TypePath>,
|
||||
) -> (
|
||||
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
|
||||
// program entry, to define a `match` statement to interpret an
|
||||
// instruction variant, and dispatch the request to the program's
|
||||
|
@ -403,7 +385,13 @@ fn enum_to_methods(
|
|||
let instruction = #instruction_enum;
|
||||
|
||||
// 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 {
|
||||
program_id,
|
||||
data,
|
||||
|
@ -655,7 +643,7 @@ fn enum_to_methods(
|
|||
|
||||
let decode_and_dispatch_tree = quote! {
|
||||
// Decode.
|
||||
let instruction: #instruction_enum_ident = Coder::from_bytes(instruction_data)
|
||||
let instruction = #instruction_enum_ident::unpack(instruction_data)
|
||||
.map_err(|_| ProgramError::InvalidInstructionData);
|
||||
// Dispatch.
|
||||
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`.
|
||||
//!
|
||||
//! 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.
|
||||
//! Instructions used with this macro must implement the
|
||||
//! `serum_common::pack::Pack` trait, where serialization should be defined.
|
||||
//!
|
||||
//! # Limitations
|
||||
//!
|
||||
|
@ -167,8 +154,6 @@ pub mod prelude {
|
|||
pub use solana_sdk::instruction::{AccountMeta, Instruction};
|
||||
pub use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
pub use crate::coder::InstructionCoder;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub use codegen::solana_client_gen;
|
||||
#[cfg(feature = "client")]
|
||||
|
@ -195,8 +180,6 @@ pub mod prelude {
|
|||
pub use thiserror::Error;
|
||||
}
|
||||
|
||||
pub mod coder;
|
||||
|
||||
// Re-export.
|
||||
#[cfg(feature = "client")]
|
||||
pub use solana_client;
|
||||
|
|
Loading…
Reference in New Issue