anchor/syn/src/codegen/program.rs

530 lines
19 KiB
Rust
Raw Normal View History

use crate::parser;
2021-01-23 00:18:50 -08:00
use crate::{Program, RpcArg, State};
2020-12-31 15:48:06 -08:00
use heck::CamelCase;
use quote::quote;
pub fn generate(program: Program) -> proc_macro2::TokenStream {
let mod_name = &program.name;
let instruction_name = instruction_enum_name(&program);
let dispatch = generate_dispatch(&program);
2021-01-20 17:13:02 -08:00
let handlers_non_inlined = generate_non_inlined_handlers(&program);
2020-12-31 15:48:06 -08:00
let methods = generate_methods(&program);
let instruction = generate_instruction(&program);
2021-01-14 15:16:27 -08:00
let cpi = generate_cpi(&program);
2020-12-31 15:48:06 -08:00
quote! {
2021-01-09 22:03:14 -08:00
// Import everything in the mod, in case the user wants to put types
2020-12-31 15:48:06 -08:00
// in there.
use #mod_name::*;
2021-01-14 15:16:27 -08:00
#[cfg(not(feature = "no-entrypoint"))]
2021-01-15 20:10:24 -08:00
anchor_lang::solana_program::entrypoint!(entry);
2021-01-14 15:16:27 -08:00
#[cfg(not(feature = "no-entrypoint"))]
2020-12-31 15:48:06 -08:00
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
let mut data: &[u8] = instruction_data;
2021-01-20 17:13:02 -08:00
let ix = __private::instruction::#instruction_name::deserialize(&mut data)
2020-12-31 15:48:06 -08:00
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
#dispatch
}
2021-01-20 17:13:02 -08:00
// Create a private module to not clutter the program's namespace.
mod __private {
use super::*;
#handlers_non_inlined
#instruction
}
2020-12-31 15:48:06 -08:00
2021-01-20 17:13:02 -08:00
#methods
2021-01-14 15:16:27 -08:00
#cpi
2020-12-31 15:48:06 -08:00
}
}
2021-01-23 00:18:50 -08:00
2020-12-31 15:48:06 -08:00
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
let ctor_state_dispatch_arm = match &program.state {
None => quote! { /* no-op */ },
Some(state) => {
let variant_arm = generate_ctor_variant(program, state);
let ctor_args = generate_ctor_args(state);
quote! {
2021-01-23 00:18:50 -08:00
__private::instruction::#variant_arm => __private::__ctor(program_id, accounts, #(#ctor_args),*),
}
}
};
let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
2021-01-23 00:18:50 -08:00
None => vec![],
Some(s) => s
.methods
.iter()
2021-01-23 00:18:50 -08:00
.map(|rpc: &crate::StateRpc| {
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let variant_arm: proc_macro2::TokenStream = generate_ix_variant(
program,
rpc.raw_method.sig.ident.to_string(),
&rpc.args,
true,
);
let rpc_name: proc_macro2::TokenStream = {
let name = &rpc.raw_method.sig.ident.to_string();
format!("__{}", name).parse().unwrap()
};
quote! {
2021-01-23 00:18:50 -08:00
__private::instruction::#variant_arm => {
2021-01-23 19:37:57 -08:00
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
}
}
})
.collect(),
};
2020-12-31 15:48:06 -08:00
let dispatch_arms: Vec<proc_macro2::TokenStream> = program
.rpcs
.iter()
.map(|rpc| {
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
2021-01-23 00:18:50 -08:00
let variant_arm = generate_ix_variant(
program,
rpc.raw_method.sig.ident.to_string(),
&rpc.args,
false,
);
2020-12-31 15:48:06 -08:00
let rpc_name = &rpc.raw_method.sig.ident;
2021-01-20 17:13:02 -08:00
quote! {
__private::instruction::#variant_arm => {
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
}
}
})
.collect();
quote! {
match ix {
#ctor_state_dispatch_arm
#(#state_dispatch_arms),*
2021-01-20 17:13:02 -08:00
#(#dispatch_arms),*
}
}
}
// Generate non-inlined wrappers for each instruction handler, since Solana's
// BPF max stack size can't handle reasonable sized dispatch trees without doing
// so.
pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStream {
let program_name = &program.name;
let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
None => quote! {},
Some(state) => {
let ctor_typed_args = generate_ctor_typed_args(state);
let ctor_untyped_args = generate_ctor_args(state);
let name = &state.strct.ident;
let mod_name = &program.name;
2021-01-23 00:18:50 -08:00
let anchor_ident = &state.ctor_anchor;
quote! {
#[inline(never)]
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
2021-01-23 00:18:50 -08:00
let mut remaining_accounts: &[AccountInfo] = accounts;
// Deserialize accounts.
let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
2021-01-23 00:18:50 -08:00
// Invoke the ctor.
let instance = #mod_name::#name::new(
anchor_lang::Context::new(
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
),
#(#ctor_untyped_args),*
)?;
2021-01-23 00:18:50 -08:00
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
let seed = anchor_lang::ProgramState::<#name>::seed();
let owner = ctor_accounts.program.key;
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
2021-01-22 06:16:48 -08:00
// Add 8 for the account discriminator.
let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
let lamports = ctor_accounts.rent.minimum_balance(space);
let seeds = &[&[nonce][..]];
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
from,
&to,
&base,
seed,
lamports,
space as u64,
owner,
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
ctor_accounts.from.clone(),
ctor_accounts.to.clone(),
ctor_accounts.base.clone(),
ctor_accounts.system_program.clone(),
],
&[seeds],
)?;
// Serialize the state and save it to storage.
2021-01-23 00:18:50 -08:00
ctor_user_def_accounts.exit(program_id)?;
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
instance.try_serialize(&mut cursor)?;
Ok(())
}
}
}
};
2021-01-23 00:18:50 -08:00
let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.methods
.iter()
.map(|rpc| {
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let private_rpc_name: proc_macro2::TokenStream = {
let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let rpc_name = &rpc.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &rpc.anchor_ident;
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.len() == 0 {
return Err(ProgramError::Custom(1)); // todo
}
// Deserialize the program state account.
let state_account = &remaining_accounts[0];
let mut state: #state_ty = {
let data = state_account.try_borrow_data()?;
let mut sliced: &[u8] = &data;
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
};
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#rpc_name(
ctx,
#(#rpc_arg_names),*
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
})
.collect(),
};
2021-01-20 17:13:02 -08:00
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.rpcs
.iter()
.map(|rpc| {
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
let rpc_name = &rpc.raw_method.sig.ident;
2020-12-31 15:48:06 -08:00
let anchor = &rpc.anchor_ident;
quote! {
2021-01-20 17:13:02 -08:00
#[inline(never)]
pub fn #rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
2021-01-14 15:16:27 -08:00
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
2020-12-31 15:48:06 -08:00
#program_name::#rpc_name(
2021-01-14 22:35:50 -08:00
Context::new(program_id, &mut accounts, remaining_accounts),
2020-12-31 15:48:06 -08:00
#(#rpc_arg_names),*
)?;
2021-01-14 15:16:27 -08:00
accounts.exit(program_id)
2020-12-31 15:48:06 -08:00
}
}
})
.collect();
quote! {
#non_inlined_ctor
2021-01-23 00:18:50 -08:00
#(#non_inlined_state_handlers)*
2021-01-20 17:13:02 -08:00
#(#non_inlined_handlers)*
2020-12-31 15:48:06 -08:00
}
}
pub fn generate_ctor_variant(program: &Program, state: &State) -> proc_macro2::TokenStream {
let enum_name = instruction_enum_name(program);
let ctor_args = generate_ctor_args(state);
if ctor_args.len() == 0 {
quote! {
#enum_name::__Ctor
}
} else {
quote! {
#enum_name::__Ctor {
#(#ctor_args),*
}
}
}
}
pub fn generate_ctor_typed_variant_with_comma(program: &Program) -> proc_macro2::TokenStream {
match &program.state {
None => quote! {},
Some(state) => {
let ctor_args = generate_ctor_typed_args(state);
if ctor_args.len() == 0 {
quote! {
2021-01-23 00:18:50 -08:00
__Ctor,
}
} else {
quote! {
__Ctor {
#(#ctor_args),*
},
}
}
}
}
}
fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
state
.ctor
.sig
.inputs
.iter()
2021-01-23 00:18:50 -08:00
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.clone())
}
_ => panic!("Invalid syntaxe,"),
})
.collect()
}
fn generate_ctor_args(state: &State) -> Vec<Box<syn::Pat>> {
state
.ctor
.sig
.inputs
.iter()
2021-01-23 00:18:50 -08:00
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.pat.clone())
}
_ => panic!(""),
})
.collect()
}
2021-01-23 00:18:50 -08:00
pub fn generate_ix_variant(
program: &Program,
name: String,
args: &[RpcArg],
underscore: bool,
) -> proc_macro2::TokenStream {
2021-01-14 15:16:27 -08:00
let enum_name = instruction_enum_name(program);
2021-01-23 00:18:50 -08:00
let rpc_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
let rpc_name_camel: proc_macro2::TokenStream = {
let n = name.to_camel_case();
if underscore {
format!("__{}", n).parse().unwrap()
} else {
n.parse().unwrap()
}
};
if args.len() == 0 {
2021-01-14 15:16:27 -08:00
quote! {
#enum_name::#rpc_name_camel
}
} else {
quote! {
#enum_name::#rpc_name_camel {
#(#rpc_arg_names),*
}
}
}
}
2020-12-31 15:48:06 -08:00
pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
let program_mod = &program.program_mod;
quote! {
#program_mod
}
}
pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
let enum_name = instruction_enum_name(program);
let ctor_variant = generate_ctor_typed_variant_with_comma(program);
2021-01-23 00:18:50 -08:00
let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.methods
.iter()
.map(|method| {
let rpc_name_camel: proc_macro2::TokenStream = {
let name = format!(
"__{}",
&method.raw_method.sig.ident.to_string().to_camel_case(),
);
name.parse().unwrap()
};
let raw_args: Vec<&syn::PatType> =
method.args.iter().map(|arg| &arg.raw_arg).collect();
// If no args, output a "unit" variant instead of a struct variant.
if method.args.len() == 0 {
quote! {
#rpc_name_camel,
}
} else {
quote! {
#rpc_name_camel {
#(#raw_args),*
},
}
}
})
.collect(),
};
2020-12-31 15:48:06 -08:00
let variants: Vec<proc_macro2::TokenStream> = program
.rpcs
.iter()
.map(|rpc| {
let rpc_name_camel = proc_macro2::Ident::new(
&rpc.raw_method.sig.ident.to_string().to_camel_case(),
rpc.raw_method.sig.ident.span(),
);
let raw_args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
// If no args, output a "unit" variant instead of a struct variant.
if rpc.args.len() == 0 {
quote! {
#rpc_name_camel
}
} else {
quote! {
#rpc_name_camel {
#(#raw_args),*
}
}
}
})
.collect();
quote! {
pub mod instruction {
use super::*;
#[derive(AnchorSerialize, AnchorDeserialize)]
pub enum #enum_name {
#ctor_variant
2021-01-23 00:18:50 -08:00
#(#state_method_variants)*
2020-12-31 15:48:06 -08:00
#(#variants),*
}
}
}
}
fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
proc_macro2::Ident::new(
2021-01-20 17:13:02 -08:00
&format!("{}Instruction", program.name.to_string().to_camel_case()),
2020-12-31 15:48:06 -08:00
program.name.span(),
)
}
2021-01-14 15:16:27 -08:00
fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
let cpi_methods: Vec<proc_macro2::TokenStream> = program
.rpcs
.iter()
.map(|rpc| {
let accounts_ident = &rpc.anchor_ident;
let cpi_method = {
2021-01-23 00:18:50 -08:00
let ix_variant = generate_ix_variant(
program,
rpc.raw_method.sig.ident.to_string(),
&rpc.args,
false,
);
2021-01-14 15:16:27 -08:00
let method_name = &rpc.ident;
let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
quote! {
pub fn #method_name<'a, 'b, 'c, 'info>(
ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
#(#args),*
) -> ProgramResult {
let ix = {
2021-01-20 17:13:02 -08:00
let ix = __private::instruction::#ix_variant;
2021-01-14 15:16:27 -08:00
let data = AnchorSerialize::try_to_vec(&ix)
.map_err(|_| ProgramError::InvalidInstructionData)?;
2021-01-20 17:13:02 -08:00
let accounts = ctx.accounts.to_account_metas(None);
2021-01-15 20:10:24 -08:00
anchor_lang::solana_program::instruction::Instruction {
2021-01-14 15:16:27 -08:00
program_id: *ctx.program.key,
accounts,
data,
}
};
let mut acc_infos = ctx.accounts.to_account_infos();
acc_infos.push(ctx.program.clone());
2021-01-15 20:10:24 -08:00
anchor_lang::solana_program::program::invoke_signed(
2021-01-14 15:16:27 -08:00
&ix,
&acc_infos,
ctx.signer_seeds,
)
}
}
};
cpi_method
})
.collect();
quote! {
2021-01-14 17:09:15 -08:00
#[cfg(feature = "cpi")]
2021-01-14 15:16:27 -08:00
pub mod cpi {
use super::*;
#(#cpi_methods)*
}
}
}