瀏覽代碼

lang: Simplify codegen (#171)

Armani Ferrante 4 年之前
父節點
當前提交
60b1c87224
共有 4 個文件被更改,包括 224 次插入107 次删除
  1. 2 0
      lang/attribute/interface/src/lib.rs
  2. 9 0
      lang/syn/src/codegen/accounts.rs
  3. 6 1
      lang/syn/src/codegen/error.rs
  4. 207 106
      lang/syn/src/codegen/program.rs

+ 2 - 0
lang/attribute/interface/src/lib.rs

@@ -241,6 +241,8 @@ pub fn interface(
     proc_macro::TokenStream::from(quote! {
         #item_trait
 
+        /// Anchor generated module for invoking programs implementing an
+        /// `#[interface]` via CPI.
         mod #mod_name {
             use super::*;
             #(#methods)*

+ 9 - 0
lang/syn/src/codegen/accounts.rs

@@ -230,6 +230,15 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     };
 
     quote! {
+        /// An internal, Anchor generated module. This is used (as an
+        /// implementation detail), to generate a struct for a given
+        /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
+        /// instead of an `AccountInfo`. This is useful for clients that want
+        /// to generate a list of accounts, without explicitly knowing the
+        /// order all the fields should be in.
+        ///
+        /// To access the struct in this module, one should use the sibling
+        /// `accounts` module (also generated), which re-exports this.
         mod #account_mod_name {
             use super::*;
             use anchor_lang::prelude::borsh;

+ 6 - 1
lang/syn/src/codegen/error.rs

@@ -33,8 +33,13 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
         .collect();
 
     quote! {
-        type Result<T> = std::result::Result<T, Error>;
+        /// Anchor generated Result to be used as the return type for the
+        /// program.
+        pub type Result<T> = std::result::Result<T, Error>;
 
+        /// Anchor generated error allowing one to easily return a
+        /// `ProgramError` or a custom, user defined error code by utilizing
+        /// its `From` implementation.
         #[derive(thiserror::Error, Debug)]
         pub enum Error {
             #[error(transparent)]

+ 207 - 106
lang/syn/src/codegen/program.rs

@@ -25,12 +25,54 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
 
         #[cfg(not(feature = "no-entrypoint"))]
         anchor_lang::solana_program::entrypoint!(entry);
+        /// The Anchor codegen exposes a programming model where a user defines
+        /// a set of methods inside of a `#[program]` module in a way similar
+        /// to writing RPC request handlers. The macro then generates a bunch of
+        /// code wrapping these user defined methods into something that can be
+        /// executed on Solana.
+        ///
+        /// These methods fall into one of three categories, each of which
+        /// can be considered a different "namespace" of the program.
+        ///
+        /// 1) Global methods - regular methods inside of the `#[program]`.
+        /// 2) State methods - associated methods inside a `#[state]` struct.
+        /// 3) Interface methods - methods inside a strait struct's
+        ///    implementation of an `#[interface]` trait.
+        ///
+        /// Care must be taken by the codegen to prevent collisions between
+        /// methods in these different namespaces. For this reason, Anchor uses
+        /// a variant of sighash to perform method dispatch, rather than
+        /// something like a simple enum variant discriminator.
+        ///
+        /// The execution flow of the generated code can be roughly outlined:
+        ///
+        /// * Start program via the entrypoint.
+        /// * Strip method identifier off the first 8 bytes of the instruction
+        ///   data and invoke the identified method. The method identifier
+        ///   is a variant of sighash. See docs.rs for `anchor_lang` for details.
+        /// * If the method identifier is an IDL identifier, execute the IDL
+        ///   instructions, which are a special set of hardcoded instructions
+        ///   baked into every Anchor program. Then exit.
+        /// * Otherwise, the method identifier is for a user defined
+        ///   instruction, i.e., one of the methods in the user defined
+        ///   `#[program]` module. Perform method dispatch, i.e., execute the
+        ///   big match statement mapping method identifier to method handler
+        ///   wrapper.
+        /// * Run the method handler wrapper. This wraps the code the user
+        ///   actually wrote, deserializing the accounts, constructing the
+        ///   context, invoking the user's code, and finally running the exit
+        ///   routine, which typically persists account changes.
+        ///
+        /// The `entry` function here, defines the standard entry to a Solana
+        /// program, where execution begins.
         #[cfg(not(feature = "no-entrypoint"))]
         fn entry(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
             if ix_data.len() < 8 {
                 return Err(ProgramError::Custom(99));
             }
 
+            // Split the instruction data into the first 8 byte method
+            // identifier (sighash) and the serialized instruction data.
             let mut ix_data: &[u8] = ix_data;
             let sighash: [u8; 8] = {
                 let mut sighash: [u8; 8] = [0; 8];
@@ -39,16 +81,14 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
                 sighash
             };
 
-            if cfg!(not(feature = "no-idl")) {
-                if sighash == anchor_lang::idl::IDL_IX_TAG.to_le_bytes() {
-                    return __private::__idl(program_id, accounts, &ix_data);
-                }
-            }
-
-            #dispatch
+            dispatch(program_id, accounts, sighash, ix_data)
         }
 
-        // Create a private module to not clutter the program's namespace.
+        #dispatch
+
+        /// Create a private module to not clutter the program's namespace.
+        /// Defines an entrypoint for each individual instruction handler
+        /// wrapper.
         mod __private {
             use super::*;
 
@@ -81,10 +121,10 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                     format!("{:?}", sighash_arr).parse().unwrap();
                 quote! {
                     #sighash_tts => {
-                        let ix = instruction::#ix_name::deserialize(&mut ix_data)
+                        let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
                             .map_err(|_| ProgramError::Custom(1))?; // todo: error code
-                        let instruction::#variant_arm = ix;
-                        __private::__ctor(program_id, accounts, #(#ctor_args),*)
+                        let instruction::state::#variant_arm = ix;
+                        __private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
                     }
                 }
             }
@@ -106,22 +146,18 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                         let name = &ix.raw_method.sig.ident.to_string();
                         let ix_method_name: proc_macro2::TokenStream =
                             { format!("__{}", name).parse().unwrap() };
-                        let variant_arm = generate_ix_variant(
-                            ix.raw_method.sig.ident.to_string(),
-                            &ix.args,
-                            true,
-                        );
-                        let ix_name =
-                            generate_ix_variant_name(ix.raw_method.sig.ident.to_string(), true);
+                        let variant_arm =
+                            generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
+                        let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
                         let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
                         let sighash_tts: proc_macro2::TokenStream =
                             format!("{:?}", sighash_arr).parse().unwrap();
                         quote! {
                             #sighash_tts => {
-                                let ix = instruction::#ix_name::deserialize(&mut ix_data)
+                                let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
                                     .map_err(|_| ProgramError::Custom(1))?; // todo: error code
-                                let instruction::#variant_arm = ix;
-                                __private::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                                let instruction::state::#variant_arm = ix;
+                                __private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
                             }
                         }
                     })
@@ -179,7 +215,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                                         let Args {
                                             #(#ix_arg_names),*
                                         } = ix;
-                                        __private::#ix_name(program_id, accounts, #(#ix_arg_names),*)
+                                        __private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
                                     }
                                 }
                             })
@@ -191,38 +227,65 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
     };
 
     // Dispatch all global instructions.
-    let dispatch_arms: Vec<proc_macro2::TokenStream> = program
+    let global_dispatch_arms: Vec<proc_macro2::TokenStream> = program
         .ixs
         .iter()
         .map(|ix| {
             let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
             let ix_method_name = &ix.raw_method.sig.ident;
-            let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string(), false);
+            let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
             let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &ix_method_name.to_string());
             let sighash_tts: proc_macro2::TokenStream =
                 format!("{:?}", sighash_arr).parse().unwrap();
-            let variant_arm =
-                generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args, false);
+            let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
             quote! {
                 #sighash_tts => {
                     let ix = instruction::#ix_name::deserialize(&mut ix_data)
                         .map_err(|_| ProgramError::Custom(1))?; // todo: error code
                     let instruction::#variant_arm = ix;
-                    __private::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                    __private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
                 }
             }
         })
         .collect();
 
     quote! {
-        match sighash {
-            #ctor_state_dispatch_arm
-            #(#state_dispatch_arms)*
-            #(#trait_dispatch_arms)*
-            #(#dispatch_arms)*
-            _ => {
-                msg!("Fallback functions are not supported. If you have a use case, please file an issue.");
-                Err(ProgramError::Custom(99))
+        /// Performs method dispatch.
+        ///
+        /// Each method in an anchor program is uniquely defined by a namespace
+        /// and a rust identifier (i.e., the name given to the method). These
+        /// two pieces can be combined to creater a method identifier,
+        /// specifically, Anchor uses
+        ///
+        /// Sha256("<namespace>::<rust-identifier>")[..8],
+        ///
+        /// where the namespace can be one of three types. 1) "global" for a
+        /// regular instruction, 2) "state" for a state struct instruction
+        /// handler and 3) a trait namespace (used in combination with the
+        /// `#[interface]` attribute), which is defined by the trait name, e..
+        /// `MyTrait`.
+        ///
+        /// With this 8 byte identifier, Anchor performs method dispatch,
+        /// matching the given 8 byte identifier to the associated method
+        /// handler, which leads to user defined code being eventually invoked.
+        fn dispatch(program_id: &Pubkey, accounts: &[AccountInfo], sighash: [u8; 8], mut ix_data: &[u8]) -> ProgramResult {
+            // If the method identifier is the IDL tag, then execute an IDL
+            // instruction, injected into all Anchor programs.
+            if cfg!(not(feature = "no-idl")) {
+                if sighash == anchor_lang::idl::IDL_IX_TAG.to_le_bytes() {
+                    return __private::__idl::__idl_dispatch(program_id, accounts, &ix_data);
+                }
+            }
+
+            match sighash {
+                #ctor_state_dispatch_arm
+                #(#state_dispatch_arms)*
+                #(#trait_dispatch_arms)*
+                #(#global_dispatch_arms)*
+                _ => {
+                    msg!("Fallback functions are not supported. If you have a use case, please file an issue.");
+                    Err(ProgramError::Custom(99))
+                }
             }
         }
     }
@@ -241,7 +304,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
             // on chain.
             #[inline(never)]
             #[cfg(not(feature = "no-idl"))]
-            pub fn __idl(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
                 let mut accounts = accounts;
                 let mut data: &[u8] = idl_ix_data;
 
@@ -280,7 +343,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
 
             #[inline(never)]
             #[cfg(feature = "no-idl")]
-            pub fn __idl(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
                 Err(anchor_lang::solana_program::program_error::ProgramError::Custom(99))
             }
 
@@ -658,11 +721,35 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
         .collect();
 
     quote! {
-        #non_inlined_idl
-        #non_inlined_ctor
-        #(#non_inlined_state_handlers)*
-        #(#non_inlined_state_trait_handlers)*
-        #(#non_inlined_handlers)*
+        /// __idl mod defines handlers for injected Anchor IDL instructions.
+        pub mod __idl {
+            use super::*;
+
+            #non_inlined_idl
+        }
+
+        /// __state mod defines wrapped handlers for state instructions.
+        pub mod __state {
+            use super::*;
+
+            #non_inlined_ctor
+            #(#non_inlined_state_handlers)*
+        }
+
+        /// __interface mod defines wrapped handlers for `#[interface]` trait
+        /// implementations.
+        pub mod __interface {
+            use super::*;
+
+            #(#non_inlined_state_trait_handlers)*
+        }
+
+        /// __global mod defines wrapped handlers for global instructions.
+        pub mod __global {
+            use super::*;
+
+            #(#non_inlined_handlers)*
+        }
     }
 }
 
@@ -683,36 +770,7 @@ pub fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
 }
 
 pub fn generate_ctor_variant_name() -> String {
-    "__Ctor".to_string()
-}
-
-pub fn generate_ctor_typed_variant_with_semi(program: &Program) -> proc_macro2::TokenStream {
-    match &program.state {
-        None => quote! {},
-        Some(state) => {
-            let ctor_args: Vec<proc_macro2::TokenStream> = generate_ctor_typed_args(state)
-                .iter()
-                .map(|arg| {
-                    format!("pub {}", parser::tts_to_string(&arg))
-                        .parse()
-                        .unwrap()
-                })
-                .collect();
-            if ctor_args.is_empty() {
-                quote! {
-                    #[derive(AnchorSerialize, AnchorDeserialize)]
-                    pub struct __Ctor;
-                }
-            } else {
-                quote! {
-                    #[derive(AnchorSerialize, AnchorDeserialize)]
-                    pub struct __Ctor {
-                        #(#ctor_args),*
-                    }
-                }
-            }
-        }
-    }
+    "Ctor".to_string()
 }
 
 fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
@@ -763,19 +821,11 @@ fn generate_ctor_args(state: &State) -> Vec<syn::Pat> {
         .unwrap_or_default()
 }
 
-pub fn generate_ix_variant(
-    name: String,
-    args: &[IxArg],
-    underscore: bool,
-) -> proc_macro2::TokenStream {
+pub fn generate_ix_variant(name: String, args: &[IxArg]) -> proc_macro2::TokenStream {
     let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
     let ix_name_camel: proc_macro2::TokenStream = {
         let n = name.to_camel_case();
-        if underscore {
-            format!("__{}", n).parse().unwrap()
-        } else {
-            n.parse().unwrap()
-        }
+        n.parse().unwrap()
     };
 
     if args.is_empty() {
@@ -791,13 +841,9 @@ pub fn generate_ix_variant(
     }
 }
 
-pub fn generate_ix_variant_name(name: String, underscore: bool) -> proc_macro2::TokenStream {
+pub fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
     let n = name.to_camel_case();
-    if underscore {
-        format!("__{}", n).parse().unwrap()
-    } else {
-        n.parse().unwrap()
-    }
+    n.parse().unwrap()
 }
 
 pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
@@ -808,7 +854,50 @@ pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
 }
 
 pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
-    let ctor_variant = generate_ctor_typed_variant_with_semi(program);
+    let ctor_variant = match &program.state {
+        None => quote! {},
+        Some(state) => {
+            let ctor_args: Vec<proc_macro2::TokenStream> = generate_ctor_typed_args(state)
+                .iter()
+                .map(|arg| {
+                    format!("pub {}", parser::tts_to_string(&arg))
+                        .parse()
+                        .unwrap()
+                })
+                .collect();
+            let strct = {
+                if ctor_args.is_empty() {
+                    quote! {
+                        #[derive(AnchorSerialize, AnchorDeserialize)]
+                        pub struct Ctor;
+                    }
+                } else {
+                    quote! {
+                        #[derive(AnchorSerialize, AnchorDeserialize)]
+                        pub struct Ctor {
+                            #(#ctor_args),*
+                        }
+                    }
+                }
+            };
+            let sighash_arr = sighash_ctor();
+            let sighash_tts: proc_macro2::TokenStream =
+                format!("{:?}", sighash_arr).parse().unwrap();
+            quote! {
+                /// Instruction arguments to the `#[state]`'s `new`
+                /// constructor.
+                #strct
+
+                impl anchor_lang::InstructionData for Ctor {
+                    fn data(&self) -> Vec<u8> {
+                        let mut d = #sighash_tts.to_vec();
+                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
+                        d
+                    }
+                }
+            }
+        }
+    };
     let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
         None => vec![],
         Some(state) => state
@@ -818,13 +907,14 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
                 methods
                     .iter()
                     .map(|method| {
-                        let ix_name_camel: proc_macro2::TokenStream = {
-                            let name = format!(
-                                "__{}",
-                                &method.raw_method.sig.ident.to_string().to_camel_case(),
-                            );
-                            name.parse().unwrap()
-                        };
+                        let ix_name_camel: proc_macro2::TokenStream = method
+                            .raw_method
+                            .sig
+                            .ident
+                            .to_string()
+                            .to_camel_case()
+                            .parse()
+                            .unwrap();
                         let raw_args: Vec<proc_macro2::TokenStream> = method
                             .args
                             .iter()
@@ -837,7 +927,7 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
 
                         let ix_data_trait = {
                             let name = method.raw_method.sig.ident.to_string();
-                            let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
+                            let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
                             let sighash_tts: proc_macro2::TokenStream =
                                 format!("{:?}", sighash_arr).parse().unwrap();
                             quote! {
@@ -854,6 +944,7 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
                         // If no args, output a "unit" variant instead of a struct variant.
                         if method.args.is_empty() {
                             quote! {
+                                /// Anchor generated instruction.
                                 #[derive(AnchorSerialize, AnchorDeserialize)]
                                 pub struct #ix_name_camel;
 
@@ -861,6 +952,7 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
                             }
                         } else {
                             quote! {
+                                /// Anchor generated instruction.
                                 #[derive(AnchorSerialize, AnchorDeserialize)]
                                 pub struct #ix_name_camel {
                                     #(#raw_args),*
@@ -907,6 +999,7 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
             // If no args, output a "unit" variant instead of a struct variant.
             if ix.args.is_empty() {
                 quote! {
+                    /// Instruction.
                     #[derive(AnchorSerialize, AnchorDeserialize)]
                     pub struct #ix_name_camel;
 
@@ -914,6 +1007,7 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
                 }
             } else {
                 quote! {
+                    /// Instruction.
                     #[derive(AnchorSerialize, AnchorDeserialize)]
                     pub struct #ix_name_camel {
                         #(#raw_args),*
@@ -926,15 +1020,23 @@ pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
         .collect();
 
     quote! {
-        /// `instruction` is a macro generated module containing the program's
-        /// instruction enum, where each variant is created from each method
-        /// handler in the `#[program]` mod. These should be used directly, when
-        /// specifying instructions on a client.
+        /// An Anchor generated module containing the program's set of
+        /// instructions, where each method handler in the `#[program]` mod is
+        /// associated with a struct defining the input arguments to the
+        /// method. These should be used directly, when one wants to serialize
+        /// Anchor instruction data, for example, when speciying
+        /// instructions on a client.
         pub mod instruction {
             use super::*;
 
-            #ctor_variant
-            #(#state_method_variants)*
+            /// Instruction struct definitions for `#[state]` methods.
+            pub mod state {
+                use super::*;
+
+                #ctor_variant
+                #(#state_method_variants)*
+            }
+
             #(#variants)*
         }
     }
@@ -984,7 +1086,7 @@ fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
     //       each struct here. This is convenient for Rust clients.
 
     quote! {
-        /// `accounts` is a macro generated module, providing a set of structs
+        /// An Anchor generated module, providing a set of structs
         /// mirroring the structs deriving `Accounts`, where each field is
         /// a `Pubkey`. This is useful for specifying accounts for a client.
         pub mod accounts {
@@ -1000,8 +1102,7 @@ fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
         .map(|ix| {
             let accounts_ident = &ix.anchor_ident;
             let cpi_method = {
-                let ix_variant =
-                    generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args, false);
+                let ix_variant = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
                 let method_name = &ix.ident;
                 let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
                 let name = &ix.raw_method.sig.ident.to_string();