瀏覽代碼

lang/syn: Allow state structs with no ctor or impl block

Armani Ferrante 4 年之前
父節點
當前提交
a780002683

+ 1 - 0
CHANGELOG.md

@@ -17,6 +17,7 @@ incremented for features.
 * cli: Stream program logs to `.anchor/program-logs` directory when testing.
 * spl: Add shared memory api.
 * lang/attribute/access-control: Allow specifying multiple modifier functions.
+* lang/syn: Allow state structs that don't have a ctor or impl block (just trait implementations).
 
 ## [0.2.0] - 2021-02-08
 

+ 1 - 9
examples/interface/programs/counter-auth/src/lib.rs

@@ -13,15 +13,7 @@ pub mod counter_auth {
     use super::*;
 
     #[state]
-    pub struct CounterAuth {}
-
-    // TODO: remove this impl block after addressing
-    //       https://github.com/project-serum/anchor/issues/71.
-    impl CounterAuth {
-        pub fn new(_ctx: Context<Empty>) -> Result<Self, ProgramError> {
-            Ok(Self {})
-        }
-    }
+    pub struct CounterAuth;
 
     impl<'info> Auth<'info, Empty> for CounterAuth {
         fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {

+ 404 - 358
lang/syn/src/codegen/program.rs

@@ -69,39 +69,14 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
     // Dispatch the state constructor.
     let ctor_state_dispatch_arm = match &program.state {
         None => quote! { /* no-op */ },
-        Some(state) => {
-            let variant_arm = generate_ctor_variant(state);
-            let ctor_args = generate_ctor_args(state);
-            let ix_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
-            let sighash_arr = sighash_ctor();
-            let sighash_tts: proc_macro2::TokenStream =
-                format!("{:?}", sighash_arr).parse().unwrap();
-            quote! {
-                #sighash_tts => {
-                    let ix = instruction::#ix_name::deserialize(&mut instruction_data)
-                        .map_err(|_| ProgramError::Custom(1))?; // todo: error code
-                    let instruction::#variant_arm = ix;
-                    __private::__ctor(program_id, accounts, #(#ctor_args),*)
-                }
-            }
-        }
-    };
-
-    // Dispatch the state impl instructions.
-    let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(s) => s
-            .methods
-            .iter()
-            .map(|rpc: &crate::StateRpc| {
-                let rpc_arg_names: Vec<&syn::Ident> =
-                    rpc.args.iter().map(|arg| &arg.name).collect();
-                let name = &rpc.raw_method.sig.ident.to_string();
-                let rpc_name: proc_macro2::TokenStream = { format!("__{}", name).parse().unwrap() };
-                let variant_arm =
-                    generate_ix_variant(rpc.raw_method.sig.ident.to_string(), &rpc.args, true);
-                let ix_name = generate_ix_variant_name(rpc.raw_method.sig.ident.to_string(), true);
-                let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
+        Some(state) => match state.ctor_and_anchor.is_some() {
+            false => quote! {},
+            true => {
+                let variant_arm = generate_ctor_variant(state);
+                let ctor_args = generate_ctor_args(state);
+                let ix_name: proc_macro2::TokenStream =
+                    generate_ctor_variant_name().parse().unwrap();
+                let sighash_arr = sighash_ctor();
                 let sighash_tts: proc_macro2::TokenStream =
                     format!("{:?}", sighash_arr).parse().unwrap();
                 quote! {
@@ -109,66 +84,110 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                         let ix = instruction::#ix_name::deserialize(&mut instruction_data)
                             .map_err(|_| ProgramError::Custom(1))?; // todo: error code
                         let instruction::#variant_arm = ix;
-                        __private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
+                        __private::__ctor(program_id, accounts, #(#ctor_args),*)
                     }
                 }
-            })
-            .collect(),
+            }
+        },
     };
 
-    // Dispatch all trait interface implementations.
-    let trait_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
+    // Dispatch the state impl instructions.
+    let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
         None => vec![],
         Some(s) => s
-            .interfaces
-            .iter()
-            .flat_map(|iface: &crate::StateInterface| {
-                iface
-                    .methods
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                methods
                     .iter()
-                    .map(|m: &crate::StateRpc| {
+                    .map(|rpc: &crate::StateRpc| {
                         let rpc_arg_names: Vec<&syn::Ident> =
-                            m.args.iter().map(|arg| &arg.name).collect();
-                        let name = &m.raw_method.sig.ident.to_string();
-                        let rpc_name: proc_macro2::TokenStream =  format!("__{}_{}", iface.trait_name, name).parse().unwrap();
-                        let raw_args: Vec<&syn::PatType> = m
-                            .args
-                            .iter()
-                            .map(|arg: &crate::RpcArg| &arg.raw_arg)
-                            .collect();
-                        let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
+                            rpc.args.iter().map(|arg| &arg.name).collect();
+                        let name = &rpc.raw_method.sig.ident.to_string();
+                        let rpc_name: proc_macro2::TokenStream =
+                            { format!("__{}", name).parse().unwrap() };
+                        let variant_arm = generate_ix_variant(
+                            rpc.raw_method.sig.ident.to_string(),
+                            &rpc.args,
+                            true,
+                        );
+                        let ix_name =
+                            generate_ix_variant_name(rpc.raw_method.sig.ident.to_string(), true);
+                        let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
                         let sighash_tts: proc_macro2::TokenStream =
                             format!("{:?}", sighash_arr).parse().unwrap();
-                        let args_struct = {
-                            if m.args.len() == 0 {
-                                quote! {
-                                    #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
-                                    struct Args;
-                                }
-                            } else {
-                                quote! {
-                                    #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
-                                    struct Args {
-                                        #(#raw_args),*
-                                    }
-                                }
-                            }
-                        };
                         quote! {
                             #sighash_tts => {
-                                #args_struct
-                                let ix = Args::deserialize(&mut instruction_data)
+                                let ix = instruction::#ix_name::deserialize(&mut instruction_data)
                                     .map_err(|_| ProgramError::Custom(1))?; // todo: error code
-                                let Args {
-                                    #(#rpc_arg_names),*
-                                } = ix;
+                                let instruction::#variant_arm = ix;
                                 __private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
                             }
                         }
                     })
-                    .collect::<Vec<proc_macro2::TokenStream>>()
+                    .collect()
             })
-            .collect(),
+            .unwrap_or(vec![]),
+    };
+
+    // Dispatch all trait interface implementations.
+    let trait_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(s) => s
+            .interfaces
+            .as_ref()
+            .map(|interfaces| {
+                interfaces
+                    .iter()
+                    .flat_map(|iface: &crate::StateInterface| {
+                        iface
+                            .methods
+                            .iter()
+                            .map(|m: &crate::StateRpc| {
+                                let rpc_arg_names: Vec<&syn::Ident> =
+                                    m.args.iter().map(|arg| &arg.name).collect();
+                                let name = &m.raw_method.sig.ident.to_string();
+                                let rpc_name: proc_macro2::TokenStream =  format!("__{}_{}", iface.trait_name, name).parse().unwrap();
+                                let raw_args: Vec<&syn::PatType> = m
+                                    .args
+                                    .iter()
+                                    .map(|arg: &crate::RpcArg| &arg.raw_arg)
+                                    .collect();
+                                let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
+                                let sighash_tts: proc_macro2::TokenStream =
+                                    format!("{:?}", sighash_arr).parse().unwrap();
+                                let args_struct = {
+                                    if m.args.len() == 0 {
+                                        quote! {
+                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
+                                            struct Args;
+                                        }
+                                    } else {
+                                        quote! {
+                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
+                                            struct Args {
+                                                #(#raw_args),*
+                                            }
+                                        }
+                                    }
+                                };
+                                quote! {
+                                    #sighash_tts => {
+                                        #args_struct
+                                        let ix = Args::deserialize(&mut instruction_data)
+                                            .map_err(|_| ProgramError::Custom(1))?; // todo: error code
+                                        let Args {
+                                            #(#rpc_arg_names),*
+                                        } = ix;
+                                        __private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
+                                    }
+                                }
+                            })
+                            .collect::<Vec<proc_macro2::TokenStream>>()
+                    })
+                    .collect()
+            })
+            .unwrap_or(vec![])
     };
 
     // Dispatch all global instructions.
@@ -346,234 +365,246 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
     };
     let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
         None => quote! {},
-        Some(state) => {
-            let ctor_typed_args = generate_ctor_typed_args(state);
-            let ctor_untyped_args = generate_ctor_args(state);
-            let name = &state.strct.ident;
-            let mod_name = &program.name;
-            let anchor_ident = &state.ctor_anchor;
-            quote! {
-                // One time state account initializer. Will faill on subsequent
-                // invocations.
-                #[inline(never)]
-                pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
-                    let mut remaining_accounts: &[AccountInfo] = accounts;
-
-                    // Deserialize accounts.
-                    let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
-                    let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
-
-                    // Invoke the ctor.
-                    let instance = #mod_name::#name::new(
-                        anchor_lang::Context::new(
-                            program_id,
-                            &mut ctor_user_def_accounts,
-                            remaining_accounts,
-                        ),
-                        #(#ctor_untyped_args),*
-                    )?;
-
-                    // Create the solana account for the ctor data.
-                    let from = ctor_accounts.from.key;
-                    let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
-                    let seed = anchor_lang::ProgramState::<#name>::seed();
-                    let owner = ctor_accounts.program.key;
-                    let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
-                    // Add 8 for the account discriminator.
-                    let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
-                    let lamports = ctor_accounts.rent.minimum_balance(space);
-                    let seeds = &[&[nonce][..]];
-                    let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
-                        from,
-                        &to,
-                        &base,
-                        seed,
-                        lamports,
-                        space as u64,
-                        owner,
-                    );
-                    anchor_lang::solana_program::program::invoke_signed(
-                        &ix,
-                        &[
-                            ctor_accounts.from.clone(),
-                            ctor_accounts.to.clone(),
-                            ctor_accounts.base.clone(),
-                            ctor_accounts.system_program.clone(),
-                        ],
-                        &[seeds],
-                    )?;
-
-                    // Serialize the state and save it to storage.
-                    ctor_user_def_accounts.exit(program_id)?;
-                    let mut data = ctor_accounts.to.try_borrow_mut_data()?;
-                    let dst: &mut [u8] = &mut data;
-                    let mut cursor = std::io::Cursor::new(dst);
-                    instance.try_serialize(&mut cursor)?;
-
-                    Ok(())
-                }
-            }
-        }
-    };
-    let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(state) => state
-            .methods
-            .iter()
-            .map(|rpc| {
-                let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
-                let rpc_arg_names: Vec<&syn::Ident> =
-                    rpc.args.iter().map(|arg| &arg.name).collect();
-                let private_rpc_name: proc_macro2::TokenStream = {
-                    let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
-                    n.parse().unwrap()
-                };
-                let rpc_name = &rpc.raw_method.sig.ident;
-                let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
-                let anchor_ident = &rpc.anchor_ident;
+        Some(state) => match state.ctor_and_anchor.as_ref() {
+            None => quote! {},
+            Some((_ctor, anchor_ident)) => {
+                let ctor_typed_args = generate_ctor_typed_args(state);
+                let ctor_untyped_args = generate_ctor_args(state);
+                let name = &state.strct.ident;
+                let mod_name = &program.name;
                 quote! {
+                    // One time state account initializer. Will faill on subsequent
+                    // invocations.
                     #[inline(never)]
-                    pub fn #private_rpc_name(
-                        program_id: &Pubkey,
-                        accounts: &[AccountInfo],
-                        #(#rpc_params),*
-                    ) -> ProgramResult {
-
+                    pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
                         let mut remaining_accounts: &[AccountInfo] = accounts;
-                        if remaining_accounts.len() == 0 {
-                            return Err(ProgramError::Custom(1)); // todo
-                        }
-
-                        // Deserialize the program state account.
-                        let state_account = &remaining_accounts[0];
-                        let mut state: #state_ty = {
-                            let data = state_account.try_borrow_data()?;
-                            let mut sliced: &[u8] = &data;
-                            anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
-                        };
-
-                        remaining_accounts = &remaining_accounts[1..];
 
-                        // Deserialize the program's execution context.
-                        let mut accounts = #anchor_ident::try_accounts(
-                            program_id,
-                            &mut remaining_accounts,
+                        // Deserialize accounts.
+                        let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
+                        let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
+
+                        // Invoke the ctor.
+                        let instance = #mod_name::#name::new(
+                            anchor_lang::Context::new(
+                                program_id,
+                                &mut ctor_user_def_accounts,
+                                remaining_accounts,
+                            ),
+                            #(#ctor_untyped_args),*
                         )?;
-                        let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
 
-                        // Execute user defined function.
-                        state.#rpc_name(
-                            ctx,
-                            #(#rpc_arg_names),*
+                        // Create the solana account for the ctor data.
+                        let from = ctor_accounts.from.key;
+                        let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
+                        let seed = anchor_lang::ProgramState::<#name>::seed();
+                        let owner = ctor_accounts.program.key;
+                        let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
+                        // Add 8 for the account discriminator.
+                        let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
+                        let lamports = ctor_accounts.rent.minimum_balance(space);
+                        let seeds = &[&[nonce][..]];
+                        let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
+                            from,
+                            &to,
+                            &base,
+                            seed,
+                            lamports,
+                            space as u64,
+                            owner,
+                        );
+                        anchor_lang::solana_program::program::invoke_signed(
+                            &ix,
+                            &[
+                                ctor_accounts.from.clone(),
+                                ctor_accounts.to.clone(),
+                                ctor_accounts.base.clone(),
+                                ctor_accounts.system_program.clone(),
+                            ],
+                            &[seeds],
                         )?;
 
                         // Serialize the state and save it to storage.
-                        accounts.exit(program_id)?;
-                        let mut data = state_account.try_borrow_mut_data()?;
+                        ctor_user_def_accounts.exit(program_id)?;
+                        let mut data = ctor_accounts.to.try_borrow_mut_data()?;
                         let dst: &mut [u8] = &mut data;
                         let mut cursor = std::io::Cursor::new(dst);
-                        state.try_serialize(&mut cursor)?;
+                        instance.try_serialize(&mut cursor)?;
 
                         Ok(())
                     }
                 }
-            })
-            .collect(),
+            }
+        },
     };
-    let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => Vec::new(),
+    let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
         Some(state) => state
-            .interfaces
-            .iter()
-            .flat_map(|iface: &crate::StateInterface| {
-                iface
-                    .methods
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                methods
                     .iter()
                     .map(|rpc| {
                         let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
                         let rpc_arg_names: Vec<&syn::Ident> =
                             rpc.args.iter().map(|arg| &arg.name).collect();
                         let private_rpc_name: proc_macro2::TokenStream = {
-                            let n = format!("__{}_{}", iface.trait_name, &rpc.raw_method.sig.ident.to_string());
+                            let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
                             n.parse().unwrap()
                         };
                         let rpc_name = &rpc.raw_method.sig.ident;
                         let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
                         let anchor_ident = &rpc.anchor_ident;
+                        quote! {
+                            #[inline(never)]
+                            pub fn #private_rpc_name(
+                                program_id: &Pubkey,
+                                accounts: &[AccountInfo],
+                                #(#rpc_params),*
+                            ) -> ProgramResult {
+
+                                let mut remaining_accounts: &[AccountInfo] = accounts;
+                                if remaining_accounts.len() == 0 {
+                                    return Err(ProgramError::Custom(1)); // todo
+                                }
 
-                        if rpc.has_receiver {
-                            quote! {
-                                #[inline(never)]
-                                pub fn #private_rpc_name(
-                                    program_id: &Pubkey,
-                                    accounts: &[AccountInfo],
-                                    #(#rpc_params),*
-                                ) -> ProgramResult {
-
-                                    let mut remaining_accounts: &[AccountInfo] = accounts;
-                                    if remaining_accounts.len() == 0 {
-                                        return Err(ProgramError::Custom(1)); // todo
-                                    }
+                                // Deserialize the program state account.
+                                let state_account = &remaining_accounts[0];
+                                let mut state: #state_ty = {
+                                    let data = state_account.try_borrow_data()?;
+                                    let mut sliced: &[u8] = &data;
+                                    anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
+                                };
+
+                                remaining_accounts = &remaining_accounts[1..];
+
+                                // Deserialize the program's execution context.
+                                let mut accounts = #anchor_ident::try_accounts(
+                                    program_id,
+                                    &mut remaining_accounts,
+                                )?;
+                                let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
+                                // Execute user defined function.
+                                state.#rpc_name(
+                                    ctx,
+                                    #(#rpc_arg_names),*
+                                )?;
 
-                                    // Deserialize the program state account.
-                                    let state_account = &remaining_accounts[0];
-                                    let mut state: #state_ty = {
-                                        let data = state_account.try_borrow_data()?;
-                                        let mut sliced: &[u8] = &data;
-                                        anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
-                                    };
-
-                                    remaining_accounts = &remaining_accounts[1..];
-
-                                    // Deserialize the program's execution context.
-                                    let mut accounts = #anchor_ident::try_accounts(
-                                        program_id,
-                                        &mut remaining_accounts,
-                                    )?;
-                                    let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
-
-                                    // Execute user defined function.
-                                    state.#rpc_name(
-                                        ctx,
-                                        #(#rpc_arg_names),*
-                                    )?;
-
-                                    // Serialize the state and save it to storage.
-                                    accounts.exit(program_id)?;
-                                    let mut data = state_account.try_borrow_mut_data()?;
-                                    let dst: &mut [u8] = &mut data;
-                                    let mut cursor = std::io::Cursor::new(dst);
-                                    state.try_serialize(&mut cursor)?;
-
-                                    Ok(())
-                                }
-                            }
-                        } else {
-                            let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
-                            quote! {
-                                #[inline(never)]
-                                pub fn #private_rpc_name(
-                                    program_id: &Pubkey,
-                                    accounts: &[AccountInfo],
-                                    #(#rpc_params),*
-                                ) -> ProgramResult {
-                                    let mut remaining_accounts: &[AccountInfo] = accounts;
-                                    let mut accounts = #anchor_ident::try_accounts(
-                                        program_id,
-                                        &mut remaining_accounts,
-                                    )?;
-                                    #state_name::#rpc_name(
-                                        Context::new(program_id, &mut accounts, remaining_accounts),
-                                        #(#rpc_arg_names),*
-                                    )?;
-                                    accounts.exit(program_id)
-                                }
+                                // Serialize the state and save it to storage.
+                                accounts.exit(program_id)?;
+                                let mut data = state_account.try_borrow_mut_data()?;
+                                let dst: &mut [u8] = &mut data;
+                                let mut cursor = std::io::Cursor::new(dst);
+                                state.try_serialize(&mut cursor)?;
+
+                                Ok(())
                             }
                         }
                     })
-                    .collect::<Vec<proc_macro2::TokenStream>>()
+                    .collect()
             })
-            .collect(),
+            .unwrap_or(vec![]),
+    };
+    let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => Vec::new(),
+        Some(state) => state
+            .interfaces
+            .as_ref()
+            .map(|interfaces| {
+                interfaces
+                    .iter()
+                    .flat_map(|iface: &crate::StateInterface| {
+                        iface
+                            .methods
+                            .iter()
+                            .map(|rpc| {
+                                let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
+                                let rpc_arg_names: Vec<&syn::Ident> =
+                                    rpc.args.iter().map(|arg| &arg.name).collect();
+                                let private_rpc_name: proc_macro2::TokenStream = {
+                                    let n = format!("__{}_{}", iface.trait_name, &rpc.raw_method.sig.ident.to_string());
+                                    n.parse().unwrap()
+                                };
+                                let rpc_name = &rpc.raw_method.sig.ident;
+                                let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
+                                let anchor_ident = &rpc.anchor_ident;
+
+                                if rpc.has_receiver {
+                                    quote! {
+                                        #[inline(never)]
+                                        pub fn #private_rpc_name(
+                                            program_id: &Pubkey,
+                                            accounts: &[AccountInfo],
+                                            #(#rpc_params),*
+                                        ) -> ProgramResult {
+
+                                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                                            if remaining_accounts.len() == 0 {
+                                                return Err(ProgramError::Custom(1)); // todo
+                                            }
+
+                                            // Deserialize the program state account.
+                                            let state_account = &remaining_accounts[0];
+                                            let mut state: #state_ty = {
+                                                let data = state_account.try_borrow_data()?;
+                                                let mut sliced: &[u8] = &data;
+                                                anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
+                                            };
+
+                                            remaining_accounts = &remaining_accounts[1..];
+
+                                            // Deserialize the program's execution context.
+                                            let mut accounts = #anchor_ident::try_accounts(
+                                                program_id,
+                                                &mut remaining_accounts,
+                                            )?;
+                                            let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
+                                            // Execute user defined function.
+                                            state.#rpc_name(
+                                                ctx,
+                                                #(#rpc_arg_names),*
+                                            )?;
+
+                                            // Serialize the state and save it to storage.
+                                            accounts.exit(program_id)?;
+                                            let mut data = state_account.try_borrow_mut_data()?;
+                                            let dst: &mut [u8] = &mut data;
+                                            let mut cursor = std::io::Cursor::new(dst);
+                                            state.try_serialize(&mut cursor)?;
+
+                                            Ok(())
+                                        }
+                                    }
+                                } else {
+                                    let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
+                                    quote! {
+                                        #[inline(never)]
+                                        pub fn #private_rpc_name(
+                                            program_id: &Pubkey,
+                                            accounts: &[AccountInfo],
+                                            #(#rpc_params),*
+                                        ) -> ProgramResult {
+                                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                                            let mut accounts = #anchor_ident::try_accounts(
+                                                program_id,
+                                                &mut remaining_accounts,
+                                            )?;
+                                            #state_name::#rpc_name(
+                                                Context::new(program_id, &mut accounts, remaining_accounts),
+                                                #(#rpc_arg_names),*
+                                            )?;
+                                            accounts.exit(program_id)
+                                        }
+                                    }
+                                }
+                            })
+                            .collect::<Vec<proc_macro2::TokenStream>>()
+                    })
+                    .collect()
+            })
+            .unwrap_or(Vec::new()),
     };
     let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
         .rpcs
@@ -663,42 +694,50 @@ pub fn generate_ctor_typed_variant_with_semi(program: &Program) -> proc_macro2::
 
 fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
     state
-        .ctor
-        .sig
-        .inputs
-        .iter()
-        .filter_map(|arg: &syn::FnArg| match arg {
-            syn::FnArg::Typed(pat_ty) => {
-                let mut arg_str = parser::tts_to_string(&pat_ty.ty);
-                arg_str.retain(|c| !c.is_whitespace());
-                if arg_str.starts_with("Context<") {
-                    return None;
-                }
-                Some(pat_ty.clone())
-            }
-            _ => panic!("Invalid syntaxe,"),
+        .ctor_and_anchor
+        .as_ref()
+        .map(|(ctor, _anchor_ident)| {
+            ctor.sig
+                .inputs
+                .iter()
+                .filter_map(|arg: &syn::FnArg| match arg {
+                    syn::FnArg::Typed(pat_ty) => {
+                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                        arg_str.retain(|c| !c.is_whitespace());
+                        if arg_str.starts_with("Context<") {
+                            return None;
+                        }
+                        Some(pat_ty.clone())
+                    }
+                    _ => panic!("Invalid syntaxe,"),
+                })
+                .collect()
         })
-        .collect()
+        .unwrap_or(Vec::new())
 }
 
 fn generate_ctor_args(state: &State) -> Vec<Box<syn::Pat>> {
     state
-        .ctor
-        .sig
-        .inputs
-        .iter()
-        .filter_map(|arg: &syn::FnArg| match arg {
-            syn::FnArg::Typed(pat_ty) => {
-                let mut arg_str = parser::tts_to_string(&pat_ty.ty);
-                arg_str.retain(|c| !c.is_whitespace());
-                if arg_str.starts_with("Context<") {
-                    return None;
-                }
-                Some(pat_ty.pat.clone())
-            }
-            _ => panic!(""),
+        .ctor_and_anchor
+        .as_ref()
+        .map(|(ctor, _anchor_ident)| {
+            ctor.sig
+                .inputs
+                .iter()
+                .filter_map(|arg: &syn::FnArg| match arg {
+                    syn::FnArg::Typed(pat_ty) => {
+                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                        arg_str.retain(|c| !c.is_whitespace());
+                        if arg_str.starts_with("Context<") {
+                            return None;
+                        }
+                        Some(pat_ty.pat.clone())
+                    }
+                    _ => panic!(""),
+                })
+                .collect()
         })
-        .collect()
+        .unwrap_or(Vec::new())
 }
 
 pub fn generate_ix_variant(
@@ -750,62 +789,67 @@ pub fn generate_instructions(program: &Program) -> proc_macro2::TokenStream {
     let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
         None => vec![],
         Some(state) => state
-            .methods
-            .iter()
-            .map(|method| {
-                let rpc_name_camel: proc_macro2::TokenStream = {
-                    let name = format!(
-                        "__{}",
-                        &method.raw_method.sig.ident.to_string().to_camel_case(),
-                    );
-                    name.parse().unwrap()
-                };
-                let raw_args: Vec<proc_macro2::TokenStream> = method
-                    .args
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                methods
                     .iter()
-                    .map(|arg| {
-                        format!("pub {}", parser::tts_to_string(&arg.raw_arg))
-                            .parse()
-                            .unwrap()
-                    })
-                    .collect();
-
-                let ix_data_trait = {
-                    let name = method.raw_method.sig.ident.to_string();
-                    let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
-                    let sighash_tts: proc_macro2::TokenStream =
-                        format!("{:?}", sighash_arr).parse().unwrap();
-                    quote! {
-                        impl anchor_lang::InstructionData for #rpc_name_camel {
-                            fn data(&self) -> Vec<u8> {
-                                let mut d = #sighash_tts.to_vec();
-                                d.append(&mut self.try_to_vec().expect("Should always serialize"));
-                                d
+                    .map(|method| {
+                        let rpc_name_camel: proc_macro2::TokenStream = {
+                            let name = format!(
+                                "__{}",
+                                &method.raw_method.sig.ident.to_string().to_camel_case(),
+                            );
+                            name.parse().unwrap()
+                        };
+                        let raw_args: Vec<proc_macro2::TokenStream> = method
+                            .args
+                            .iter()
+                            .map(|arg| {
+                                format!("pub {}", parser::tts_to_string(&arg.raw_arg))
+                                    .parse()
+                                    .unwrap()
+                            })
+                            .collect();
+
+                        let ix_data_trait = {
+                            let name = method.raw_method.sig.ident.to_string();
+                            let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
+                            let sighash_tts: proc_macro2::TokenStream =
+                                format!("{:?}", sighash_arr).parse().unwrap();
+                            quote! {
+                                impl anchor_lang::InstructionData for #rpc_name_camel {
+                                    fn data(&self) -> Vec<u8> {
+                                        let mut d = #sighash_tts.to_vec();
+                                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
+                                        d
+                                    }
+                                }
                             }
-                        }
-                    }
-                };
+                        };
 
-                // If no args, output a "unit" variant instead of a struct variant.
-                if method.args.len() == 0 {
-                    quote! {
-                        #[derive(AnchorSerialize, AnchorDeserialize)]
-                        pub struct #rpc_name_camel;
+                        // If no args, output a "unit" variant instead of a struct variant.
+                        if method.args.len() == 0 {
+                            quote! {
+                                #[derive(AnchorSerialize, AnchorDeserialize)]
+                                pub struct #rpc_name_camel;
 
-                        #ix_data_trait
-                    }
-                } else {
-                    quote! {
-                        #[derive(AnchorSerialize, AnchorDeserialize)]
-                        pub struct #rpc_name_camel {
-                            #(#raw_args),*
-                        }
+                                #ix_data_trait
+                            }
+                        } else {
+                            quote! {
+                                #[derive(AnchorSerialize, AnchorDeserialize)]
+                                pub struct #rpc_name_camel {
+                                    #(#raw_args),*
+                                }
 
-                        #ix_data_trait
-                    }
-                }
+                                #ix_data_trait
+                            }
+                        }
+                    })
+                    .collect()
             })
-            .collect(),
+            .unwrap_or(Vec::new()),
     };
     let variants: Vec<proc_macro2::TokenStream> = program
         .rpcs
@@ -876,16 +920,18 @@ pub fn generate_instructions(program: &Program) -> proc_macro2::TokenStream {
 fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
     let mut accounts = std::collections::HashSet::new();
 
-    // Got through state accounts.
+    // Go through state accounts.
     if let Some(state) = &program.state {
-        for rpc in &state.methods {
-            let anchor_ident = &rpc.anchor_ident;
-            // TODO: move to fn and share with accounts.rs.
-            let macro_name = format!(
-                "__client_accounts_{}",
-                anchor_ident.to_string().to_snake_case()
-            );
-            accounts.insert(macro_name);
+        if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
+            for rpc in methods {
+                let anchor_ident = &rpc.anchor_ident;
+                // TODO: move to fn and share with accounts.rs.
+                let macro_name = format!(
+                    "__client_accounts_{}",
+                    anchor_ident.to_string().to_snake_case()
+                );
+                accounts.insert(macro_name);
+            }
         }
     }
 

+ 3 - 5
lang/syn/src/lib.rs

@@ -30,11 +30,9 @@ pub struct Program {
 pub struct State {
     pub name: String,
     pub strct: syn::ItemStruct,
-    pub impl_block: syn::ItemImpl,
-    pub methods: Vec<StateRpc>,
-    pub interfaces: Vec<StateInterface>,
-    pub ctor: syn::ImplItemMethod,
-    pub ctor_anchor: syn::Ident, // TODO: consolidate this with ctor above.
+    pub ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)>,
+    pub impl_block_and_methods: Option<(syn::ItemImpl, Vec<StateRpc>)>,
+    pub interfaces: Option<Vec<StateInterface>>,
 }
 
 #[derive(Debug)]

+ 101 - 91
lang/syn/src/parser/file.rs

@@ -33,102 +33,112 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
         acc_names
     };
 
-    let state = p.state.map(|state| {
-        let mut methods = state
-            .methods
-            .iter()
-            .map(|method: &StateRpc| {
-                let name = method.ident.to_string().to_mixed_case();
-                let args = method
-                    .args
-                    .iter()
-                    .map(|arg| {
-                        let mut tts = proc_macro2::TokenStream::new();
-                        arg.raw_arg.ty.to_tokens(&mut tts);
-                        let ty = tts.to_string().parse().unwrap();
-                        IdlField {
-                            name: arg.name.to_string().to_mixed_case(),
-                            ty,
-                        }
+    let state = match p.state {
+        None => None,
+        Some(state) => match state.ctor_and_anchor {
+            None => None, // State struct defined but no implementation
+            Some((ctor, anchor_ident)) => {
+                let mut methods = state
+                    .impl_block_and_methods
+                    .map(|(_impl_block, methods)| {
+                        methods
+                            .iter()
+                            .map(|method: &StateRpc| {
+                                let name = method.ident.to_string().to_mixed_case();
+                                let args = method
+                                    .args
+                                    .iter()
+                                    .map(|arg| {
+                                        let mut tts = proc_macro2::TokenStream::new();
+                                        arg.raw_arg.ty.to_tokens(&mut tts);
+                                        let ty = tts.to_string().parse().unwrap();
+                                        IdlField {
+                                            name: arg.name.to_string().to_mixed_case(),
+                                            ty,
+                                        }
+                                    })
+                                    .collect::<Vec<_>>();
+                                let accounts_strct =
+                                    accs.get(&method.anchor_ident.to_string()).unwrap();
+                                let accounts = accounts_strct.idl_accounts(&accs);
+                                IdlStateMethod {
+                                    name,
+                                    args,
+                                    accounts,
+                                }
+                            })
+                            .collect::<Vec<_>>()
                     })
-                    .collect::<Vec<_>>();
-                let accounts_strct = accs.get(&method.anchor_ident.to_string()).unwrap();
-                let accounts = accounts_strct.idl_accounts(&accs);
-                IdlStateMethod {
-                    name,
-                    args,
-                    accounts,
-                }
-            })
-            .collect::<Vec<_>>();
-        let ctor = {
-            let name = "new".to_string();
-            let args = state
-                .ctor
-                .sig
-                .inputs
-                .iter()
-                .filter_map(|arg: &syn::FnArg| match arg {
-                    syn::FnArg::Typed(pat_ty) => {
-                        // TODO: this filtering should be donein the parser.
-                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
-                        arg_str.retain(|c| !c.is_whitespace());
-                        if arg_str.starts_with("Context<") {
-                            return None;
-                        }
-                        Some(arg)
-                    }
-                    _ => None,
-                })
-                .map(|arg: &syn::FnArg| match arg {
-                    syn::FnArg::Typed(arg_typed) => {
-                        let mut tts = proc_macro2::TokenStream::new();
-                        arg_typed.ty.to_tokens(&mut tts);
-                        let ty = tts.to_string().parse().unwrap();
-                        IdlField {
-                            name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(),
-                            ty,
-                        }
+                    .unwrap_or(Vec::new());
+                let ctor = {
+                    let name = "new".to_string();
+                    let args = ctor
+                        .sig
+                        .inputs
+                        .iter()
+                        .filter_map(|arg: &syn::FnArg| match arg {
+                            syn::FnArg::Typed(pat_ty) => {
+                                // TODO: this filtering should be donein the parser.
+                                let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                                arg_str.retain(|c| !c.is_whitespace());
+                                if arg_str.starts_with("Context<") {
+                                    return None;
+                                }
+                                Some(arg)
+                            }
+                            _ => None,
+                        })
+                        .map(|arg: &syn::FnArg| match arg {
+                            syn::FnArg::Typed(arg_typed) => {
+                                let mut tts = proc_macro2::TokenStream::new();
+                                arg_typed.ty.to_tokens(&mut tts);
+                                let ty = tts.to_string().parse().unwrap();
+                                IdlField {
+                                    name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(),
+                                    ty,
+                                }
+                            }
+                            _ => panic!("Invalid syntax"),
+                        })
+                        .collect();
+                    let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
+                    let accounts = accounts_strct.idl_accounts(&accs);
+                    IdlStateMethod {
+                        name,
+                        args,
+                        accounts,
                     }
-                    _ => panic!("Invalid syntax"),
-                })
-                .collect();
-            let accounts_strct = accs.get(&state.ctor_anchor.to_string()).unwrap();
-            let accounts = accounts_strct.idl_accounts(&accs);
-            IdlStateMethod {
-                name,
-                args,
-                accounts,
-            }
-        };
+                };
 
-        methods.insert(0, ctor);
+                methods.insert(0, ctor);
 
-        let strct = {
-            let fields = match state.strct.fields {
-                syn::Fields::Named(f_named) => f_named
-                    .named
-                    .iter()
-                    .map(|f: &syn::Field| {
-                        let mut tts = proc_macro2::TokenStream::new();
-                        f.ty.to_tokens(&mut tts);
-                        let ty = tts.to_string().parse().unwrap();
-                        IdlField {
-                            name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
-                            ty,
-                        }
-                    })
-                    .collect::<Vec<IdlField>>(),
-                _ => panic!("State must be a struct"),
-            };
-            IdlTypeDef {
-                name: state.name,
-                ty: IdlTypeDefTy::Struct { fields },
-            }
-        };
+                let strct = {
+                    let fields = match state.strct.fields {
+                        syn::Fields::Named(f_named) => f_named
+                            .named
+                            .iter()
+                            .map(|f: &syn::Field| {
+                                let mut tts = proc_macro2::TokenStream::new();
+                                f.ty.to_tokens(&mut tts);
+                                let ty = tts.to_string().parse().unwrap();
+                                IdlField {
+                                    name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
+                                    ty,
+                                }
+                            })
+                            .collect::<Vec<IdlField>>(),
+                        _ => panic!("State must be a struct"),
+                    };
+                    IdlTypeDef {
+                        name: state.name,
+                        ty: IdlTypeDefTy::Struct { fields },
+                    }
+                };
 
-        IdlState { strct, methods }
-    });
+                Some(IdlState { strct, methods })
+            }
+        },
+    };
     let error = parse_error_enum(&f).map(|mut e| error::parse(&mut e));
     let error_codes = error.as_ref().map(|e| {
         e.codes

+ 79 - 75
lang/syn/src/parser/program.rs

@@ -27,8 +27,9 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
             })
             .next();
 
-        let impl_block: Option<&syn::ItemImpl> = strct.map(|strct| {
-            let item_impls = mod_content
+        let impl_block: Option<syn::ItemImpl> = match strct {
+            None => None,
+            Some(strct) => mod_content
                 .iter()
                 .filter_map(|item| match item {
                     syn::Item::Impl(item_impl) => {
@@ -40,13 +41,12 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                         if strct_name != impl_ty_str {
                             return None;
                         }
-                        Some(item_impl)
+                        Some(item_impl.clone())
                     }
                     _ => None,
                 })
-                .collect::<Vec<&syn::ItemImpl>>();
-            item_impls[0]
-        });
+                .next(),
+        };
 
         // All program interface implementations.
         let trait_impls: Option<Vec<StateInterface>> = strct.map(|_strct| {
@@ -84,83 +84,87 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
             let mut strct = strct.clone();
             strct.attrs = vec![];
 
-            let impl_block = impl_block.expect("Must exist if struct exists").clone();
-            let (ctor, ctor_anchor) = impl_block
-                .items
-                .iter()
-                .filter_map(|item: &syn::ImplItem| match item {
-                    syn::ImplItem::Method(m) => {
-                        if m.sig.ident.to_string() == "new" {
-                            let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
-                            match ctx_arg {
-                                syn::FnArg::Receiver(_) => panic!("invalid syntax"),
-                                syn::FnArg::Typed(arg) => {
-                                    Some((m.clone(), extract_ident(&arg).clone()))
+            let ctor_and_anchor = match &impl_block {
+                None => None,
+                Some(impl_block) => {
+                    impl_block
+                        .items
+                        .iter()
+                        .filter_map(|item: &syn::ImplItem| match item {
+                            syn::ImplItem::Method(m) => {
+                                if m.sig.ident.to_string() == "new" {
+                                    let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
+                                    match ctx_arg {
+                                        syn::FnArg::Receiver(_) => panic!("invalid syntax"),
+                                        syn::FnArg::Typed(arg) => {
+                                            Some((m.clone(), extract_ident(&arg).clone()))
+                                        }
+                                    }
+                                } else {
+                                    None
                                 }
                             }
-                        } else {
-                            None
-                        }
-                    }
-                    _ => None,
-                })
-                .next()
-                .expect("Must exist if struct exists")
-                .clone();
+                            _ => None,
+                        })
+                        .next()
+                        .clone()
+                }
+            };
 
-            let methods: Vec<StateRpc> = impl_block
-                .items
-                .iter()
-                .filter_map(|item: &syn::ImplItem| match item {
-                    syn::ImplItem::Method(m) => match m.sig.inputs.first() {
-                        None => None,
-                        Some(arg) => match arg {
-                            syn::FnArg::Typed(_) => None,
-                            syn::FnArg::Receiver(_) => {
-                                let mut args = m
-                                    .sig
-                                    .inputs
-                                    .iter()
-                                    .filter_map(|arg| match arg {
-                                        syn::FnArg::Receiver(_) => None,
-                                        syn::FnArg::Typed(arg) => Some(arg),
-                                    })
-                                    .map(|raw_arg| {
-                                        let ident = match &*raw_arg.pat {
-                                            syn::Pat::Ident(ident) => &ident.ident,
-                                            _ => panic!("invalid syntax"),
-                                        };
-                                        RpcArg {
-                                            name: ident.clone(),
-                                            raw_arg: raw_arg.clone(),
-                                        }
-                                    })
-                                    .collect::<Vec<RpcArg>>();
-                                // Remove the Anchor accounts argument
-                                let anchor = args.remove(0);
-                                let anchor_ident = extract_ident(&anchor.raw_arg).clone();
+            let impl_block_and_methods = impl_block.map(|impl_block| {
+                let methods: Vec<StateRpc> = impl_block
+                    .items
+                    .iter()
+                    .filter_map(|item: &syn::ImplItem| match item {
+                        syn::ImplItem::Method(m) => match m.sig.inputs.first() {
+                            None => None,
+                            Some(arg) => match arg {
+                                syn::FnArg::Typed(_) => None,
+                                syn::FnArg::Receiver(_) => {
+                                    let mut args = m
+                                        .sig
+                                        .inputs
+                                        .iter()
+                                        .filter_map(|arg| match arg {
+                                            syn::FnArg::Receiver(_) => None,
+                                            syn::FnArg::Typed(arg) => Some(arg),
+                                        })
+                                        .map(|raw_arg| {
+                                            let ident = match &*raw_arg.pat {
+                                                syn::Pat::Ident(ident) => &ident.ident,
+                                                _ => panic!("invalid syntax"),
+                                            };
+                                            RpcArg {
+                                                name: ident.clone(),
+                                                raw_arg: raw_arg.clone(),
+                                            }
+                                        })
+                                        .collect::<Vec<RpcArg>>();
+                                    // Remove the Anchor accounts argument
+                                    let anchor = args.remove(0);
+                                    let anchor_ident = extract_ident(&anchor.raw_arg).clone();
 
-                                Some(StateRpc {
-                                    raw_method: m.clone(),
-                                    ident: m.sig.ident.clone(),
-                                    args,
-                                    anchor_ident,
-                                    has_receiver: true,
-                                })
-                            }
+                                    Some(StateRpc {
+                                        raw_method: m.clone(),
+                                        ident: m.sig.ident.clone(),
+                                        args,
+                                        anchor_ident,
+                                        has_receiver: true,
+                                    })
+                                }
+                            },
                         },
-                    },
-                    _ => None,
-                })
-                .collect();
+                        _ => None,
+                    })
+                    .collect();
+                (impl_block.clone(), methods)
+            });
             State {
                 name: strct.ident.to_string(),
                 strct: strct.clone(),
-                interfaces: trait_impls.expect("Some if state exists"),
-                impl_block,
-                ctor,
-                ctor_anchor,
-                methods,
+                interfaces: trait_impls,
+                impl_block_and_methods,
+                ctor_and_anchor,
             }
         })
     };