anchor/syn/src/codegen/program.rs

188 lines
6.2 KiB
Rust
Raw Normal View History

2021-01-14 15:16:27 -08:00
use crate::{Program, Rpc};
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);
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;
let ix = instruction::#instruction_name::deserialize(&mut data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
#dispatch
}
#methods
#instruction
2021-01-14 15:16:27 -08:00
#cpi
2020-12-31 15:48:06 -08:00
}
}
pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
let program_name = &program.name;
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-14 15:16:27 -08:00
let variant_arm = generate_ix_variant(program, rpc);
2020-12-31 15:48:06 -08:00
let rpc_name = &rpc.raw_method.sig.ident;
let anchor = &rpc.anchor_ident;
quote! {
instruction::#variant_arm => {
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! {
match ix {
#(#dispatch_arms),*
}
}
}
2021-01-14 15:16:27 -08:00
pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
let enum_name = instruction_enum_name(program);
let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
let rpc_name_camel = proc_macro2::Ident::new(
&rpc.raw_method.sig.ident.to_string().to_camel_case(),
rpc.raw_method.sig.ident.span(),
);
if rpc.args.len() == 0 {
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 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 {
#(#variants),*
}
}
}
}
fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
proc_macro2::Ident::new(
2021-01-14 15:16:27 -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 = {
let ix_variant = generate_ix_variant(program, rpc);
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 = {
let ix = instruction::#ix_variant;
let data = AnchorSerialize::try_to_vec(&ix)
.map_err(|_| ProgramError::InvalidInstructionData)?;
let accounts = ctx.accounts.to_account_metas();
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)*
}
}
}