12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736 |
- use quote::{format_ident, quote};
- use std::collections::HashSet;
- use crate::*;
- 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 the field is optional we do all the inner checks as if the account
- // wasn't optional. If the account is init we also need to return an Option
- // by wrapping the resulting value with Some or returning None if it doesn't exist.
- 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)*
- }
- }
- // Linearizes the constraint group so that constraints with dependencies
- // run after those without.
- pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
- let ConstraintGroup {
- init,
- zeroed,
- mutable,
- 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) = 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::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);
- // Require `zero` constraint accounts to be unique by:
- //
- // 1. Getting the names of all accounts that have the `zero` or the `init` constraints and are
- // declared before the current field (in order to avoid checking the same field).
- // 2. Comparing the key of the current field with all the previous fields' keys.
- // 3. Returning an error if a match is found.
- 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 zero = &c.zero;
- 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! {
- // Blocks duplicate account reallocs in a single instruction to prevent accidental account overwrites
- // and to ensure the calculation of the change in bytes is based on account size at program entry
- // which inheritantly guarantee idempotency.
- 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.to_account_info(),
- 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.realloc(#new_space, #zero)?;
- __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;
- // Convert from account info to account context wrapper type.
- 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);
- // PDA bump seeds.
- let (find_pda, seeds_with_bump) = match &c.seeds {
- None => (quote! {}, quote! {}),
- Some(c) => {
- let seeds = &mut c.seeds.clone();
- // If the seeds came with a trailing comma, we need to chop it off
- // before we interpolate them below.
- if let Some(pair) = seeds.pop() {
- seeds.push_value(pair.into_value());
- }
- let maybe_seeds_plus_comma = (!seeds.is_empty()).then(|| {
- quote! { #seeds, }
- });
- let validate_pda = {
- // If the bump is provided with init *and target*, then force it to be the
- // canonical bump.
- //
- // Note that for `#[account(init, seeds)]`, find_program_address has already
- // been run in the init constraint find_pda variable.
- if c.bump.is_some() {
- let b = c.bump.as_ref().unwrap();
- 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 {
- // Init seeds but no bump. We already used the canonical to create bump so
- // just check the address.
- //
- // Note that for `#[account(init, seeds)]`, find_program_address has already
- // been run in the init constraint find_pda variable.
- 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 = 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;
- #validate_pda
- },
- quote! {
- &[
- #maybe_seeds_plus_comma
- &[__bump][..]
- ][..]
- },
- )
- }
- };
- // Optional check idents
- 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! {
- // Define the bump and pda variable.
- #find_pda
- let #field: #ty_decl = ({ #[inline(never)] || {
- // Checks that all the required accounts for this operation are present.
- #optional_checks
- let owner_program = #account_ref.owner;
- if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
- #payer_optional_check
- // Create the account with the system program.
- #create_account
- // Initialize the token account.
- let cpi_program = #token_program.to_account_info();
- 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, 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! {
- // Define the bump and pda variable.
- #find_pda
- let #field: #ty_decl = ({ #[inline(never)] || {
- // Checks that all the required accounts for this operation are present.
- #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.to_account_info(),
- ::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! {},
- };
- // extension checks
- 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! {
- // Define the bump and pda variable.
- #find_pda
- let #field: #ty_decl = ({ #[inline(never)] || {
- // Checks that all the required accounts for this operation are present.
- #optional_checks
- let owner_program = AsRef::<AccountInfo>::as_ref(&#field).owner;
- if !#if_needed || owner_program == &anchor_lang::solana_program::system_program::ID {
- // Define payer variable.
- #payer_optional_check
- // Create the account with the system program.
- #create_account
- // Initialize extensions.
- 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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::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(#token_program.to_account_info(), ::anchor_spl::token_interface::PermanentDelegateInitialize {
- token_program_id: #token_program.to_account_info(),
- mint: #field.to_account_info(),
- }), #permanent_delegate.unwrap())?;
- },
- // All extensions specified by the user should be implemented.
- // If this line runs, it means there is a bug in the codegen.
- _ => unimplemented!("{e:?}"),
- }
- };
- }
- // Initialize the mint account.
- let cpi_program = #token_program.to_account_info();
- let accounts = ::anchor_spl::token_interface::InitializeMint2 {
- mint: #field.to_account_info(),
- };
- let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, 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 } => {
- // Define the space variable.
- let space = quote! {let space = #space;};
- let system_program_optional_check = check_scope.generate_check(system_program);
- // Define the owner of the account being created. If not specified,
- // default to the currently executing program.
- let (owner, owner_optional_check) = match owner {
- None => (
- quote! {
- __program_id
- },
- quote! {},
- ),
- Some(o) => {
- // We clone the `check_scope` here to avoid collisions with the
- // `payer_optional_check`, which is in a separate scope
- 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
- };
- // CPI to the system program to create the account.
- let create_account = generate_create_account(
- field,
- quote! {space},
- owner.clone(),
- quote! {#payer},
- seeds_with_bump,
- );
- // Put it all together.
- quote! {
- // Define the bump variable.
- #find_pda
- let #field = ({ #[inline(never)] || {
- // Checks that all the required accounts for this operation are present.
- #optional_checks
- let actual_field = #account_ref;
- let actual_owner = actual_field.owner;
- // Define the account space variable.
- #space
- // Create the account. Always do this in the event
- // if needed is not specified or the system program is the owner.
- let pa: #ty_decl = if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID {
- #payer_optional_check
- // CPI to the system program to create.
- #create_account
- // Convert from account info to account context wrapper type.
- #from_account_info_unchecked
- } else {
- // Convert from account info to account context wrapper type.
- #from_account_info
- };
- // Assert the account was created correctly.
- 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));
- }
- }
- }
- // Done.
- Ok(pa)
- }})()?;
- }
- }
- }
- }
- fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
- if c.is_init {
- // Note that for `#[account(init, seeds)]`, the seed generation and checks is checked in
- // the init constraint find_pda/validate_pda block, so we don't do anything here and
- // return nothing!
- quote! {}
- } else {
- let name = &f.ident;
- let name_str = name.to_string();
- let s = &mut c.seeds.clone();
- let deriving_program_id = c
- .program_seed
- .clone()
- // If they specified a seeds::program to use when deriving the PDA, use it.
- .map(|program_id| quote! { #program_id.key() })
- // Otherwise fall back to the current program's program_id.
- .unwrap_or(quote! { __program_id });
- // If the seeds came with a trailing comma, we need to chop it off
- // before we interpolate them below.
- if let Some(pair) = s.pop() {
- s.push_value(pair.into_value());
- }
- let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
- quote! { #s, }
- });
- let bump = if f.is_optional {
- quote!(Some(__bump))
- } else {
- quote!(__bump)
- };
- // Not init here, so do all the checks.
- let define_pda = match c.bump.as_ref() {
- // Bump target not given. Find it.
- None => quote! {
- let (__pda_address, __bump) = Pubkey::find_program_address(
- &[#maybe_seeds_plus_comma],
- &#deriving_program_id,
- );
- __bumps.#name = #bump;
- },
- // Bump target given. Use it.
- Some(b) => 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))?;
- },
- };
- quote! {
- // Define the PDA.
- #define_pda
- // Check it.
- 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
- }
- }
- }
- }
- // Generated code to create an account with with system program with the
- // given `space` amount of data, owned by `owner`.
- //
- // `seeds_with_nonce` should be given for creating PDAs. Otherwise it's an
- // empty stream.
- //
- // This should only be run within scopes where `system_program` is not Optional
- 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 {
- // Field, payer, and system program are already validated to not be an Option at this point
- quote! {
- // If the account being initialized already has lamports, then
- // return them all back to the payer so that the account has
- // zero lamports when the system program's create instruction
- // is eventually called.
- let __current_lamports = #field.lamports();
- if __current_lamports == 0 {
- // Create the token account with right amount of lamports and space, and the correct owner.
- 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.to_account_info(), 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);
- // Fund the account for rent exemption.
- 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.to_account_info(), cpi_accounts);
- anchor_lang::system_program::transfer(cpi_context, required_lamports)?;
- }
- // Allocate space.
- 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.to_account_info(), cpi_accounts);
- anchor_lang::system_program::allocate(cpi_context.with_signer(&[#seeds_with_nonce]), #space as u64)?;
- // Assign to the spl token program.
- 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.to_account_info(), 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);
- // because we are only acting on the field, we know it isnt optional at this point
- // as it was unwrapped in `generate_constraint`
- 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)),
- }
- }
|