use {
crate::{
codegen::{private_ident, program::common::*},
Program,
},
quote::{quote, quote_spanned, ToTokens},
syn::spanned::Spanned,
};
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let program_name = &program.name;
let legacy_idl_mod = match &program.program_args {
Some(args) if args.legacy_idl => generate_legacy_idl_mod(),
_ => proc_macro2::TokenStream::new(),
};
let event_cpi_mod = generate_event_cpi_mod();
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.ixs
.iter()
.map(|ix| {
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
let ix_method_name = &ix.raw_method.sig.ident;
let ix_method_name_str = ix_method_name.to_string();
let ix_name = match generate_ix_variant_name(&ix_method_name_str) {
Ok(name) => quote! { #name },
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant name: `", #err, "`")) };
}
};
let variant_arm = match generate_ix_variant(&ix_method_name_str, &ix.args) {
Ok(v) => v,
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant arm: `", #err, "`")) };
}
};
let ix_name_log = format!("Instruction: {ix_name}");
let accounts_struct_name = &ix.anchor_ident;
let ret_type = &ix.returns.ty.to_token_stream();
let cfgs = &ix.cfgs;
let program_id = private_ident("__program_id");
let account_infos = private_ident("__accounts");
let ix_data = private_ident("__ix_data");
let deserialized_ix = private_ident("ix");
let bumps = private_ident("__bumps");
let reallocs = private_ident("__reallocs");
let remaining_accounts = private_ident("__remaining_accounts");
let deserialized_accounts = private_ident("__accounts");
let shorten_remaining_accounts =
private_ident("__shorten_invariant_lifetime_remaining_accounts");
let shortened_value = private_ident("value");
let result = private_ident("result");
let return_data = private_ident("return_data");
let maybe_set_return_data = match ret_type.to_string().as_str() {
"()" => quote! {},
_ => quote! {
let mut #return_data = Vec::with_capacity(256);
#result.serialize(&mut #return_data).unwrap();
anchor_lang::solana_program::program::set_return_data(&#return_data);
},
};
let actual_param_count = ix.args.len();
let count_error_msg = format!(
"#[instruction(...)] on Account `{}<'_>` expects MORE args, the ix `{}(...)` has only {} args.",
accounts_struct_name,
ix_method_name_str,
actual_param_count,
);
let type_validations: Vec<proc_macro2::TokenStream> = ix.args
.iter()
.enumerate()
.map(|(idx, arg)| {
let arg_ty = &arg.raw_arg.ty;
let arg_ty_span = arg_ty.span();
let method_name = syn::Ident::new(
&format!("__anchor_validate_ix_arg_type_{}", idx),
proc_macro2::Span::call_site().located_at(arg_ty_span),
);
quote_spanned! {arg_ty_span=>
const _: fn() = || {
let _: fn(&#arg_ty) = #accounts_struct_name::#method_name;
};
}
})
.collect();
let param_validation = quote! {
const _: () = {
const EXPECTED_COUNT: usize = #accounts_struct_name::__ANCHOR_IX_PARAM_COUNT;
const HANDLER_PARAM_COUNT: usize = #actual_param_count;
if EXPECTED_COUNT > HANDLER_PARAM_COUNT {
panic!(#count_error_msg);
}
};
#(#type_validations)*
};
quote! {
#(#cfgs)*
#[inline(never)]
pub fn #ix_method_name<'info>(
#program_id: &'info Pubkey,
#account_infos: &'info [AccountInfo<'info>],
#ix_data: &'info [u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
#param_validation
let #deserialized_ix = instruction::#ix_name::deserialize(&mut &#ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = #deserialized_ix;
let mut #bumps = <#accounts_struct_name as anchor_lang::Bumps>::Bumps::default();
let mut #reallocs = ::std::collections::BTreeSet::new();
let mut #remaining_accounts = #account_infos;
let mut #deserialized_accounts = #accounts_struct_name::try_accounts(
#program_id,
&mut #remaining_accounts,
#ix_data,
&mut #bumps,
&mut #reallocs,
)?;
#[inline(always)]
unsafe fn #shorten_remaining_accounts<'a, 'info: 'a>(
#shortened_value: &'a [AccountInfo<'info>],
) -> &'a [AccountInfo<'a>] {
unsafe { ::core::mem::transmute(#shortened_value) }
}
let #result = #program_name::#ix_method_name(
anchor_lang::context::Context::new(
#program_id,
unsafe { #accounts_struct_name::__shorten_invariant_lifetime(&mut #deserialized_accounts) },
unsafe { #shorten_remaining_accounts(#remaining_accounts) },
#bumps,
),
#(#ix_arg_names),*
)?;
#maybe_set_return_data
#deserialized_accounts.exit(#program_id)
}
}
})
.collect();
quote! {
mod __private {
use super::*;
#legacy_idl_mod
pub mod __global {
use super::*;
#(#non_inlined_handlers)*
}
#event_cpi_mod
}
}
}
fn generate_legacy_idl_mod() -> proc_macro2::TokenStream {
use crate::codegen::program::idl::idl_accounts_and_functions;
let idl_accounts_and_functions = idl_accounts_and_functions();
let non_inlined_idl: proc_macro2::TokenStream = quote! {
#[inline(never)]
pub fn __idl_dispatch<'info>(program_id: &Pubkey, accounts: &'info [AccountInfo<'info>], idl_ix_data: &[u8]) -> anchor_lang::Result<()> {
let mut accounts = accounts;
let mut data: &[u8] = idl_ix_data;
let ix = anchor_lang::idl::IdlInstruction::deserialize(&mut data)
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
match ix {
anchor_lang::idl::IdlInstruction::Create { data_len } => {
let mut bumps = <IdlCreateAccounts as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_create_account(program_id, &mut accounts, data_len)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::Resize { data_len } => {
let mut bumps = <IdlResizeAccount as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlResizeAccount::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_resize_account(program_id, &mut accounts, data_len)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::Close => {
let mut bumps = <IdlCloseAccount as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlCloseAccount::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_close_account(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::CreateBuffer => {
let mut bumps = <IdlCreateBuffer as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_create_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::Write { data } => {
let mut bumps = <IdlAccounts as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_write(program_id, &mut accounts, data)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
let mut bumps = <IdlAccounts as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_set_authority(program_id, &mut accounts, new_authority)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetBuffer => {
let mut bumps = <IdlSetBuffer as anchor_lang::Bumps>::Bumps::default();
let mut reallocs = ::std::collections::BTreeSet::new();
let mut accounts =
IdlSetBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?;
__idl_set_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
}
Ok(())
}
};
quote! {
pub mod __idl {
use super::*;
#non_inlined_idl
#idl_accounts_and_functions
}
}
}
fn generate_event_cpi_mod() -> proc_macro2::TokenStream {
#[cfg(feature = "event-cpi")]
{
let authority = crate::parser::accounts::event_cpi::EventAuthority::get();
let authority_name = authority.name;
quote! {
pub mod __events {
use super::*;
#[inline(never)]
pub fn __event_dispatch(
program_id: &Pubkey,
accounts: &[AccountInfo],
event_data: &[u8],
) -> anchor_lang::Result<()> {
let given_event_authority = next_account_info(&mut accounts.iter())?;
if !given_event_authority.is_signer {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSigner,
)
.with_account_name(#authority_name));
}
if given_event_authority.key() != crate::EVENT_AUTHORITY_AND_BUMP.0 {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds,
)
.with_account_name(#authority_name)
.with_pubkeys((given_event_authority.key(), crate::EVENT_AUTHORITY_AND_BUMP.0)));
}
Ok(())
}
}
}
}
#[cfg(not(feature = "event-cpi"))]
quote! {}
}