2021-05-30 12:59:33 -07:00
|
|
|
use crate::codegen::accounts::{constraints, generics};
|
|
|
|
use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
|
|
|
|
use proc_macro2::TokenStream;
|
|
|
|
use quote::quote;
|
2021-06-15 17:15:51 -07:00
|
|
|
use syn::Expr;
|
2021-05-30 12:59:33 -07:00
|
|
|
|
|
|
|
// Generates the `Accounts` trait implementation.
|
|
|
|
pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
|
|
|
let name = &accs.ident;
|
|
|
|
let (combined_generics, trait_generics, strct_generics) = generics(accs);
|
|
|
|
|
|
|
|
// Deserialization for each field
|
|
|
|
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
|
|
|
.fields
|
|
|
|
.iter()
|
|
|
|
.map(|af: &AccountField| {
|
|
|
|
match af {
|
|
|
|
AccountField::CompositeField(s) => {
|
|
|
|
let name = &s.ident;
|
|
|
|
let ty = &s.raw_field.ty;
|
|
|
|
quote! {
|
|
|
|
#[cfg(feature = "anchor-debug")]
|
|
|
|
::solana_program::log::sol_log(stringify!(#name));
|
2021-06-15 17:15:51 -07:00
|
|
|
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
|
2021-05-30 12:59:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
AccountField::Field(f) => {
|
|
|
|
// Associated fields are *first* deserialized into
|
|
|
|
// AccountInfos, and then later deserialized into
|
|
|
|
// ProgramAccounts in the "constraint check" phase.
|
2021-06-15 17:15:51 -07:00
|
|
|
if is_pda_init(af) {
|
2021-05-30 12:59:33 -07:00
|
|
|
let name = &f.ident;
|
|
|
|
quote!{
|
|
|
|
let #name = &accounts[0];
|
|
|
|
*accounts = &accounts[1..];
|
|
|
|
}
|
|
|
|
} else {
|
2021-06-27 17:09:46 -07:00
|
|
|
let name = typed_ident(f);
|
2021-05-30 12:59:33 -07:00
|
|
|
match f.constraints.is_init() {
|
|
|
|
false => quote! {
|
|
|
|
#[cfg(feature = "anchor-debug")]
|
|
|
|
::solana_program::log::sol_log(stringify!(#name));
|
2021-06-15 17:15:51 -07:00
|
|
|
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
|
2021-05-30 12:59:33 -07:00
|
|
|
},
|
|
|
|
true => quote! {
|
|
|
|
#[cfg(feature = "anchor-debug")]
|
|
|
|
::solana_program::log::sol_log(stringify!(#name));
|
|
|
|
let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2021-06-15 17:15:51 -07:00
|
|
|
let constraints = generate_constraints(accs);
|
|
|
|
let accounts_instance = generate_accounts_instance(accs);
|
2021-05-30 12:59:33 -07:00
|
|
|
|
2021-06-15 17:15:51 -07:00
|
|
|
let ix_de = match &accs.instruction_api {
|
|
|
|
None => quote! {},
|
|
|
|
Some(ix_api) => {
|
|
|
|
let strct_inner = &ix_api;
|
|
|
|
let field_names: Vec<proc_macro2::TokenStream> = ix_api
|
|
|
|
.iter()
|
|
|
|
.map(|expr: &Expr| match expr {
|
|
|
|
Expr::Type(expr_type) => {
|
|
|
|
let field = &expr_type.expr;
|
|
|
|
quote! {
|
|
|
|
#field
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => panic!("Invalid instruction declaration"),
|
|
|
|
})
|
|
|
|
.collect();
|
2021-05-30 12:59:33 -07:00
|
|
|
quote! {
|
2021-06-15 17:15:51 -07:00
|
|
|
let mut ix_data = ix_data;
|
|
|
|
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
|
|
|
|
struct __Args {
|
|
|
|
#strct_inner
|
|
|
|
}
|
|
|
|
let __Args {
|
|
|
|
#(#field_names),*
|
|
|
|
} = __Args::deserialize(&mut ix_data)
|
|
|
|
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
|
2021-05-30 12:59:33 -07:00
|
|
|
}
|
2021-06-15 17:15:51 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-05-30 12:59:33 -07:00
|
|
|
quote! {
|
|
|
|
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
|
|
|
#[inline(never)]
|
|
|
|
fn try_accounts(
|
|
|
|
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
|
|
|
|
accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
|
2021-06-15 17:15:51 -07:00
|
|
|
ix_data: &[u8],
|
2021-05-30 12:59:33 -07:00
|
|
|
) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
2021-06-15 17:15:51 -07:00
|
|
|
// Deserialize instruction, if declared.
|
|
|
|
#ix_de
|
2021-05-30 12:59:33 -07:00
|
|
|
// Deserialize each account.
|
|
|
|
#(#deser_fields)*
|
2021-06-15 17:15:51 -07:00
|
|
|
// Execute accounts constraints.
|
|
|
|
#constraints
|
2021-05-30 12:59:33 -07:00
|
|
|
// Success. Return the validated accounts.
|
2021-06-15 17:15:51 -07:00
|
|
|
Ok(#accounts_instance)
|
2021-05-30 12:59:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if the given AccountField has an associated init constraint.
|
2021-06-15 17:15:51 -07:00
|
|
|
fn is_pda_init(af: &AccountField) -> bool {
|
2021-05-30 12:59:33 -07:00
|
|
|
match af {
|
|
|
|
AccountField::CompositeField(_s) => false,
|
2021-06-15 17:15:51 -07:00
|
|
|
AccountField::Field(f) => {
|
|
|
|
f.constraints
|
|
|
|
.associated
|
|
|
|
.as_ref()
|
|
|
|
.map(|f| f.is_init)
|
|
|
|
.unwrap_or(false)
|
|
|
|
|| f.constraints
|
|
|
|
.seeds
|
|
|
|
.as_ref()
|
|
|
|
.map(|f| f.is_init)
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
2021-05-30 12:59:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn typed_ident(field: &Field) -> TokenStream {
|
|
|
|
let name = &field.ident;
|
|
|
|
|
|
|
|
let ty = match &field.ty {
|
|
|
|
Ty::AccountInfo => quote! { AccountInfo },
|
|
|
|
Ty::ProgramState(ty) => {
|
|
|
|
let account = &ty.account_ident;
|
|
|
|
quote! {
|
|
|
|
ProgramState<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ty::CpiState(ty) => {
|
|
|
|
let account = &ty.account_ident;
|
|
|
|
quote! {
|
|
|
|
CpiState<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ty::ProgramAccount(ty) => {
|
|
|
|
let account = &ty.account_ident;
|
|
|
|
quote! {
|
|
|
|
ProgramAccount<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ty::Loader(ty) => {
|
|
|
|
let account = &ty.account_ident;
|
|
|
|
quote! {
|
|
|
|
Loader<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ty::CpiAccount(ty) => {
|
|
|
|
let account = &ty.account_ident;
|
|
|
|
quote! {
|
|
|
|
CpiAccount<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ty::Sysvar(ty) => {
|
|
|
|
let account = match ty {
|
|
|
|
SysvarTy::Clock => quote! {Clock},
|
|
|
|
SysvarTy::Rent => quote! {Rent},
|
|
|
|
SysvarTy::EpochSchedule => quote! {EpochSchedule},
|
|
|
|
SysvarTy::Fees => quote! {Fees},
|
|
|
|
SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
|
|
|
|
SysvarTy::SlotHashes => quote! {SlotHashes},
|
|
|
|
SysvarTy::SlotHistory => quote! {SlotHistory},
|
|
|
|
SysvarTy::StakeHistory => quote! {StakeHistory},
|
|
|
|
SysvarTy::Instructions => quote! {Instructions},
|
|
|
|
SysvarTy::Rewards => quote! {Rewards},
|
|
|
|
};
|
|
|
|
quote! {
|
|
|
|
Sysvar<#account>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#name: #ty
|
|
|
|
}
|
|
|
|
}
|
2021-06-15 17:15:51 -07:00
|
|
|
|
|
|
|
pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
|
|
|
// All fields without an `#[account(associated)]` attribute.
|
|
|
|
let non_associated_fields: Vec<&AccountField> =
|
|
|
|
accs.fields.iter().filter(|af| !is_pda_init(af)).collect();
|
|
|
|
|
|
|
|
// Deserialization for each *associated* field. This must be after
|
|
|
|
// the inital extraction from the accounts slice and before access_checks.
|
|
|
|
let init_associated_fields: Vec<proc_macro2::TokenStream> = accs
|
|
|
|
.fields
|
|
|
|
.iter()
|
|
|
|
.filter_map(|af| match af {
|
|
|
|
AccountField::CompositeField(_s) => None,
|
|
|
|
AccountField::Field(f) => match is_pda_init(af) {
|
|
|
|
false => None,
|
|
|
|
true => Some(f),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.map(constraints::generate)
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
// Constraint checks for each account fields.
|
|
|
|
let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
|
|
|
|
.iter()
|
|
|
|
.map(|af: &&AccountField| match af {
|
|
|
|
AccountField::Field(f) => constraints::generate(f),
|
|
|
|
AccountField::CompositeField(s) => constraints::generate_composite(s),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#(#init_associated_fields)*
|
|
|
|
#(#access_checks)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn generate_accounts_instance(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
|
|
|
let name = &accs.ident;
|
|
|
|
// Each field in the final deserialized accounts struct.
|
|
|
|
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
|
|
|
.fields
|
|
|
|
.iter()
|
|
|
|
.map(|f: &AccountField| {
|
|
|
|
let name = match f {
|
|
|
|
AccountField::CompositeField(s) => &s.ident,
|
|
|
|
AccountField::Field(f) => &f.ident,
|
|
|
|
};
|
|
|
|
quote! {
|
|
|
|
#name
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#name {
|
|
|
|
#(#return_tys),*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|