lang: Syntax analyzer
This commit is contained in:
parent
512604b85e
commit
de77e29858
|
@ -68,6 +68,7 @@ pub struct Initialize<'info> {
|
||||||
pub struct TestOwner<'info> {
|
pub struct TestOwner<'info> {
|
||||||
#[account(owner = misc)]
|
#[account(owner = misc)]
|
||||||
data: AccountInfo<'info>,
|
data: AccountInfo<'info>,
|
||||||
|
#[account(address = "asdf")]
|
||||||
misc: AccountInfo<'info>,
|
misc: AccountInfo<'info>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use anchor_syn::analyzer::accounts as accounts_analyzer;
|
||||||
use anchor_syn::codegen::accounts as accounts_codegen;
|
use anchor_syn::codegen::accounts as accounts_codegen;
|
||||||
use anchor_syn::parser::accounts as accounts_parser;
|
use anchor_syn::parser::accounts as accounts_parser;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
@ -50,11 +51,21 @@ use syn::parse_macro_input;
|
||||||
/// | `#[account(state = <target>)]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. |
|
/// | `#[account(state = <target>)]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. |
|
||||||
/// | `#[account(owner = <target>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. |
|
/// | `#[account(owner = <target>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. |
|
||||||
/// | `#[account(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. |
|
/// | `#[account(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. |
|
||||||
|
/// | `#[account(address = "<literal>")]` | On any type deriving `ToAccountInfo` | Asserts the address of account is the given Rust literal. |
|
||||||
|
/// | `#[account(unsafe)]` | On any type deriving `Accounts` | Opts out of syntax analysis checks. |
|
||||||
// TODO: How do we make the markdown render correctly without putting everything
|
// TODO: How do we make the markdown render correctly without putting everything
|
||||||
// on absurdly long lines?
|
// on absurdly long lines?
|
||||||
#[proc_macro_derive(Accounts, attributes(account))]
|
#[proc_macro_derive(Accounts, attributes(account))]
|
||||||
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||||
let strct = parse_macro_input!(item as syn::ItemStruct);
|
let strct = parse_macro_input!(item as syn::ItemStruct);
|
||||||
let tts = accounts_codegen::generate(accounts_parser::parse(&strct));
|
// Parse the syntax.
|
||||||
|
let unsafe_accounts = accounts_parser::parse(&strct);
|
||||||
|
// Perform syntax analysis.
|
||||||
|
let accounts = match accounts_analyzer::analyze(unsafe_accounts) {
|
||||||
|
Err(err) => panic!("{}", err), // TODO: use compile_error trick.
|
||||||
|
Ok(a) => a,
|
||||||
|
};
|
||||||
|
// Generate the new token stream.
|
||||||
|
let tts = accounts_codegen::generate(accounts);
|
||||||
proc_macro::TokenStream::from(tts)
|
proc_macro::TokenStream::from(tts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//! Perform syntax analysis on an accounts struct, providing additional compile
|
||||||
|
//! time guarantees for Anchor programs.
|
||||||
|
|
||||||
|
use crate::{AccountField, AccountsStruct};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub fn analyze(accs: AccountsStruct) -> Result<AccountsStruct, AnalyzeError> {
|
||||||
|
CpiOwner::analyze(&accs)?;
|
||||||
|
OwnerRoot::analyze(&accs)?;
|
||||||
|
Ok(accs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum AnalyzeError {
|
||||||
|
#[error("Owner not specified on field: {0}")]
|
||||||
|
OwnerNotSpecified(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SyntaxAnalyzer {
|
||||||
|
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asserts all cpi accounts have an owner specified.
|
||||||
|
struct CpiOwner;
|
||||||
|
impl SyntaxAnalyzer for CpiOwner {
|
||||||
|
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError> {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asserts all owners have explicit program ids, or are marked unsafe.
|
||||||
|
struct OwnerRoot;
|
||||||
|
impl SyntaxAnalyzer for OwnerRoot {
|
||||||
|
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError> {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod accounts;
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
|
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAddress,
|
||||||
ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
|
ConstraintAssociated, ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral,
|
||||||
ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, Field, Ty,
|
ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState,
|
||||||
|
ConstraintUnsafe, Field, Ty,
|
||||||
};
|
};
|
||||||
use heck::SnakeCase;
|
use heck::SnakeCase;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -380,6 +381,8 @@ pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::Toke
|
||||||
Constraint::Executable(c) => generate_constraint_executable(f, c),
|
Constraint::Executable(c) => generate_constraint_executable(f, c),
|
||||||
Constraint::State(c) => generate_constraint_state(f, c),
|
Constraint::State(c) => generate_constraint_state(f, c),
|
||||||
Constraint::Associated(c) => generate_constraint_associated(f, c),
|
Constraint::Associated(c) => generate_constraint_associated(f, c),
|
||||||
|
Constraint::Address(c) => generate_constraint_address(f, c),
|
||||||
|
Constraint::Unsafe(c) => generate_constraint_unsafe(f, c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,3 +644,17 @@ fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
tts
|
tts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
|
||||||
|
let tokens = &c.tokens;
|
||||||
|
let ident = &f.ident;
|
||||||
|
quote! {
|
||||||
|
if #ident.to_account_info().key != #tokens {
|
||||||
|
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_constraint_unsafe(f: &Field, _c: &ConstraintUnsafe) -> proc_macro2::TokenStream {
|
||||||
|
quote! { /* No-op. */}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use heck::MixedCase;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub mod analyzer;
|
||||||
pub mod codegen;
|
pub mod codegen;
|
||||||
#[cfg(feature = "hash")]
|
#[cfg(feature = "hash")]
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
|
@ -294,6 +295,8 @@ pub enum Constraint {
|
||||||
Executable(ConstraintExecutable),
|
Executable(ConstraintExecutable),
|
||||||
State(ConstraintState),
|
State(ConstraintState),
|
||||||
Associated(ConstraintAssociated),
|
Associated(ConstraintAssociated),
|
||||||
|
Address(ConstraintAddress),
|
||||||
|
Unsafe(ConstraintUnsafe),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -338,6 +341,14 @@ pub struct ConstraintAssociated {
|
||||||
pub associated_target: proc_macro2::Ident,
|
pub associated_target: proc_macro2::Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConstraintAddress {
|
||||||
|
pub tokens: proc_macro2::TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConstraintUnsafe {}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
|
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAddress,
|
||||||
ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
|
ConstraintAssociated, ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral,
|
||||||
ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, CpiAccountTy,
|
ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState,
|
||||||
CpiStateTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
|
ConstraintUnsafe, CpiAccountTy, CpiStateTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy,
|
||||||
|
Ty,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||||
|
@ -224,6 +225,9 @@ fn parse_constraints(
|
||||||
is_signer = true;
|
is_signer = true;
|
||||||
constraints.push(Constraint::Signer(ConstraintSigner {}));
|
constraints.push(Constraint::Signer(ConstraintSigner {}));
|
||||||
}
|
}
|
||||||
|
"unsafe" => {
|
||||||
|
constraints.push(Constraint::Unsafe(ConstraintUnsafe {}));
|
||||||
|
}
|
||||||
"seeds" => {
|
"seeds" => {
|
||||||
match inner_tts.next().unwrap() {
|
match inner_tts.next().unwrap() {
|
||||||
proc_macro2::TokenTree::Punct(punct) => {
|
proc_macro2::TokenTree::Punct(punct) => {
|
||||||
|
@ -369,6 +373,22 @@ fn parse_constraints(
|
||||||
_ => panic!("invalid space"),
|
_ => panic!("invalid space"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"address" => {
|
||||||
|
match inner_tts.next().unwrap() {
|
||||||
|
proc_macro2::TokenTree::Punct(punct) => {
|
||||||
|
assert!(punct.as_char() == '=');
|
||||||
|
punct
|
||||||
|
}
|
||||||
|
_ => panic!("invalid syntax"),
|
||||||
|
};
|
||||||
|
let tokens = match inner_tts.next().unwrap() {
|
||||||
|
proc_macro2::TokenTree::Literal(literal) => {
|
||||||
|
literal.to_string().replace("\"", "").parse().unwrap()
|
||||||
|
}
|
||||||
|
_ => panic!("invalid address syntax"),
|
||||||
|
};
|
||||||
|
constraints.push(Constraint::Address(ConstraintAddress { tokens }));
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("invalid syntax");
|
panic!("invalid syntax");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue