use {
crate::*,
quote::{format_ident, quote},
std::collections::HashSet,
};
pub fn generate(f: &Field, accs: &AccountsStruct) -> proc_macro2::TokenStream {
let constraints = linearize(&f.constraints);
let rent = if constraints
.iter()
.any(|c| matches!(c, Constraint::RentExempt(ConstraintRentExempt::Enforce)))
{
quote! { let __anchor_rent = Rent::get()?; }
} else {
quote! {}
};
let checks: Vec<proc_macro2::TokenStream> = constraints
.iter()
.map(|c| generate_constraint(f, c, accs))
.collect();
let mut all_checks = quote! {#(#checks)*};
if f.is_optional && !constraints.is_empty() {
let ident = &f.ident;
let ty_decl = f.ty_decl(false);
all_checks = match &constraints[0] {
Constraint::Init(_) | Constraint::Zeroed(_) => {
quote! {
let #ident: #ty_decl = if let Some(#ident) = #ident {
#all_checks
Some(#ident)
} else {
None
};
}
}
_ => {
quote! {
if let Some(#ident) = &#ident {
#all_checks
}
}
}
};
}
quote! {
#rent
#all_checks
}
}
pub fn generate_composite(f: &CompositeField) -> proc_macro2::TokenStream {
let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
.iter()
.map(|c| match c {
Constraint::Raw(_) => c,
_ => panic!("Invariant violation: composite constraints can only be raw or literals"),
})
.map(|c| generate_constraint_composite(f, c))
.collect();
quote! {
#(#checks)*
}
}
pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
let ConstraintGroup {
init,
zeroed,
mutable,
dup,
signer,
has_one,
raw,
owner,
rent_exempt,
seeds,
executable,
close,
address,
associated_token,
token_account,
mint,
realloc,
} = c_group.clone();
let mut constraints = Vec::new();
if let Some(c) = zeroed {
constraints.push(Constraint::Zeroed(c));
}
if let Some(c) = init {
constraints.push(Constraint::Init(c));
}
if let Some(c) = realloc {
constraints.push(Constraint::Realloc(c));
}
if let Some(c) = seeds {
constraints.push(Constraint::Seeds(c));
}
if let Some(c) = associated_token {
constraints.push(Constraint::AssociatedToken(c));
}
if let Some(c) = mutable {
constraints.push(Constraint::Mut(c));
}
if let Some(c) = dup {
constraints.push(Constraint::Dup(c));
}
if let Some(c) = signer {
constraints.push(Constraint::Signer(c));
}
constraints.append(&mut has_one.into_iter().map(Constraint::HasOne).collect());
constraints.append(&mut raw.into_iter().map(Constraint::Raw).collect());
if let Some(c) = owner {
constraints.push(Constraint::Owner(c));
}
if let Some(c) = rent_exempt {
constraints.push(Constraint::RentExempt(c));
}
if let Some(c) = executable {
constraints.push(Constraint::Executable(c));
}
if let Some(c) = close {
constraints.push(Constraint::Close(c));
}
if let Some(c) = address {
constraints.push(Constraint::Address(c));
}
if let Some(c) = token_account {
constraints.push(Constraint::TokenAccount(c));
}
if let Some(c) = mint {
constraints.push(Constraint::Mint(c));
}
constraints
}
fn generate_constraint(
f: &Field,
c: &Constraint,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
match c {
Constraint::Init(c) => generate_constraint_init(f, c, accs),
Constraint::Zeroed(c) => generate_constraint_zeroed(f, c, accs),
Constraint::Mut(c) => generate_constraint_mut(f, c),
Constraint::Dup(_) => quote! {}, Constraint::HasOne(c) => generate_constraint_has_one(f, c, accs),
Constraint::Signer(c) => generate_constraint_signer(f, c),
Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
Constraint::Owner(c) => generate_constraint_owner(f, c),
Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
Constraint::Seeds(c) => generate_constraint_seeds(f, c),
Constraint::Executable(c) => generate_constraint_executable(f, c),
Constraint::Close(c) => generate_constraint_close(f, c, accs),
Constraint::Address(c) => generate_constraint_address(f, c),
Constraint::AssociatedToken(c) => generate_constraint_associated_token(f, c, accs),
Constraint::TokenAccount(c) => generate_constraint_token_account(f, c, accs),
Constraint::Mint(c) => generate_constraint_mint(f, c, accs),
Constraint::Realloc(c) => generate_constraint_realloc(f, c, accs),
}
}
fn generate_constraint_composite(f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
match c {
Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
_ => panic!("Invariant violation"),
}
}
fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
let field = &f.ident;
let addr = &c.address;
let error = generate_custom_error(
field,
&c.error,
quote! { ConstraintAddress },
&Some(&(quote! { actual }, quote! { expected })),
);
quote! {
{
let actual = #field.key();
let expected = #addr;
if actual != expected {
return #error;
}
}
}
}
pub fn generate_constraint_init(
f: &Field,
c: &ConstraintInitGroup,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
generate_constraint_init_group(f, c, accs)
}
pub fn generate_constraint_zeroed(
f: &Field,
_c: &ConstraintZeroed,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let account_ty = f.account_ty();
let discriminator = quote! { #account_ty::DISCRIMINATOR };
let field = &f.ident;
let name_str = field.to_string();
let ty_decl = f.ty_decl(true);
let from_account_info = f.from_account_info(None, false);
let unique_account_checks = accs
.fields
.iter()
.filter_map(|af| match af {
AccountField::Field(field) => Some(field),
_ => None,
})
.take_while(|field| field.ident != f.ident)
.filter(|field| field.constraints.is_zeroed() || field.constraints.init.is_some())
.map(|other_field| {
let other = &other_field.ident;
let err = quote! {
Err(
anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintZero
).with_account_name(#name_str)
)
};
if other_field.is_optional {
quote! {
if #other.is_some() && #field.key == &#other.as_ref().unwrap().key() {
return #err;
}
}
} else {
quote! {
if #field.key == &#other.key() {
return #err;
}
}
}
});
quote! {
let #field: #ty_decl = {
let mut __data: &[u8] = &#field.try_borrow_data()?;
let __disc = &__data[..#discriminator.len()];
let __has_disc = __disc.iter().any(|b| *b != 0);
if __has_disc {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintZero).with_account_name(#name_str));
}
#(#unique_account_checks)*
#from_account_info
};
}
}
pub fn generate_constraint_close(
f: &Field,
c: &ConstraintClose,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let name_str = field.to_string();
let target = &c.sol_dest;
let target_optional_check =
OptionalCheckScope::new_with_field(accs, field).generate_check(target);
quote! {
{
#target_optional_check
if #field.key() == #target.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintClose).with_account_name(#name_str));
}
}
}
}
pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream {
let ident = &f.ident;
let account_ref = generate_account_ref(f);
let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut }, &None);
quote! {
if !#account_ref.is_writable {
return #error;
}
}
}
pub fn generate_constraint_has_one(
f: &Field,
c: &ConstraintHasOne,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let target = &c.join_target;
let ident = &f.ident;
let field = match &f.ty {
Ty::AccountLoader(_) => quote! {#ident.load()?},
_ => quote! {#ident},
};
let my_key = match &f.ty {
Ty::LazyAccount(_) => {
let load_ident = format_ident!("load_{}", target.to_token_stream().to_string());
quote! { *#field.#load_ident()? }
}
_ => quote! { #field.#target },
};
let error = generate_custom_error(
ident,
&c.error,
quote! { ConstraintHasOne },
&Some(&(quote! { my_key }, quote! { target_key })),
);
let target_optional_check =
OptionalCheckScope::new_with_field(accs, &field).generate_check(target);
quote! {
{
#target_optional_check
let my_key = #my_key;
let target_key = #target.key();
if my_key != target_key {
return #error;
}
}
}
}
pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro2::TokenStream {
let ident = &f.ident;
let account_ref = generate_account_ref(f);
let error = generate_custom_error(ident, &c.error, quote! { ConstraintSigner }, &None);
quote! {
if !#account_ref.is_signer {
return #error;
}
}
}
pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2::TokenStream {
let raw = &c.raw;
let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw }, &None);
quote! {
if !(#raw) {
return #error;
}
}
}
pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
let ident = &f.ident;
let maybe_deref = if match &f.ty {
Ty::Account(AccountTy { boxed, .. })
| Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => *boxed,
_ => false,
} {
quote!(*)
} else {
Default::default()
};
let owner_address = &c.owner_address;
let error = generate_custom_error(
ident,
&c.error,
quote! { ConstraintOwner },
&Some(&(quote! { *my_owner }, quote! { owner_address })),
);
quote! {
{
let my_owner = AsRef::<AccountInfo>::as_ref(& #maybe_deref #ident).owner;
let owner_address = #owner_address;
if my_owner != &owner_address {
return #error;
}
}
}
}
pub fn generate_constraint_rent_exempt(
f: &Field,
c: &ConstraintRentExempt,
) -> proc_macro2::TokenStream {
let ident = &f.ident;
let name_str = ident.to_string();
let info = quote! {
#ident.to_account_info()
};
match c {
ConstraintRentExempt::Skip => quote! {},
ConstraintRentExempt::Enforce => quote! {
if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
}
},
}
}
fn generate_constraint_realloc(
f: &Field,
c: &ConstraintReallocGroup,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let account_name = field.to_string();
let new_space = &c.space;
let payer = &c.payer;
let mut optional_check_scope = OptionalCheckScope::new_with_field(accs, field);
let payer_optional_check = optional_check_scope.generate_check(payer);
let system_program_optional_check =
optional_check_scope.generate_check(quote! {system_program});
quote! {
if __reallocs.contains(&#field.key()) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountDuplicateReallocs).with_account_name(#account_name));
}
let __anchor_rent = anchor_lang::prelude::Rent::get()?;
let __field_info = #field.to_account_info();
let __new_rent_minimum = __anchor_rent.minimum_balance(#new_space);
let __delta_space = (::std::convert::TryInto::<isize>::try_into(#new_space).unwrap())
.checked_sub(::std::convert::TryInto::try_into(__field_info.data_len()).unwrap())
.unwrap();
if __delta_space != 0 {
#payer_optional_check
if __delta_space > 0 {
#system_program_optional_check
if ::std::convert::TryInto::<usize>::try_into(__delta_space).unwrap() > anchor_lang::solana_program::entrypoint::MAX_PERMITTED_DATA_INCREASE {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountReallocExceedsLimit).with_account_name(#account_name));
}
if __new_rent_minimum > __field_info.lamports() {
anchor_lang::system_program::transfer(
anchor_lang::context::CpiContext::new(
system_program.key(),
anchor_lang::system_program::Transfer {
from: #payer.to_account_info(),
to: __field_info.clone(),
},
),
__new_rent_minimum.checked_sub(__field_info.lamports()).unwrap(),
)?;
}
} else {
let __lamport_amt = __field_info.lamports().checked_sub(__new_rent_minimum).unwrap();
**#payer.to_account_info().lamports.borrow_mut() = #payer.to_account_info().lamports().checked_add(__lamport_amt).unwrap();
**__field_info.lamports.borrow_mut() = __field_info.lamports().checked_sub(__lamport_amt).unwrap();
}
__field_info.resize(#new_space)?;
__reallocs.insert(#field.key());
}
}
}
fn generate_constraint_init_group(
f: &Field,
c: &ConstraintInitGroup,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let name_str = f.ident.to_string();
let ty_decl = f.ty_decl(true);
let if_needed = if c.if_needed {
quote! {true}
} else {
quote! {false}
};
let space = &c.space;
let payer = &c.payer;
let from_account_info = f.from_account_info(Some(&c.kind), true);
let from_account_info_unchecked = f.from_account_info(Some(&c.kind), false);
let account_ref = generate_account_ref(f);
let (find_pda, seeds_with_bump) = match &c.seeds {
None => (quote! {}, quote! {}),
Some(c) => match &c.seeds {
SeedsExpr::List(list) => {
let maybe_seeds_plus_comma = (!list.is_empty()).then(|| quote! { #list, });
let validate_pda = if let Some(b) = &c.bump {
quote! {
if #field.key() != __pda_address {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str)
.with_pubkeys((#field.key(), __pda_address)));
}
if __bump != #b {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str)
.with_values((__bump, #b)));
}
}
} else {
quote! {
if #field.key() != __pda_address {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str)
.with_pubkeys((#field.key(), __pda_address)));
}
}
};
let bump_tok = if f.is_optional {
quote!(Some(__bump))
} else {
quote!(__bump)
};
(
quote! {
let (__pda_address, __bump) = Pubkey::find_program_address(
&[ #maybe_seeds_plus_comma ],
__program_id,
);
__bumps.#field = #bump_tok;
#validate_pda
},
quote! {
&[
#maybe_seeds_plus_comma
&[__bump][..]
][..]
},
)
}
SeedsExpr::Expr(expr) => {
let bump_tok = if f.is_optional {
quote!(Some(__bump))
} else {
quote!(__bump)
};
(
quote! {
let __seeds_slice: &[&[u8]] = #expr;
let (__pda_address, __bump) =
Pubkey::find_program_address(__seeds_slice, __program_id);
__bumps.#field = #bump_tok;
let mut __signer_seeds_vec: ::std::vec::Vec<&[u8]> = __seeds_slice.to_vec();
__signer_seeds_vec.push(&[__bump][..]);
let __signer_seeds = __signer_seeds_vec;
if #field.key() != __pda_address {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str)
.with_pubkeys((#field.key(), __pda_address)));
}
},
quote! { &__signer_seeds[..] },
)
}
},
};
let system_program = "e! {system_program};
let associated_token_program = "e! {associated_token_program};
let rent = "e! {rent};
let mut check_scope = OptionalCheckScope::new_with_field(accs, field);
match &c.kind {
InitKind::Token {
owner,
mint,
token_program,
} => {
let token_program = match token_program {
Some(t) => t.to_token_stream(),
None => quote! {token_program},
};
let owner_optional_check = check_scope.generate_check(owner);
let mint_optional_check = check_scope.generate_check(mint);
let system_program_optional_check = check_scope.generate_check(system_program);
let token_program_optional_check = check_scope.generate_check(&token_program);
let rent_optional_check = check_scope.generate_check(rent);
let optional_checks = quote! {
#system_program_optional_check
#token_program_optional_check
#rent_optional_check
#owner_optional_check
#mint_optional_check
};
let payer_optional_check = check_scope.generate_check(payer);
let token_account_space = generate_get_token_account_space(mint);
let create_account = generate_create_account(
field,
quote! {#token_account_space},
quote! {&#token_program.key()},
quote! {#payer},
seeds_with_bump,
);
quote! {
#find_pda
let #field: #ty_decl = ({ #[inline(never)] || {
#optional_checks
let owner_program = #account_ref.owner;
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
#payer_optional_check
#create_account
let cpi_program_id = #token_program.key();
let accounts = ::anchor_spl::token_interface::InitializeAccount3 {
account: #field.to_account_info(),
mint: #mint.to_account_info(),
authority: #owner.to_account_info(),
};
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program_id, accounts);
::anchor_spl::token_interface::initialize_account3(cpi_ctx)?;
}
let pa: #ty_decl = #from_account_info_unchecked;
if #if_needed {
if pa.mint != #mint.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key())));
}
if pa.owner != #owner.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
}
if owner_program != &#token_program.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
}
}
Ok(pa)
}})()?;
}
}
InitKind::AssociatedToken {
owner,
mint,
token_program,
} => {
let token_program = match token_program {
Some(t) => t.to_token_stream(),
None => quote! {token_program},
};
let owner_optional_check = check_scope.generate_check(owner);
let mint_optional_check = check_scope.generate_check(mint);
let system_program_optional_check = check_scope.generate_check(system_program);
let token_program_optional_check = check_scope.generate_check(&token_program);
let associated_token_program_optional_check =
check_scope.generate_check(associated_token_program);
let rent_optional_check = check_scope.generate_check(rent);
let optional_checks = quote! {
#system_program_optional_check
#token_program_optional_check
#associated_token_program_optional_check
#rent_optional_check
#owner_optional_check
#mint_optional_check
};
let payer_optional_check = check_scope.generate_check(payer);
quote! {
#find_pda
let #field: #ty_decl = ({ #[inline(never)] || {
#optional_checks
let owner_program = #account_ref.owner;
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
#payer_optional_check
::anchor_spl::associated_token::create(
anchor_lang::context::CpiContext::new(
associated_token_program.key(),
::anchor_spl::associated_token::Create {
payer: #payer.to_account_info(),
associated_token: #field.to_account_info(),
authority: #owner.to_account_info(),
mint: #mint.to_account_info(),
system_program: system_program.to_account_info(),
token_program: #token_program.to_account_info(),
}
)
)?;
}
let pa: #ty_decl = #from_account_info_unchecked;
if #if_needed {
if pa.mint != #mint.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key())));
}
if pa.owner != #owner.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
}
if owner_program != &#token_program.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociatedTokenTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
}
if pa.key() != ::anchor_spl::associated_token::get_associated_token_address_with_program_id(&#owner.key(), &#mint.key(), &#token_program.key()) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str));
}
}
Ok(pa)
}})()?;
}
}
InitKind::Mint {
owner,
decimals,
freeze_authority,
token_program,
group_pointer_authority,
group_pointer_group_address,
group_member_pointer_authority,
group_member_pointer_member_address,
metadata_pointer_authority,
metadata_pointer_metadata_address,
close_authority,
permanent_delegate,
transfer_hook_authority,
transfer_hook_program_id,
} => {
let token_program = match token_program {
Some(t) => t.to_token_stream(),
None => quote! {token_program},
};
let owner_optional_check = check_scope.generate_check(owner);
let freeze_authority_optional_check = match freeze_authority {
Some(fa) => check_scope.generate_check(fa),
None => quote! {},
};
let group_pointer_authority_check = match group_pointer_authority {
Some(gpa) => check_scope.generate_check(gpa),
None => quote! {},
};
let group_pointer_group_address_check = match group_pointer_group_address {
Some(gpga) => check_scope.generate_check(gpga),
None => quote! {},
};
let group_member_pointer_authority_check = match group_member_pointer_authority {
Some(gmpa) => check_scope.generate_check(gmpa),
None => quote! {},
};
let group_member_pointer_member_address_check =
match group_member_pointer_member_address {
Some(gmpm) => check_scope.generate_check(gmpm),
None => quote! {},
};
let metadata_pointer_authority_check = match metadata_pointer_authority {
Some(mpa) => check_scope.generate_check(mpa),
None => quote! {},
};
let metadata_pointer_metadata_address_check = match metadata_pointer_metadata_address {
Some(mpma) => check_scope.generate_check(mpma),
None => quote! {},
};
let close_authority_check = match close_authority {
Some(ca) => check_scope.generate_check(ca),
None => quote! {},
};
let transfer_hook_authority_check = match transfer_hook_authority {
Some(tha) => check_scope.generate_check(tha),
None => quote! {},
};
let transfer_hook_program_id_check = match transfer_hook_program_id {
Some(thpid) => check_scope.generate_check(thpid),
None => quote! {},
};
let permanent_delegate_check = match permanent_delegate {
Some(pd) => check_scope.generate_check(pd),
None => quote! {},
};
let system_program_optional_check = check_scope.generate_check(system_program);
let token_program_optional_check = check_scope.generate_check(&token_program);
let rent_optional_check = check_scope.generate_check(rent);
let optional_checks = quote! {
#system_program_optional_check
#token_program_optional_check
#rent_optional_check
#owner_optional_check
#freeze_authority_optional_check
#group_pointer_authority_check
#group_pointer_group_address_check
#group_member_pointer_authority_check
#group_member_pointer_member_address_check
#metadata_pointer_authority_check
#metadata_pointer_metadata_address_check
#close_authority_check
#transfer_hook_authority_check
#transfer_hook_program_id_check
#permanent_delegate_check
};
let payer_optional_check = check_scope.generate_check(payer);
let mut extensions = vec![];
if group_pointer_authority.is_some() || group_pointer_group_address.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupPointer});
}
if group_member_pointer_authority.is_some()
|| group_member_pointer_member_address.is_some()
{
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupMemberPointer});
}
if metadata_pointer_authority.is_some() || metadata_pointer_metadata_address.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MetadataPointer});
}
if close_authority.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MintCloseAuthority});
}
if transfer_hook_authority.is_some() || transfer_hook_program_id.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::TransferHook});
}
if permanent_delegate.is_some() {
extensions.push(quote! {::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::PermanentDelegate});
}
let mint_space = if extensions.is_empty() {
quote! { ::anchor_spl::token::Mint::LEN }
} else {
quote! { ::anchor_spl::token_interface::find_mint_account_size(Some(&vec![#(#extensions),*]))? }
};
let extensions = if extensions.is_empty() {
quote! {Option::<&::anchor_spl::token_interface::ExtensionsVec>::None}
} else {
quote! {Option::<&::anchor_spl::token_interface::ExtensionsVec>::Some(&vec![#(#extensions),*])}
};
let freeze_authority = match freeze_authority {
Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
};
let group_pointer_authority = match group_pointer_authority {
Some(gpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gpa.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let group_pointer_group_address = match group_pointer_group_address {
Some(gpga) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gpga.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let group_member_pointer_authority = match group_member_pointer_authority {
Some(gmpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gmpa.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let group_member_pointer_member_address = match group_member_pointer_member_address {
Some(gmpma) => {
quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#gmpma.key()) }
}
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let metadata_pointer_authority = match metadata_pointer_authority {
Some(mpa) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#mpa.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let metadata_pointer_metadata_address = match metadata_pointer_metadata_address {
Some(mpma) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#mpma.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let close_authority = match close_authority {
Some(ca) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#ca.key()) },
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
};
let permanent_delegate = match permanent_delegate {
Some(pd) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#pd.key()) },
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
};
let transfer_hook_authority = match transfer_hook_authority {
Some(tha) => quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#tha.key()) },
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let transfer_hook_program_id = match transfer_hook_program_id {
Some(thpid) => {
quote! { Option::<anchor_lang::prelude::Pubkey>::Some(#thpid.key()) }
}
None => quote! { Option::<anchor_lang::prelude::Pubkey>::None },
};
let create_account = generate_create_account(
field,
mint_space,
quote! {&#token_program.key()},
quote! {#payer},
seeds_with_bump,
);
quote! {
#find_pda
let #field: #ty_decl = ({ #[inline(never)] || {
#optional_checks
let owner_program = AsRef::<AccountInfo>::as_ref(&#field).owner;
if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
#payer_optional_check
#create_account
let cpi_program_id = #token_program.key();
if let Some(extensions) = #extensions {
for e in extensions {
match e {
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupPointer => {
::anchor_spl::token_interface::group_pointer_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::GroupPointerInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #group_pointer_authority, #group_pointer_group_address)?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::GroupMemberPointer => {
::anchor_spl::token_interface::group_member_pointer_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::GroupMemberPointerInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #group_member_pointer_authority, #group_member_pointer_member_address)?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MetadataPointer => {
::anchor_spl::token_interface::metadata_pointer_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::MetadataPointerInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #metadata_pointer_authority, #metadata_pointer_metadata_address)?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::MintCloseAuthority => {
::anchor_spl::token_interface::mint_close_authority_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::MintCloseAuthorityInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #close_authority)?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::TransferHook => {
::anchor_spl::token_interface::transfer_hook_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::TransferHookInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #transfer_hook_authority, #transfer_hook_program_id)?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::NonTransferable => {
::anchor_spl::token_interface::non_transferable_mint_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::NonTransferableMintInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}))?;
},
::anchor_spl::token_interface::spl_token_2022::extension::ExtensionType::PermanentDelegate => {
::anchor_spl::token_interface::permanent_delegate_initialize(anchor_lang::context::CpiContext::new(cpi_program_id, ::anchor_spl::token_interface::PermanentDelegateInitialize {
token_program_id: #token_program.to_account_info(),
mint: #field.to_account_info(),
}), #permanent_delegate.unwrap())?;
},
_ => unimplemented!("{e:?}"),
}
};
}
let accounts = ::anchor_spl::token_interface::InitializeMint2 {
mint: #field.to_account_info(),
};
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program_id, accounts);
::anchor_spl::token_interface::initialize_mint2(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
}
let pa: #ty_decl = #from_account_info_unchecked;
if #if_needed {
if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority).with_account_name(#name_str));
}
if pa.freeze_authority
.as_ref()
.map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
.unwrap_or(#freeze_authority.is_some()) {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority).with_account_name(#name_str));
}
if pa.decimals != #decimals {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str).with_values((pa.decimals, #decimals)));
}
if owner_program != &#token_program.key() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintTokenProgram).with_account_name(#name_str).with_pubkeys((*owner_program, #token_program.key())));
}
}
Ok(pa)
}})()?;
}
}
InitKind::Program { owner } | InitKind::Interface { owner } => {
let space = quote! {let space = #space;};
let system_program_optional_check = check_scope.generate_check(system_program);
let (owner, owner_optional_check) = match owner {
None => (
quote! {
__program_id
},
quote! {},
),
Some(o) => {
let owner_optional_check = check_scope.clone().generate_check(o);
(
quote! {
&#o
},
owner_optional_check,
)
}
};
let payer_optional_check = check_scope.generate_check(payer);
let optional_checks = quote! {
#system_program_optional_check
};
let create_account = generate_create_account(
field,
quote! {space},
owner.clone(),
quote! {#payer},
seeds_with_bump,
);
quote! {
#find_pda
let #field = ({ #[inline(never)] || {
#optional_checks
let actual_field = #account_ref;
let actual_owner = actual_field.owner;
#space
let pa: #ty_decl = if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID {
#payer_optional_check
#create_account
#from_account_info_unchecked
} else {
#from_account_info
};
if #if_needed {
#owner_optional_check
if space != actual_field.data_len() {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSpace).with_account_name(#name_str).with_values((space, actual_field.data_len())));
}
if actual_owner != #owner {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintOwner).with_account_name(#name_str).with_pubkeys((*actual_owner, *#owner)));
}
{
let required_lamports = __anchor_rent.minimum_balance(space);
if pa.to_account_info().lamports() < required_lamports {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
}
}
}
Ok(pa)
}})()?;
}
}
}
}
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
if c.is_init {
quote! {}
} else {
let name = &f.ident;
let name_str = name.to_string();
let deriving_program_id = c
.program_seed
.clone()
.map(|program_id| quote! { #program_id.key() })
.unwrap_or(quote! { __program_id });
let bump_store = if f.is_optional {
quote!(Some(__bump))
} else {
quote!(__bump)
};
let define_pda = match (&c.seeds, &c.bump) {
(SeedsExpr::List(list), None) => {
let maybe_seeds_plus_comma = (!list.is_empty()).then(|| quote! { #list, });
quote! {
let (__pda_address, __bump) = Pubkey::find_program_address(
&[ #maybe_seeds_plus_comma ],
&#deriving_program_id,
);
__bumps.#name = #bump_store;
}
}
(SeedsExpr::List(list), Some(b)) => {
let maybe_seeds_plus_comma = (!list.is_empty()).then(|| quote! { #list, });
quote! {
let __pda_address = Pubkey::create_program_address(
&[ #maybe_seeds_plus_comma &[#b][..] ],
&#deriving_program_id,
).map_err(|_| anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str))?;
}
}
(SeedsExpr::Expr(expr), None) => quote! {
let __seeds_slice: &[&[u8]] = #expr;
let (__pda_address, __bump) =
Pubkey::find_program_address(__seeds_slice, &#deriving_program_id);
__bumps.#name = #bump_store;
},
(SeedsExpr::Expr(expr), Some(b)) => quote! {
let __bump_bytes = [#b];
let __seeds_vec: ::std::vec::Vec<&[u8]> = [#expr, &[&__bump_bytes[..]]].concat();
let __pda_address = Pubkey::create_program_address(
&__seeds_vec[..],
&#deriving_program_id,
).map_err(|_| anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds
).with_account_name(#name_str))?;
},
};
quote! {
#define_pda
if #name.key() != __pda_address {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address)));
}
}
}
}
fn generate_constraint_associated_token(
f: &Field,
c: &ConstraintAssociatedToken,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let name_str = name.to_string();
let account_ref = generate_account_ref(f);
let wallet_address = &c.wallet;
let spl_token_mint_address = &c.mint;
let mut optional_check_scope = OptionalCheckScope::new_with_field(accs, name);
let wallet_address_optional_check = optional_check_scope.generate_check(wallet_address);
let spl_token_mint_address_optional_check =
optional_check_scope.generate_check(spl_token_mint_address);
let optional_checks = quote! {
#wallet_address_optional_check
#spl_token_mint_address_optional_check
};
let token_program_check = match &c.token_program {
Some(token_program) => {
let token_program_optional_check = optional_check_scope.generate_check(token_program);
quote! {
#token_program_optional_check
if #account_ref.owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintAssociatedTokenTokenProgram.into()); }
}
}
None => quote! {},
};
let get_associated_token_address = match &c.token_program {
Some(token_program) => quote! {
::anchor_spl::associated_token::get_associated_token_address_with_program_id(&wallet_address, &#spl_token_mint_address.key(), &#token_program.key())
},
None => quote! {
::anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key())
},
};
quote! {
{
#optional_checks
#token_program_check
let my_owner = #name.owner;
let wallet_address = #wallet_address.key();
if my_owner != wallet_address {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((my_owner, wallet_address)));
}
let __associated_token_address = #get_associated_token_address;
let my_key = #name.key();
if my_key != __associated_token_address {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str).with_pubkeys((my_key, __associated_token_address)));
}
}
}
}
fn generate_constraint_token_account(
f: &Field,
c: &ConstraintTokenAccountGroup,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let account_ref = generate_account_ref(f);
let mut optional_check_scope = OptionalCheckScope::new_with_field(accs, name);
let authority_check = match &c.authority {
Some(authority) => {
let authority_optional_check = optional_check_scope.generate_check(authority);
quote! {
#authority_optional_check
if #name.owner != #authority.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); }
}
}
None => quote! {},
};
let mint_check = match &c.mint {
Some(mint) => {
let mint_optional_check = optional_check_scope.generate_check(mint);
quote! {
#mint_optional_check
if #name.mint != #mint.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenMint.into()); }
}
}
None => quote! {},
};
let token_program_check = match &c.token_program {
Some(token_program) => {
let token_program_optional_check = optional_check_scope.generate_check(token_program);
quote! {
#token_program_optional_check
if #account_ref.owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenTokenProgram.into()); }
}
}
None => quote! {},
};
quote! {
{
#authority_check
#mint_check
#token_program_check
}
}
}
fn generate_constraint_mint(
f: &Field,
c: &ConstraintTokenMintGroup,
accs: &AccountsStruct,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let account_ref = generate_account_ref(f);
let decimal_check = match &c.decimals {
Some(decimals) => quote! {
if #name.decimals != #decimals {
return Err(anchor_lang::error::ErrorCode::ConstraintMintDecimals.into());
}
},
None => quote! {},
};
let mut optional_check_scope = OptionalCheckScope::new_with_field(accs, name);
let mint_authority_check = match &c.mint_authority {
Some(mint_authority) => {
let mint_authority_optional_check = optional_check_scope.generate_check(mint_authority);
quote! {
#mint_authority_optional_check
if #name.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#mint_authority.key()) {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority.into());
}
}
}
None => quote! {},
};
let freeze_authority_check = match &c.freeze_authority {
Some(freeze_authority) => {
let freeze_authority_optional_check =
optional_check_scope.generate_check(freeze_authority);
quote! {
#freeze_authority_optional_check
if #name.freeze_authority != anchor_lang::solana_program::program_option::COption::Some(#freeze_authority.key()) {
return Err(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority.into());
}
}
}
None => quote! {},
};
let token_program_check = match &c.token_program {
Some(token_program) => {
let token_program_optional_check = optional_check_scope.generate_check(token_program);
quote! {
#token_program_optional_check
if #account_ref.owner != &#token_program.key() { return Err(anchor_lang::error::ErrorCode::ConstraintMintTokenProgram.into()); }
}
}
None => quote! {},
};
let group_pointer_authority_check = match &c.group_pointer_authority {
Some(group_pointer_authority) => {
let group_pointer_authority_optional_check =
optional_check_scope.generate_check(group_pointer_authority);
quote! {
let group_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_pointer::GroupPointer>(#account_ref);
if group_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtension.into());
}
#group_pointer_authority_optional_check
if group_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_pointer_authority.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtensionAuthority.into());
}
}
}
None => quote! {},
};
let group_pointer_group_address_check = match &c.group_pointer_group_address {
Some(group_pointer_group_address) => {
let group_pointer_group_address_optional_check =
optional_check_scope.generate_check(group_pointer_group_address);
quote! {
let group_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_pointer::GroupPointer>(#account_ref);
if group_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtension.into());
}
#group_pointer_group_address_optional_check
if group_pointer.unwrap().group_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_pointer_group_address.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupPointerExtensionGroupAddress.into());
}
}
}
None => quote! {},
};
let group_member_pointer_authority_check = match &c.group_member_pointer_authority {
Some(group_member_pointer_authority) => {
let group_member_pointer_authority_optional_check =
optional_check_scope.generate_check(group_member_pointer_authority);
quote! {
let group_member_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_member_pointer::GroupMemberPointer>(#account_ref);
if group_member_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtension.into());
}
#group_member_pointer_authority_optional_check
if group_member_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_member_pointer_authority.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtensionAuthority.into());
}
}
}
None => quote! {},
};
let group_member_pointer_member_address_check = match &c.group_member_pointer_member_address {
Some(group_member_pointer_member_address) => {
let group_member_pointer_member_address_optional_check =
optional_check_scope.generate_check(group_member_pointer_member_address);
quote! {
let group_member_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::group_member_pointer::GroupMemberPointer>(#account_ref);
if group_member_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtension.into());
}
#group_member_pointer_member_address_optional_check
if group_member_pointer.unwrap().member_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#group_member_pointer_member_address.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintGroupMemberPointerExtensionMemberAddress.into());
}
}
}
None => quote! {},
};
let metadata_pointer_authority_check = match &c.metadata_pointer_authority {
Some(metadata_pointer_authority) => {
let metadata_pointer_authority_optional_check =
optional_check_scope.generate_check(metadata_pointer_authority);
quote! {
let metadata_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::metadata_pointer::MetadataPointer>(#account_ref);
if metadata_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtension.into());
}
#metadata_pointer_authority_optional_check
if metadata_pointer.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#metadata_pointer_authority.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtensionAuthority.into());
}
}
}
None => quote! {},
};
let metadata_pointer_metadata_address_check = match &c.metadata_pointer_metadata_address {
Some(metadata_pointer_metadata_address) => {
let metadata_pointer_metadata_address_optional_check =
optional_check_scope.generate_check(metadata_pointer_metadata_address);
quote! {
let metadata_pointer = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::metadata_pointer::MetadataPointer>(#account_ref);
if metadata_pointer.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtension.into());
}
#metadata_pointer_metadata_address_optional_check
if metadata_pointer.unwrap().metadata_address != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#metadata_pointer_metadata_address.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintMetadataPointerExtensionMetadataAddress.into());
}
}
}
None => quote! {},
};
let close_authority_check = match &c.close_authority {
Some(close_authority) => {
let close_authority_optional_check =
optional_check_scope.generate_check(close_authority);
quote! {
let close_authority = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::mint_close_authority::MintCloseAuthority>(#account_ref);
if close_authority.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintCloseAuthorityExtension.into());
}
#close_authority_optional_check
if close_authority.unwrap().close_authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#close_authority.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintCloseAuthorityExtensionAuthority.into());
}
}
}
None => quote! {},
};
let permanent_delegate_check = match &c.permanent_delegate {
Some(permanent_delegate) => {
let permanent_delegate_optional_check =
optional_check_scope.generate_check(permanent_delegate);
quote! {
let permanent_delegate = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::permanent_delegate::PermanentDelegate>(#account_ref);
if permanent_delegate.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintPermanentDelegateExtension.into());
}
#permanent_delegate_optional_check
if permanent_delegate.unwrap().delegate != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#permanent_delegate.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintPermanentDelegateExtensionDelegate.into());
}
}
}
None => quote! {},
};
let transfer_hook_authority_check = match &c.transfer_hook_authority {
Some(transfer_hook_authority) => {
let transfer_hook_authority_optional_check =
optional_check_scope.generate_check(transfer_hook_authority);
quote! {
let transfer_hook = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::transfer_hook::TransferHook>(#account_ref);
if transfer_hook.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtension.into());
}
#transfer_hook_authority_optional_check
if transfer_hook.unwrap().authority != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#transfer_hook_authority.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtensionAuthority.into());
}
}
}
None => quote! {},
};
let transfer_hook_program_id_check = match &c.transfer_hook_program_id {
Some(transfer_hook_program_id) => {
let transfer_hook_program_id_optional_check =
optional_check_scope.generate_check(transfer_hook_program_id);
quote! {
let transfer_hook = ::anchor_spl::token_interface::get_mint_extension_data::<::anchor_spl::token_interface::spl_token_2022::extension::transfer_hook::TransferHook>(#account_ref);
if transfer_hook.is_err() {
return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtension.into());
}
#transfer_hook_program_id_optional_check
if transfer_hook.unwrap().program_id != ::anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey::try_from(Some(#transfer_hook_program_id.key()))? {
return Err(anchor_lang::error::ErrorCode::ConstraintMintTransferHookExtensionProgramId.into());
}
}
}
None => quote! {},
};
quote! {
{
#decimal_check
#mint_authority_check
#freeze_authority_check
#token_program_check
#group_pointer_authority_check
#group_pointer_group_address_check
#group_member_pointer_authority_check
#group_member_pointer_member_address_check
#metadata_pointer_authority_check
#metadata_pointer_metadata_address_check
#close_authority_check
#permanent_delegate_check
#transfer_hook_authority_check
#transfer_hook_program_id_check
}
}
}
#[derive(Clone, Debug)]
pub struct OptionalCheckScope<'a> {
seen: HashSet<String>,
accounts: &'a AccountsStruct,
}
impl<'a> OptionalCheckScope<'a> {
pub fn new(accounts: &'a AccountsStruct) -> Self {
Self {
seen: HashSet::new(),
accounts,
}
}
pub fn new_with_field(accounts: &'a AccountsStruct, field: impl ToString) -> Self {
let mut check_scope = Self::new(accounts);
check_scope.seen.insert(field.to_string());
check_scope
}
pub fn generate_check(&mut self, field: impl ToTokens) -> TokenStream {
let field_name = parser::tts_to_string(&field);
if self.seen.contains(&field_name) {
quote! {}
} else {
self.seen.insert(field_name.clone());
if self.accounts.is_field_optional(&field) {
quote! {
let #field = if let Some(ref account) = #field {
account
} else {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAccountIsNone).with_account_name(#field_name));
};
}
} else {
quote! {}
}
}
}
}
fn generate_get_token_account_space(mint: &Expr) -> proc_macro2::TokenStream {
quote! {
{
let mint_info = #mint.to_account_info();
if *mint_info.owner == ::anchor_spl::token_2022::Token2022::id() {
use ::anchor_spl::token_2022::spl_token_2022::extension::{BaseStateWithExtensions, ExtensionType, StateWithExtensions};
use ::anchor_spl::token_2022::spl_token_2022::state::{Account, Mint};
let mint_data = mint_info.try_borrow_data()?;
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_data)?;
let mint_extensions = mint_state.get_extension_types()?;
let required_extensions = ExtensionType::get_required_init_account_extensions(&mint_extensions);
ExtensionType::try_calculate_account_len::<Account>(&required_extensions)?
} else {
::anchor_spl::token::TokenAccount::LEN
}
}
}
}
fn generate_create_account(
field: &Ident,
space: proc_macro2::TokenStream,
owner: proc_macro2::TokenStream,
payer: proc_macro2::TokenStream,
seeds_with_nonce: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
quote! {
let __current_lamports = #field.lamports();
if __current_lamports == 0 {
let space = #space;
let lamports = __anchor_rent.minimum_balance(space);
let cpi_accounts = anchor_lang::system_program::CreateAccount {
from: #payer.to_account_info(),
to: #field.to_account_info()
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.key(), cpi_accounts);
anchor_lang::system_program::create_account(cpi_context.with_signer(&[#seeds_with_nonce]), lamports, space as u64, #owner)?;
} else {
require_keys_neq!(#payer.key(), #field.key(), anchor_lang::error::ErrorCode::TryingToInitPayerAsProgramAccount);
let required_lamports = __anchor_rent
.minimum_balance(#space)
.max(1)
.saturating_sub(__current_lamports);
if required_lamports > 0 {
let cpi_accounts = anchor_lang::system_program::Transfer {
from: #payer.to_account_info(),
to: #field.to_account_info(),
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.key(), cpi_accounts);
anchor_lang::system_program::transfer(cpi_context, required_lamports)?;
}
let cpi_accounts = anchor_lang::system_program::Allocate {
account_to_allocate: #field.to_account_info()
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.key(), cpi_accounts);
anchor_lang::system_program::allocate(cpi_context.with_signer(&[#seeds_with_nonce]), #space as u64)?;
let cpi_accounts = anchor_lang::system_program::Assign {
account_to_assign: #field.to_account_info()
};
let cpi_context = anchor_lang::context::CpiContext::new(system_program.key(), cpi_accounts);
anchor_lang::system_program::assign(cpi_context.with_signer(&[#seeds_with_nonce]), #owner)?;
}
}
}
pub fn generate_constraint_executable(
f: &Field,
_c: &ConstraintExecutable,
) -> proc_macro2::TokenStream {
let name_str = f.ident.to_string();
let account_ref = generate_account_ref(f);
quote! {
if !#account_ref.executable {
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintExecutable).with_account_name(#name_str));
}
}
}
fn generate_custom_error(
account_name: &Ident,
custom_error: &Option<Expr>,
error: proc_macro2::TokenStream,
compared_values: &Option<&(proc_macro2::TokenStream, proc_macro2::TokenStream)>,
) -> proc_macro2::TokenStream {
let account_name = account_name.to_string();
let mut error = match custom_error {
Some(error) => {
quote! { anchor_lang::error::Error::from(#error).with_account_name(#account_name) }
}
None => {
quote! { anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::#error).with_account_name(#account_name) }
}
};
let compared_values = match compared_values {
Some((left, right)) => quote! { .with_pubkeys((#left, #right)) },
None => quote! {},
};
error.extend(compared_values);
quote! {
Err(#error)
}
}
fn generate_account_ref(field: &Field) -> proc_macro2::TokenStream {
let name = &field.ident;
match &field.ty {
Ty::AccountInfo => quote!(&#name),
Ty::Account(acc) if acc.boxed => quote!(AsRef::<AccountInfo>::as_ref(#name.as_ref())),
Ty::InterfaceAccount(acc) if acc.boxed => {
quote!(AsRef::<AccountInfo>::as_ref(#name.as_ref()))
}
_ => quote!(AsRef::<AccountInfo>::as_ref(&#name)),
}
}