Explorar o código

lang: Zero copy state structs (#206)

Armani Ferrante %!s(int64=4) %!d(string=hai) anos
pai
achega
a8aa84a3d1

+ 80 - 0
examples/zero-copy/programs/zero-copy/src/lib.rs

@@ -9,6 +9,41 @@ use anchor_lang::prelude::*;
 pub mod zero_copy {
     use super::*;
 
+    #[state(zero_copy)]
+    pub struct Globals {
+        pub authority: Pubkey,
+        // The solana runtime currently restricts how much one can resize an
+        // account on CPI to ~10240 bytes. State accounts are program derived
+        // addresses, which means its max size is limited by this restriction
+        // (i.e., this is not an Anchor specific issue).
+        //
+        // As a result, we only use 250 events here.
+        //
+        // For larger zero-copy data structures, one must use non-state anchor
+        // accounts, as is demonstrated below.
+        pub events: [Event; 250],
+    }
+
+    impl Globals {
+        // Note that the `new` constructor is different from non-zero-copy
+        // state accounts. Namely, it takes in a `&mut self` parameter.
+        pub fn new(&mut self, ctx: Context<New>) -> ProgramResult {
+            self.authority = *ctx.accounts.authority.key;
+            Ok(())
+        }
+
+        #[access_control(auth(&self, &ctx))]
+        pub fn set_event(
+            &mut self,
+            ctx: Context<SetEvent>,
+            idx: u32,
+            event: RpcEvent,
+        ) -> ProgramResult {
+            self.events[idx as usize] = event.into();
+            Ok(())
+        }
+    }
+
     pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
         let foo = &mut ctx.accounts.foo.load_init()?;
         foo.authority = *ctx.accounts.authority.key;
@@ -58,6 +93,18 @@ pub mod zero_copy {
     }
 }
 
+#[derive(Accounts)]
+pub struct New<'info> {
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct SetEvent<'info> {
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+}
+
 #[derive(Accounts)]
 pub struct CreateFoo<'info> {
     #[account(init)]
@@ -142,3 +189,36 @@ pub struct Event {
     pub from: Pubkey,
     pub data: u64,
 }
+
+// A separate type is used for the RPC interface for two main reasons.
+//
+// 1. AnchorSerialize and AnchorDeserialize must be derived. Anchor requires
+//    *all* instructions to implement the AnchorSerialize and AnchorDeserialize
+//    traits, so any types in method signatures must as well.
+// 2. All types for zero copy deserialization are `#[repr(packed)]`. However,
+//    the implementation of AnchorSerialize (i.e. borsh), uses references
+//    to the fields it serializes. So if we were to just throw tehse derives
+//    onto the other `Event` struct, we would have references to
+//    `#[repr(packed)]` fields, which is unsafe. To avoid the unsafeness, we
+//    just use a separate type.
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct RpcEvent {
+    pub from: Pubkey,
+    pub data: u64,
+}
+
+impl From<RpcEvent> for Event {
+    fn from(e: RpcEvent) -> Event {
+        Event {
+            from: e.from,
+            data: e.data,
+        }
+    }
+}
+
+fn auth(globals: &Globals, ctx: &Context<SetEvent>) -> ProgramResult {
+    if &globals.authority != ctx.accounts.authority.key {
+        return Err(ProgramError::Custom(1)); // Arbitrary error.
+    }
+    Ok(())
+}

+ 39 - 0
examples/zero-copy/tests/zero-copy.js

@@ -9,6 +9,45 @@ describe("zero-copy", () => {
 
   const foo = new anchor.web3.Account();
 
+  it("Creates zero copy state", async () => {
+    await program.state.rpc.new({
+      accounts: {
+        authority: program.provider.wallet.publicKey,
+      },
+    });
+    const state = await program.state();
+    assert.ok(state.authority.equals(program.provider.wallet.publicKey));
+    assert.ok(state.events.length === 250);
+    state.events.forEach((event, idx) => {
+      assert.ok(event.from.equals(new anchor.web3.PublicKey()));
+      assert.ok(event.data.toNumber() === 0);
+    });
+  });
+
+  it("Updates zero copy state", async () => {
+    let event = {
+      from: new anchor.web3.PublicKey(),
+      data: new anchor.BN(1234),
+    };
+    await program.state.rpc.setEvent(5, event, {
+      accounts: {
+        authority: program.provider.wallet.publicKey,
+      },
+    });
+    const state = await program.state();
+    assert.ok(state.authority.equals(program.provider.wallet.publicKey));
+    assert.ok(state.events.length === 250);
+    state.events.forEach((event, idx) => {
+      if (idx === 5) {
+        assert.ok(event.from.equals(event.from));
+        assert.ok(event.data.eq(event.data));
+      } else {
+        assert.ok(event.from.equals(new anchor.web3.PublicKey()));
+        assert.ok(event.data.toNumber() === 0);
+      }
+    });
+  });
+
   it("Is creates a zero copy account", async () => {
     await program.rpc.createFoo({
       accounts: {

+ 36 - 8
lang/attribute/state/src/lib.rs

@@ -12,6 +12,17 @@ use syn::parse_macro_input;
 /// account size. When determining a size, make sure to reserve enough space
 /// for the 8 byte account discriminator prepended to the account. That is,
 /// always use 8 extra bytes.
+///
+/// # Zero Copy Deserialization
+///
+/// Similar to the `#[account]` attribute one can enable zero copy
+/// deserialization by using the `zero_copy` argument:
+///
+/// ```ignore
+/// #[state(zero_copy)]
+/// ```
+///
+/// For more, see the [`account`](./attr.account.html) attribute.
 #[proc_macro_attribute]
 pub fn state(
     args: proc_macro::TokenStream,
@@ -19,13 +30,14 @@ pub fn state(
 ) -> proc_macro::TokenStream {
     let item_struct = parse_macro_input!(input as syn::ItemStruct);
     let struct_ident = &item_struct.ident;
+    let is_zero_copy = args.to_string() == "zero_copy";
 
     let size_override = {
         if args.is_empty() {
             // No size override given. The account size is whatever is given
             // as the initialized value. Use the default implementation.
             quote! {
-                impl anchor_lang::AccountSize for #struct_ident {
+                impl anchor_lang::__private::AccountSize for #struct_ident {
                     fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
                         Ok(8 + self
                            .try_to_vec()
@@ -35,20 +47,36 @@ pub fn state(
                 }
             }
         } else {
-            let size = proc_macro2::TokenStream::from(args);
-            // Size override given to the macro. Use it.
-            quote! {
-                impl anchor_lang::AccountSize for #struct_ident {
-                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
-                        Ok(#size)
+            if is_zero_copy {
+                quote! {
+                    impl anchor_lang::__private::AccountSize for #struct_ident {
+                        fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                            let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64;
+                            Ok(8 + len)
+                        }
+                    }
+                }
+            } else {
+                let size = proc_macro2::TokenStream::from(args.clone());
+                // Size override given to the macro. Use it.
+                quote! {
+                    impl anchor_lang::__private::AccountSize for #struct_ident {
+                        fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                            Ok(#size)
+                        }
                     }
                 }
             }
         }
     };
 
+    let attribute = match is_zero_copy {
+        false => quote! {#[account]},
+        true => quote! {#[account(zero_copy)]},
+    };
+
     proc_macro::TokenStream::from(quote! {
-        #[account]
+        #attribute
         #item_struct
 
         #size_override

+ 10 - 7
lang/src/lib.rs

@@ -177,13 +177,6 @@ pub trait InstructionData: AnchorSerialize {
     fn data(&self) -> Vec<u8>;
 }
 
-// Calculates the size of an account, which may be larger than the deserialized
-// data in it. This trait is currently only used for `#[state]` accounts.
-#[doc(hidden)]
-pub trait AccountSize: AnchorSerialize {
-    fn size(&self) -> Result<u64, ProgramError>;
-}
-
 /// An event that can be emitted via a Solana log.
 pub trait Event: AnchorSerialize + AnchorDeserialize + Discriminator {
     fn data(&self) -> Vec<u8>;
@@ -242,6 +235,7 @@ pub mod prelude {
 // Internal module used by macros.
 #[doc(hidden)]
 pub mod __private {
+    use solana_program::program_error::ProgramError;
     use solana_program::pubkey::Pubkey;
 
     pub use crate::ctor::Ctor;
@@ -251,6 +245,13 @@ pub mod __private {
     pub use base64;
     pub use bytemuck;
 
+    // Calculates the size of an account, which may be larger than the deserialized
+    // data in it. This trait is currently only used for `#[state]` accounts.
+    #[doc(hidden)]
+    pub trait AccountSize {
+        fn size(&self) -> Result<u64, ProgramError>;
+    }
+
     // Very experimental trait.
     pub trait ZeroCopyAccessor<Ty> {
         fn get(&self) -> Ty;
@@ -265,4 +266,6 @@ pub mod __private {
             input.to_bytes()
         }
     }
+
+    pub use crate::state::PROGRAM_STATE_SEED;
 }

+ 3 - 1
lang/src/state.rs

@@ -9,6 +9,8 @@ use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::ops::{Deref, DerefMut};
 
+pub const PROGRAM_STATE_SEED: &'static str = "unversioned";
+
 /// Boxed container for the program state singleton.
 #[derive(Clone)]
 pub struct ProgramState<'info, T: AccountSerialize + AccountDeserialize + Clone> {
@@ -39,7 +41,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> {
     }
 
     pub fn seed() -> &'static str {
-        "unversioned"
+        PROGRAM_STATE_SEED
     }
 
     pub fn address(program_id: &Pubkey) -> Pubkey {

+ 244 - 107
lang/syn/src/codegen/program.rs

@@ -145,7 +145,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                             ix.args.iter().map(|arg| &arg.name).collect();
                         let name = &ix.raw_method.sig.ident.to_string();
                         let ix_method_name: proc_macro2::TokenStream =
-                            { format!("__{}", name).parse().unwrap() };
+                        { format!("__{}", name).parse().unwrap() };
                         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());
@@ -447,6 +447,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
             }
         }
     };
+    // Constructor handler.
     let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
         None => quote! {},
         Some(state) => match state.ctor_and_anchor.as_ref() {
@@ -456,72 +457,145 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                 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 __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
-                        let mut remaining_accounts: &[AccountInfo] = accounts;
-
-                        // Deserialize accounts.
-                        let ctor_accounts = anchor_lang::__private::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),*
-                        ).map_err(|e| {
-                            anchor_lang::solana_program::msg!(&e.to_string());
-                            e
-                        })?;
-
-                        // 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();
-                        let space = anchor_lang::AccountSize::size(&instance)?;
-                        let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
-                        let seeds = &[&[nonce][..]];
-                        let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
-                            from,
-                            &to,
-                            &base,
-                            seed,
-                            lamports,
-                            space,
-                            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(())
+                if state.is_zero_copy {
+                    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::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
+                            let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
+
+                            // 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::__private::PROGRAM_STATE_SEED;
+                            let owner = ctor_accounts.program.key;
+                            let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
+                            let space = 8 + std::mem::size_of::<#name>();
+                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
+                            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],
+                            )?;
+
+                            // Zero copy deserialize.
+                            let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from_init(&ctor_accounts.to)?;
+
+                            // Invoke the ctor in a new lexical scope so that
+                            // the zero-copy RefMut gets dropped. Required
+                            // so that we can subsequently run the exit routine.
+                            {
+                                let mut instance = loader.load_init()?;
+                                instance.new(
+                                    anchor_lang::Context::new(
+                                        program_id,
+                                        &mut ctor_user_def_accounts,
+                                        remaining_accounts,
+                                    ),
+                                    #(#ctor_untyped_args),*
+                                ).map_err(|e| {
+                                    anchor_lang::solana_program::msg!(&e.to_string());
+                                    e
+                                })?;
+                            }
+
+                            // Exit routines.
+                            ctor_user_def_accounts.exit(program_id)?;
+                            loader.exit(program_id)?;
+
+                            Ok(())
+                        }
+                    }
+                } else {
+                    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::__private::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),*
+                            ).map_err(|e| {
+                                anchor_lang::solana_program::msg!(&e.to_string());
+                                e
+                            })?;
+
+                            // 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();
+                            let space = anchor_lang::__private::AccountSize::size(&instance)?;
+                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
+                            let seeds = &[&[nonce][..]];
+                            let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
+                                from,
+                                &to,
+                                &base,
+                                seed,
+                                lamports,
+                                space,
+                                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(())
+                        }
                     }
                 }
             }
         },
     };
+
+    // State method handlers.
     let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
         None => vec![],
         Some(state) => state
@@ -541,60 +615,107 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                         let ix_name = &ix.raw_method.sig.ident;
                         let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
                         let anchor_ident = &ix.anchor_ident;
-                        quote! {
-                            #[inline(never)]
-                            pub fn #private_ix_name(
-                                program_id: &Pubkey,
-                                accounts: &[AccountInfo],
-                                #(#ix_params),*
-                            ) -> ProgramResult {
-
-                                let mut remaining_accounts: &[AccountInfo] = accounts;
-                                if remaining_accounts.is_empty() {
-                                    return Err(ProgramError::Custom(1)); // todo
-                                }
+                        let name = &state.strct.ident;
+                        let mod_name = &program.name;
 
-                                // 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)?
-                                };
+                        if state.is_zero_copy {
+                            quote! {
+                                #[inline(never)]
+                                pub fn #private_ix_name(
+                                    program_id: &Pubkey,
+                                    accounts: &[AccountInfo],
+                                    #(#ix_params),*
+                                ) -> ProgramResult {
+                                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                                    if remaining_accounts.is_empty() {
+                                        return Err(ProgramError::Custom(1)); // todo
+                                    }
 
-                                remaining_accounts = &remaining_accounts[1..];
+                                    let state_account = &remaining_accounts[0];
+                                    let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from(&state_account)?;
+                                    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.
+                                    {
+                                        let mut state = loader.load_mut()?;
+                                        state.#ix_name(
+                                            ctx,
+                                            #(#ix_arg_names),*
+                                        ).map_err(|e| {
+                                            anchor_lang::solana_program::msg!(&e.to_string());
+                                            e
+                                        })?;
+                                    }
+                                    // Serialize the state and save it to storage.
+                                    accounts.exit(program_id)?;
+                                    loader.exit(program_id)?;
 
-                                // 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.#ix_name(
-                                    ctx,
-                                    #(#ix_arg_names),*
-                                ).map_err(|e| {
-                                    anchor_lang::solana_program::msg!(&e.to_string());
-                                    e
-                                })?;
+                                    Ok(())
+                                }
+                            }
+                        } else {
+                            quote! {
+                                #[inline(never)]
+                                pub fn #private_ix_name(
+                                    program_id: &Pubkey,
+                                    accounts: &[AccountInfo],
+                                    #(#ix_params),*
+                                ) -> ProgramResult {
+                                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                                    if remaining_accounts.is_empty() {
+                                        return Err(ProgramError::Custom(1)); // todo
+                                    }
 
-                                // 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)?;
+                                    // 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)?
+                                    };
 
-                                Ok(())
+                                    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.#ix_name(
+                                        ctx,
+                                        #(#ix_arg_names),*
+                                    ).map_err(|e| {
+                                        anchor_lang::solana_program::msg!(&e.to_string());
+                                        e
+                                    })?;
+
+                                    // 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()
             })
-            .unwrap_or_default()
+            .unwrap_or_default(),
     };
+
+    // State trait handlers.
     let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
         None => Vec::new(),
         Some(state) => state
@@ -619,6 +740,12 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                                 let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
                                 let anchor_ident = &ix.anchor_ident;
 
+                                if state.is_zero_copy {
+                                    // Easy to implement. Just need to write a test.
+                                    // Feel free to open a PR.
+                                    panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
+                                }
+
                                 if ix.has_receiver {
                                     quote! {
                                         #[inline(never)]
@@ -802,7 +929,12 @@ fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
                         }
                         Some(pat_ty.clone())
                     }
-                    _ => panic!("Invalid syntaxe,"),
+                    _ => {
+                        if !state.is_zero_copy {
+                            panic!("Cannot pass self as parameter")
+                        }
+                        None
+                    }
                 })
                 .collect()
         })
@@ -826,7 +958,12 @@ fn generate_ctor_args(state: &State) -> Vec<syn::Pat> {
                         }
                         Some(*pat_ty.pat.clone())
                     }
-                    _ => panic!(""),
+                    _ => {
+                        if !state.is_zero_copy {
+                            panic!("Cannot pass self as parameter");
+                        }
+                        None
+                    }
                 })
                 .collect()
         })

+ 1 - 0
lang/syn/src/lib.rs

@@ -33,6 +33,7 @@ pub struct State {
     pub ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)>,
     pub impl_block_and_methods: Option<(syn::ItemImpl, Vec<StateIx>)>,
     pub interfaces: Option<Vec<StateInterface>>,
+    pub is_zero_copy: bool,
 }
 
 #[derive(Debug)]

+ 28 - 8
lang/syn/src/parser/program.rs

@@ -1,8 +1,12 @@
 use crate::parser;
 use crate::{Ix, IxArg, Program, State, StateInterface, StateIx};
 
+// Name of the attribute denoting a state struct.
 const STATE_STRUCT_ATTRIBUTE: &str = "state";
 
+// Reserved keyword for the constructor method.
+const CTOR_METHOD_NAME: &str = "new";
+
 pub fn parse(program_mod: syn::ItemMod) -> Program {
     let mod_ident = &program_mod.ident;
     let mod_content = &program_mod.content.as_ref().unwrap().1;
@@ -10,7 +14,7 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
     // Parse program state.
     let state: Option<State> = {
         // Parse `struct` marked with the `#[state]` attribute.
-        let strct: Option<&syn::ItemStruct> = mod_content
+        let strct: Option<(&syn::ItemStruct, bool)> = mod_content
             .iter()
             .filter_map(|item| match item {
                 syn::Item::Struct(item_strct) => {
@@ -22,8 +26,8 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                     if attr_label != Some(STATE_STRUCT_ATTRIBUTE.to_string()) {
                         return None;
                     }
-
-                    Some(item_strct)
+                    let is_zero_copy = parser::tts_to_string(&attrs[0].tokens) == "(zero_copy)";
+                    Some((item_strct, is_zero_copy))
                 }
                 _ => None,
             })
@@ -31,7 +35,7 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
         // Parse `impl` block for the state struct.
         let impl_block: Option<syn::ItemImpl> = match strct {
             None => None,
-            Some(strct) => mod_content
+            Some((strct, _)) => mod_content
                 .iter()
                 .filter_map(|item| match item {
                     syn::Item::Impl(item_impl) => {
@@ -58,10 +62,21 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                     .iter()
                     .filter_map(|item: &syn::ImplItem| match item {
                         syn::ImplItem::Method(m) => {
-                            if m.sig.ident == "new" {
-                                let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
+                            if m.sig.ident == CTOR_METHOD_NAME {
+                                let (_, is_zero_copy) = strct.as_ref().unwrap();
+                                let ctx_arg = {
+                                    if *is_zero_copy {
+                                        // Second param is context.
+                                        let mut iter = m.sig.inputs.iter();
+                                        iter.next().expect("First param must be receiver");
+                                        iter.next().expect("Second param must be Context")
+                                    } else {
+                                        // First param is ctx.
+                                        m.sig.inputs.first().unwrap()
+                                    }
+                                };
                                 match ctx_arg {
-                                    syn::FnArg::Receiver(_) => panic!("invalid syntax"),
+                                    syn::FnArg::Receiver(_) => panic!("invalid ctor syntax"),
                                     syn::FnArg::Typed(arg) => {
                                         Some((m.clone(), extract_ident(&arg).clone()))
                                     }
@@ -80,6 +95,10 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
             impl_block
                 .items
                 .iter()
+                .filter(|item| match item {
+                    syn::ImplItem::Method(m) => m.sig.ident.to_string() != CTOR_METHOD_NAME,
+                    _ => false,
+                })
                 .filter_map(|item: &syn::ImplItem| match item {
                     syn::ImplItem::Method(m) => match m.sig.inputs.first() {
                         None => None,
@@ -196,7 +215,7 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                 .collect::<Vec<StateInterface>>()
         });
         // Put it all together.
-        strct.map(|strct| {
+        strct.map(|(strct, is_zero_copy)| {
             // Chop off the `#[state]` attribute. It's just a marker.
             let mut strct = strct.clone();
             strct.attrs = vec![];
@@ -207,6 +226,7 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                 interfaces: trait_impls,
                 impl_block_and_methods: impl_block.map(|impl_block| (impl_block, methods.unwrap())),
                 ctor_and_anchor,
+                is_zero_copy,
             }
         })
     };