use codegen::accounts as accounts_codegen; use codegen::program as program_codegen; use parser::accounts as accounts_parser; use parser::program as program_parser; use proc_macro2::{Span, TokenStream}; use quote::quote; use quote::ToTokens; use std::ops::Deref; use syn::ext::IdentExt; use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::{ Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt, LitStr, PatType, Token, TypePath, }; pub mod codegen; #[cfg(feature = "hash")] pub mod hash; #[cfg(not(feature = "hash"))] pub(crate) mod hash; #[cfg(feature = "idl")] pub mod idl; pub mod parser; #[derive(Debug)] pub struct Program { pub state: Option, pub ixs: Vec, pub name: Ident, pub program_mod: ItemMod, pub fallback_fn: Option, } impl Parse for Program { fn parse(input: ParseStream) -> ParseResult { let program_mod = ::parse(input)?; program_parser::parse(program_mod) } } impl From<&Program> for TokenStream { fn from(program: &Program) -> Self { program_codegen::generate(program) } } impl ToTokens for Program { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend::(self.into()); } } #[derive(Debug)] pub struct State { pub name: String, pub strct: ItemStruct, pub ctor_and_anchor: Option<(ImplItemMethod, Ident)>, pub impl_block_and_methods: Option<(ItemImpl, Vec)>, pub interfaces: Option>, pub is_zero_copy: bool, } #[derive(Debug)] pub struct StateIx { pub raw_method: ImplItemMethod, pub ident: Ident, pub args: Vec, pub anchor_ident: Ident, // True if there exists a &self on the method. pub has_receiver: bool, } #[derive(Debug)] pub struct StateInterface { pub trait_name: String, pub methods: Vec, } #[derive(Debug)] pub struct Ix { pub raw_method: ItemFn, pub ident: Ident, pub args: Vec, // The ident for the struct deriving Accounts. pub anchor_ident: Ident, } #[derive(Debug)] pub struct IxArg { pub name: Ident, pub raw_arg: PatType, } #[derive(Debug)] pub struct FallbackFn { raw_method: ItemFn, } #[derive(Debug)] pub struct AccountsStruct { // Name of the accounts struct. pub ident: Ident, // Generics + lifetimes on the accounts struct. pub generics: Generics, // Fields on the accounts struct. pub fields: Vec, // Instruction data api expression. instruction_api: Option>, } impl Parse for AccountsStruct { fn parse(input: ParseStream) -> ParseResult { let strct = ::parse(input)?; accounts_parser::parse(&strct) } } impl From<&AccountsStruct> for TokenStream { fn from(accounts: &AccountsStruct) -> Self { accounts_codegen::generate(accounts) } } impl ToTokens for AccountsStruct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend::(self.into()); } } impl AccountsStruct { pub fn new( strct: ItemStruct, fields: Vec, instruction_api: Option>, ) -> Self { let ident = strct.ident.clone(); let generics = strct.generics; Self { ident, generics, fields, instruction_api, } } } #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum AccountField { Field(Field), CompositeField(CompositeField), } #[derive(Debug)] pub struct Field { pub ident: Ident, pub constraints: ConstraintGroup, pub instruction_constraints: ConstraintGroup, pub ty: Ty, } impl Field { pub fn typed_ident(&self) -> proc_macro2::TokenStream { let name = &self.ident; let ty_decl = self.ty_decl(); quote! { #name: #ty_decl } } pub fn ty_decl(&self) -> proc_macro2::TokenStream { let account_ty = self.account_ty(); let container_ty = self.container_ty(); match &self.ty { Ty::AccountInfo => quote! { AccountInfo }, Ty::UncheckedAccount => quote! { UncheckedAccount }, Ty::Signer => quote! { Signer }, Ty::ProgramData => quote! { ProgramData }, Ty::SystemAccount => quote! { SystemAccount }, Ty::Account(AccountTy { boxed, .. }) => { if *boxed { quote! { Box<#container_ty<#account_ty>> } } else { quote! { #container_ty<#account_ty> } } } Ty::Sysvar(ty) => { let account = match ty { SysvarTy::Clock => quote! {Clock}, SysvarTy::Rent => quote! {Rent}, SysvarTy::EpochSchedule => quote! {EpochSchedule}, SysvarTy::Fees => quote! {Fees}, SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes}, SysvarTy::SlotHashes => quote! {SlotHashes}, SysvarTy::SlotHistory => quote! {SlotHistory}, SysvarTy::StakeHistory => quote! {StakeHistory}, SysvarTy::Instructions => quote! {Instructions}, SysvarTy::Rewards => quote! {Rewards}, }; quote! { Sysvar<#account> } } _ => quote! { #container_ty<#account_ty> }, } } // TODO: remove the option once `CpiAccount` is completely removed (not // just deprecated). pub fn from_account_info_unchecked(&self, kind: Option<&InitKind>) -> proc_macro2::TokenStream { let field = &self.ident; let container_ty = self.container_ty(); match &self.ty { Ty::AccountInfo => quote! { #field.to_account_info() }, Ty::UncheckedAccount => { quote! { UncheckedAccount::try_from(#field.to_account_info()) } } Ty::Account(AccountTy { boxed, .. }) => { if *boxed { quote! { Box::new(#container_ty::try_from_unchecked( &#field, )?) } } else { quote! { #container_ty::try_from_unchecked( &#field, )? } } } Ty::CpiAccount(_) => { quote! { #container_ty::try_from_unchecked( &#field, )? } } _ => { let owner_addr = match &kind { None => quote! { program_id }, Some(InitKind::Program { .. }) => quote! { program_id }, _ => quote! { &anchor_spl::token::ID }, }; quote! { #container_ty::try_from_unchecked( #owner_addr, &#field, )? } } } } pub fn container_ty(&self) -> proc_macro2::TokenStream { match &self.ty { Ty::ProgramAccount(_) => quote! { anchor_lang::ProgramAccount }, Ty::Account(_) => quote! { anchor_lang::Account }, Ty::AccountLoader(_) => quote! { anchor_lang::AccountLoader }, Ty::Loader(_) => quote! { anchor_lang::Loader }, Ty::CpiAccount(_) => quote! { anchor_lang::CpiAccount }, Ty::Sysvar(_) => quote! { anchor_lang::Sysvar }, Ty::CpiState(_) => quote! { anchor_lang::CpiState }, Ty::ProgramState(_) => quote! { anchor_lang::ProgramState }, Ty::Program(_) => quote! { anchor_lang::Program }, Ty::AccountInfo => quote! {}, Ty::UncheckedAccount => quote! {}, Ty::Signer => quote! {}, Ty::SystemAccount => quote! {}, Ty::ProgramData => quote! {}, } } // Returns the inner account struct type. pub fn account_ty(&self) -> proc_macro2::TokenStream { match &self.ty { Ty::AccountInfo => quote! { AccountInfo }, Ty::UncheckedAccount => quote! { UncheckedAccount }, Ty::Signer => quote! { Signer }, Ty::SystemAccount => quote! { SystemAccount }, Ty::ProgramData => quote! { ProgramData }, Ty::ProgramAccount(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::Account(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::AccountLoader(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::Loader(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::CpiAccount(ty) => { let ident = &ty.account_type_path; quote! { #ident } } Ty::ProgramState(ty) => { let account = &ty.account_type_path; quote! { #account } } Ty::CpiState(ty) => { let account = &ty.account_type_path; quote! { #account } } Ty::Sysvar(ty) => match ty { SysvarTy::Clock => quote! {Clock}, SysvarTy::Rent => quote! {Rent}, SysvarTy::EpochSchedule => quote! {EpochSchedule}, SysvarTy::Fees => quote! {Fees}, SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes}, SysvarTy::SlotHashes => quote! {SlotHashes}, SysvarTy::SlotHistory => quote! {SlotHistory}, SysvarTy::StakeHistory => quote! {StakeHistory}, SysvarTy::Instructions => quote! {Instructions}, SysvarTy::Rewards => quote! {Rewards}, }, Ty::Program(ty) => { let program = &ty.account_type_path; quote! { #program } } } } } #[derive(Debug)] pub struct CompositeField { pub ident: Ident, pub constraints: ConstraintGroup, pub instruction_constraints: ConstraintGroup, pub symbol: String, pub raw_field: syn::Field, } // A type of an account field. #[derive(Debug, PartialEq)] pub enum Ty { AccountInfo, UncheckedAccount, ProgramState(ProgramStateTy), CpiState(CpiStateTy), ProgramAccount(ProgramAccountTy), Loader(LoaderTy), AccountLoader(LoaderAccountTy), CpiAccount(CpiAccountTy), Sysvar(SysvarTy), Account(AccountTy), Program(ProgramTy), Signer, SystemAccount, ProgramData, } #[derive(Debug, PartialEq)] pub enum SysvarTy { Clock, Rent, EpochSchedule, Fees, RecentBlockhashes, SlotHashes, SlotHistory, StakeHistory, Instructions, Rewards, } #[derive(Debug, PartialEq)] pub struct ProgramStateTy { pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct CpiStateTy { pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct ProgramAccountTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct CpiAccountTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct LoaderAccountTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct LoaderTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug, PartialEq)] pub struct AccountTy { // The struct type of the account. pub account_type_path: TypePath, // True if the account has been boxed via `Box`. pub boxed: bool, } #[derive(Debug, PartialEq)] pub struct ProgramTy { // The struct type of the account. pub account_type_path: TypePath, } #[derive(Debug)] pub struct Error { pub name: String, pub raw_enum: ItemEnum, pub ident: Ident, pub codes: Vec, pub args: Option, } #[derive(Debug)] pub struct ErrorArgs { pub offset: LitInt, } impl Parse for ErrorArgs { fn parse(stream: ParseStream) -> ParseResult { let offset_span = stream.span(); let offset = stream.call(Ident::parse_any)?; if offset.to_string().as_str() != "offset" { return Err(ParseError::new(offset_span, "expected keyword offset")); } stream.parse::()?; Ok(ErrorArgs { offset: stream.parse()?, }) } } #[derive(Debug)] pub struct ErrorCode { pub id: u32, pub ident: Ident, pub msg: Option, } // All well formed constraints on a single `Accounts` field. #[derive(Debug, Default, Clone)] pub struct ConstraintGroup { init: Option, zeroed: Option, mutable: Option, signer: Option, owner: Option, rent_exempt: Option, seeds: Option, executable: Option, state: Option, has_one: Vec, literal: Vec, raw: Vec, close: Option, address: Option, associated_token: Option, } impl ConstraintGroup { pub fn is_zeroed(&self) -> bool { self.zeroed.is_some() } pub fn is_mutable(&self) -> bool { self.mutable.is_some() } pub fn is_signer(&self) -> bool { self.signer.is_some() } pub fn is_close(&self) -> bool { self.close.is_some() } } // A single account constraint *after* merging all tokens into a well formed // constraint. Some constraints like "seeds" are defined by multiple // tokens, so a merging phase is required. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum Constraint { Init(ConstraintInitGroup), Zeroed(ConstraintZeroed), Mut(ConstraintMut), Signer(ConstraintSigner), HasOne(ConstraintHasOne), Literal(ConstraintLiteral), Raw(ConstraintRaw), Owner(ConstraintOwner), RentExempt(ConstraintRentExempt), Seeds(ConstraintSeedsGroup), AssociatedToken(ConstraintAssociatedToken), Executable(ConstraintExecutable), State(ConstraintState), Close(ConstraintClose), Address(ConstraintAddress), } // Constraint token is a single keyword in a `#[account()]` attribute. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum ConstraintToken { Init(Context), Zeroed(Context), Mut(Context), Signer(Context), HasOne(Context), Literal(Context), Raw(Context), Owner(Context), RentExempt(Context), Seeds(Context), Executable(Context), State(Context), Close(Context), Payer(Context), Space(Context), Address(Context), TokenMint(Context), TokenAuthority(Context), AssociatedTokenMint(Context), AssociatedTokenAuthority(Context), MintAuthority(Context), MintFreezeAuthority(Context), MintDecimals(Context), Bump(Context), } impl Parse for ConstraintToken { fn parse(stream: ParseStream) -> ParseResult { accounts_parser::constraints::parse_token(stream) } } #[derive(Debug, Clone)] pub struct ConstraintInit { pub if_needed: bool, } #[derive(Debug, Clone)] pub struct ConstraintInitIfNeeded {} #[derive(Debug, Clone)] pub struct ConstraintZeroed {} #[derive(Debug, Clone)] pub struct ConstraintMut { pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintSigner { pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintHasOne { pub join_target: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintLiteral { pub lit: LitStr, } #[derive(Debug, Clone)] pub struct ConstraintRaw { pub raw: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintOwner { pub owner_address: Expr, pub error: Option, } #[derive(Debug, Clone)] pub struct ConstraintAddress { pub address: Expr, pub error: Option, } #[derive(Debug, Clone)] pub enum ConstraintRentExempt { Enforce, Skip, } #[derive(Debug, Clone)] pub struct ConstraintInitGroup { pub if_needed: bool, pub seeds: Option, pub payer: Option, pub space: Option, pub kind: InitKind, } #[derive(Debug, Clone)] pub struct ConstraintSeedsGroup { pub is_init: bool, pub seeds: Punctuated, pub bump: Option, // None => bump was given without a target. } #[derive(Debug, Clone)] pub struct ConstraintSeeds { pub seeds: Punctuated, } #[derive(Debug, Clone)] pub struct ConstraintExecutable {} #[derive(Debug, Clone)] pub struct ConstraintState { pub program_target: Ident, } #[derive(Debug, Clone)] pub struct ConstraintPayer { pub target: Expr, } #[derive(Debug, Clone)] pub struct ConstraintSpace { pub space: Expr, } #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] pub enum InitKind { Program { owner: Option, }, // Owner for token and mint represents the authority. Not to be confused // with the owner of the AccountInfo. Token { owner: Expr, mint: Expr, }, AssociatedToken { owner: Expr, mint: Expr, }, Mint { owner: Expr, freeze_authority: Option, decimals: Expr, }, } #[derive(Debug, Clone)] pub struct ConstraintClose { pub sol_dest: Ident, } #[derive(Debug, Clone)] pub struct ConstraintTokenMint { mint: Expr, } #[derive(Debug, Clone)] pub struct ConstraintTokenAuthority { auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintAuthority { mint_auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintFreezeAuthority { mint_freeze_auth: Expr, } #[derive(Debug, Clone)] pub struct ConstraintMintDecimals { decimals: Expr, } #[derive(Debug, Clone)] pub struct ConstraintTokenBump { bump: Option, } #[derive(Debug, Clone)] pub struct ConstraintAssociatedToken { pub wallet: Expr, pub mint: Expr, } // Syntaxt context object for preserving metadata about the inner item. #[derive(Debug, Clone)] pub struct Context { span: Span, inner: T, } impl Context { pub fn new(span: Span, inner: T) -> Self { Self { span, inner } } pub fn into_inner(self) -> T { self.inner } } impl Deref for Context { type Target = T; fn deref(&self) -> &Self::Target { &self.inner } } impl Spanned for Context { fn span(&self) -> Span { self.span } }