Browse Source

Merge pull request #77 from project-serum/armani/fns

Armani Ferrante 4 years ago
parent
commit
d20363fb2c

+ 5 - 0
CHANGELOG.md

@@ -11,8 +11,13 @@ incremented for features.
 
 ## [Unreleased]
 
+### Features
+
 * cli: Embed workspace programs into local validator genesis when testing.
 * 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
 

+ 2 - 0
Cargo.lock

@@ -57,6 +57,7 @@ dependencies = [
  "anyhow",
  "proc-macro2 1.0.24",
  "quote 1.0.8",
+ "regex",
  "syn 1.0.57",
 ]
 
@@ -181,6 +182,7 @@ name = "anchor-spl"
 version = "0.2.0"
 dependencies = [
  "anchor-lang",
+ "solana-program",
  "spl-token 3.0.1",
 ]
 

+ 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 {

+ 7 - 6
examples/lockup/programs/lockup/src/calculator.rs

@@ -7,7 +7,7 @@ pub fn available_for_withdrawal(vesting: &Vesting, current_ts: i64) -> u64 {
 }
 
 // The amount of funds currently in the vault.
-pub fn balance(vesting: &Vesting) -> u64 {
+fn balance(vesting: &Vesting) -> u64 {
     vesting
         .outstanding
         .checked_sub(vesting.whitelist_owned)
@@ -33,12 +33,13 @@ fn withdrawn_amount(vesting: &Vesting) -> u64 {
 // Returns the total vested amount up to the given ts, assuming zero
 // withdrawals and zero funds sent to other programs.
 fn total_vested(vesting: &Vesting, current_ts: i64) -> u64 {
-    assert!(current_ts >= vesting.start_ts);
-
-    if current_ts >= vesting.end_ts {
-        return vesting.start_balance;
+    if current_ts < vesting.start_ts {
+        0
+    } else if current_ts >= vesting.end_ts {
+        vesting.start_balance
+    } else {
+        linear_unlock(vesting, current_ts).unwrap()
     }
-    linear_unlock(vesting, current_ts).unwrap()
 }
 
 fn linear_unlock(vesting: &Vesting, current_ts: i64) -> Option<u64> {

+ 31 - 20
examples/lockup/programs/lockup/src/lib.rs

@@ -4,8 +4,8 @@
 #![feature(proc_macro_hygiene)]
 
 use anchor_lang::prelude::*;
-use anchor_lang::solana_program;
 use anchor_lang::solana_program::instruction::Instruction;
+use anchor_lang::solana_program::program;
 use anchor_spl::token::{self, TokenAccount, Transfer};
 
 mod calculator;
@@ -18,7 +18,8 @@ pub mod lockup {
     pub struct Lockup {
         /// The key with the ability to change the whitelist.
         pub authority: Pubkey,
-        /// Valid programs the program can relay transactions to.
+        /// List of programs locked tokens can be sent to. These programs
+        /// are completely trusted to maintain the locked property.
         pub whitelist: Vec<WhitelistEntry>,
     }
 
@@ -70,25 +71,19 @@ pub mod lockup {
     pub fn create_vesting(
         ctx: Context<CreateVesting>,
         beneficiary: Pubkey,
-        end_ts: i64,
-        period_count: u64,
         deposit_amount: u64,
         nonce: u8,
+        start_ts: i64,
+        end_ts: i64,
+        period_count: u64,
         realizor: Option<Realizor>,
     ) -> Result<()> {
-        if end_ts <= ctx.accounts.clock.unix_timestamp {
-            return Err(ErrorCode::InvalidTimestamp.into());
-        }
-        if period_count > (end_ts - ctx.accounts.clock.unix_timestamp) as u64 {
-            return Err(ErrorCode::InvalidPeriod.into());
-        }
-        if period_count == 0 {
-            return Err(ErrorCode::InvalidPeriod.into());
-        }
         if deposit_amount == 0 {
             return Err(ErrorCode::InvalidDepositAmount.into());
         }
-
+        if !is_valid_schedule(start_ts, end_ts, period_count) {
+            return Err(ErrorCode::InvalidSchedule.into());
+        }
         let vesting = &mut ctx.accounts.vesting;
         vesting.beneficiary = beneficiary;
         vesting.mint = ctx.accounts.vault.mint;
@@ -96,7 +91,8 @@ pub mod lockup {
         vesting.period_count = period_count;
         vesting.start_balance = deposit_amount;
         vesting.end_ts = end_ts;
-        vesting.start_ts = ctx.accounts.clock.unix_timestamp;
+        vesting.start_ts = start_ts;
+        vesting.created_ts = ctx.accounts.clock.unix_timestamp;
         vesting.outstanding = deposit_amount;
         vesting.whitelist_owned = 0;
         vesting.grantor = *ctx.accounts.depositor_authority.key;
@@ -321,9 +317,10 @@ pub struct Vesting {
     /// originally deposited.
     pub start_balance: u64,
     /// The unix timestamp at which this vesting account was created.
+    pub created_ts: i64,
+    /// The time at which vesting begins.
     pub start_ts: i64,
-    /// The ts at which all the tokens associated with this account
-    /// should be vested.
+    /// The time at which all tokens are vested.
     pub end_ts: i64,
     /// The number of times vesting will occur. For example, if vesting
     /// is once a year over seven years, this will be 7.
@@ -400,6 +397,8 @@ pub enum ErrorCode {
     InvalidLockRealizor,
     #[msg("You have not realized this vesting account.")]
     UnrealizedVesting,
+    #[msg("Invalid vesting schedule given.")]
+    InvalidSchedule,
 }
 
 impl<'a, 'b, 'c, 'info> From<&mut CreateVesting<'info>>
@@ -471,8 +470,7 @@ pub fn whitelist_relay_cpi<'info>(
     let signer = &[&seeds[..]];
     let mut accounts = transfer.to_account_infos();
     accounts.extend_from_slice(&remaining_accounts);
-    solana_program::program::invoke_signed(&relay_instruction, &accounts, signer)
-        .map_err(Into::into)
+    program::invoke_signed(&relay_instruction, &accounts, signer).map_err(Into::into)
 }
 
 pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()> {
@@ -491,10 +489,23 @@ fn whitelist_auth(lockup: &Lockup, ctx: &Context<Auth>) -> Result<()> {
     Ok(())
 }
 
+pub fn is_valid_schedule(start_ts: i64, end_ts: i64, period_count: u64) -> bool {
+    if end_ts <= start_ts {
+        return false;
+    }
+    if period_count > (end_ts - start_ts) as u64 {
+        return false;
+    }
+    if period_count == 0 {
+        return false;
+    }
+    true
+}
+
 // Returns Ok if the locked vesting account has been "realized". Realization
 // is application dependent. For example, in the case of staking, one must first
 // unstake before being able to earn locked tokens.
-fn is_realized<'info>(ctx: &Context<Withdraw>) -> Result<()> {
+fn is_realized(ctx: &Context<Withdraw>) -> Result<()> {
     if let Some(realizor) = &ctx.accounts.vesting.realizor {
         let cpi_program = {
             let p = ctx.remaining_accounts[0].clone();

+ 24 - 19
examples/lockup/programs/registry/src/lib.rs

@@ -358,6 +358,16 @@ mod registry {
         if ctx.accounts.clock.unix_timestamp >= expiry_ts {
             return Err(ErrorCode::InvalidExpiry.into());
         }
+        if let RewardVendorKind::Locked {
+            start_ts,
+            end_ts,
+            period_count,
+        } = kind
+        {
+            if !lockup::is_valid_schedule(start_ts, end_ts, period_count) {
+                return Err(ErrorCode::InvalidVestingSchedule.into());
+            }
+        }
 
         // Transfer funds into the vendor's vault.
         token::transfer(ctx.accounts.into(), total)?;
@@ -384,7 +394,7 @@ mod registry {
         vendor.from = *ctx.accounts.depositor_authority.key;
         vendor.total = total;
         vendor.expired = false;
-        vendor.kind = kind.clone();
+        vendor.kind = kind;
 
         Ok(())
     }
@@ -434,12 +444,13 @@ mod registry {
         ctx: Context<'a, 'b, 'c, 'info, ClaimRewardLocked<'info>>,
         nonce: u8,
     ) -> Result<()> {
-        let (end_ts, period_count) = match ctx.accounts.cmn.vendor.kind {
+        let (start_ts, end_ts, period_count) = match ctx.accounts.cmn.vendor.kind {
             RewardVendorKind::Unlocked => return Err(ErrorCode::ExpectedLockedVendor.into()),
             RewardVendorKind::Locked {
+                start_ts,
                 end_ts,
                 period_count,
-            } => (end_ts, period_count),
+            } => (start_ts, end_ts, period_count),
         };
 
         // Reward distribution.
@@ -452,19 +463,6 @@ mod registry {
             .unwrap();
         assert!(reward_amount > 0);
 
-        // The lockup program requires the timestamp to be >= clock's timestamp.
-        // So update if the time has already passed.
-        //
-        // If the reward is within `period_count` seconds of fully vesting, then
-        // we bump the `end_ts` because, otherwise, the vesting account would
-        // fail to be created. Vesting must have no more frequently than the
-        // smallest unit of time, once per second, expressed as
-        // `period_count <= end_ts - start_ts`.
-        let end_ts = match end_ts < ctx.accounts.cmn.clock.unix_timestamp + period_count as i64 {
-            true => ctx.accounts.cmn.clock.unix_timestamp + period_count as i64,
-            false => end_ts,
-        };
-
         // Specify the vesting account's realizor, so that unlocks can only
         // execute once completely unstaked.
         let realizor = Some(Realizor {
@@ -487,10 +485,11 @@ mod registry {
         lockup::cpi::create_vesting(
             cpi_ctx,
             ctx.accounts.cmn.member.beneficiary,
-            end_ts,
-            period_count,
             reward_amount,
             nonce,
+            start_ts,
+            end_ts,
+            period_count,
             realizor,
         )?;
 
@@ -1165,7 +1164,11 @@ pub struct RewardVendor {
 #[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
 pub enum RewardVendorKind {
     Unlocked,
-    Locked { end_ts: i64, period_count: u64 },
+    Locked {
+        start_ts: i64,
+        end_ts: i64,
+        period_count: u64,
+    },
 }
 
 #[error]
@@ -1216,6 +1219,8 @@ pub enum ErrorCode {
     InvalidBeneficiary,
     #[msg("The given member account does not match the realizor metadata.")]
     InvalidRealizorMetadata,
+    #[msg("Invalid vesting schedule for the locked reward.")]
+    InvalidVestingSchedule,
 }
 
 impl<'a, 'b, 'c, 'info> From<&mut Deposit<'info>>

+ 11 - 7
examples/lockup/tests/lockup.js

@@ -12,6 +12,7 @@ describe("Lockup and Registry", () => {
   anchor.setProvider(provider);
 
   const lockup = anchor.workspace.Lockup;
+  const linear = anchor.workspace.Linear;
   const registry = anchor.workspace.Registry;
 
   let lockupAddress = null;
@@ -138,9 +139,10 @@ describe("Lockup and Registry", () => {
   let vestingSigner = null;
 
   it("Creates a vesting account", async () => {
-    const beneficiary = provider.wallet.publicKey;
-    const endTs = new anchor.BN(Date.now() / 1000 + 5);
+    const startTs = new anchor.BN(Date.now() / 1000);
+    const endTs = new anchor.BN(startTs.toNumber() + 5);
     const periodCount = new anchor.BN(2);
+    const beneficiary = provider.wallet.publicKey;
     const depositAmount = new anchor.BN(100);
 
     const vault = new anchor.web3.Account();
@@ -155,10 +157,11 @@ describe("Lockup and Registry", () => {
 
     await lockup.rpc.createVesting(
       beneficiary,
-      endTs,
-      periodCount,
       depositAmount,
       nonce,
+      startTs,
+      endTs,
+      periodCount,
       null, // Lock realizor is None.
       {
         accounts: {
@@ -190,11 +193,11 @@ describe("Lockup and Registry", () => {
     assert.ok(vestingAccount.grantor.equals(provider.wallet.publicKey));
     assert.ok(vestingAccount.outstanding.eq(depositAmount));
     assert.ok(vestingAccount.startBalance.eq(depositAmount));
-    assert.ok(vestingAccount.endTs.eq(endTs));
-    assert.ok(vestingAccount.periodCount.eq(periodCount));
     assert.ok(vestingAccount.whitelistOwned.eq(new anchor.BN(0)));
     assert.equal(vestingAccount.nonce, nonce);
-    assert.ok(endTs.gt(vestingAccount.startTs));
+    assert.ok(vestingAccount.createdTs.gt(new anchor.BN(0)));
+    assert.ok(vestingAccount.startTs.eq(startTs));
+    assert.ok(vestingAccount.endTs.eq(endTs));
     assert.ok(vestingAccount.realizor === null);
   });
 
@@ -582,6 +585,7 @@ describe("Lockup and Registry", () => {
   it("Drops a locked reward", async () => {
     lockedRewardKind = {
       locked: {
+        startTs: new anchor.BN(Date.now() / 1000),
         endTs: new anchor.BN(Date.now() / 1000 + 6),
         periodCount: new anchor.BN(2),
       },

+ 2 - 1
lang/attribute/access-control/Cargo.toml

@@ -15,4 +15,5 @@ proc-macro2 = "1.0"
 quote = "1.0"
 syn = { version = "=1.0.57", features = ["full"] }
 anyhow = "1.0.32"
-anchor-syn = { path = "../../syn", version = "0.2.0" }
+anchor-syn = { path = "../../syn", version = "0.2.0" }
+regex = "1.0"

+ 13 - 2
lang/attribute/access-control/src/lib.rs

@@ -50,7 +50,18 @@ pub fn access_control(
     args: proc_macro::TokenStream,
     input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
-    let access_control: proc_macro2::TokenStream = args.to_string().parse().unwrap();
+    let mut args = args.to_string();
+    args.retain(|c| !c.is_whitespace());
+    let access_control: Vec<proc_macro2::TokenStream> = args
+        .split(')')
+        .filter_map(|ac| match ac {
+            "" => None,
+            _ => Some(ac),
+        })
+        .map(|ac| format!("{})", ac)) // Put back on the split char.
+        .map(|ac| format!("{}?;", ac)) // Add `?;` syntax.
+        .map(|ac| ac.parse().unwrap())
+        .collect();
 
     let item_fn = parse_macro_input!(input as syn::ItemFn);
 
@@ -63,7 +74,7 @@ pub fn access_control(
     proc_macro::TokenStream::from(quote! {
         #fn_vis #fn_sig {
 
-            #access_control?;
+            #(#access_control)*
 
             #(#fn_stmts)*
         }

+ 9 - 0
lang/src/sysvar.rs

@@ -23,6 +23,15 @@ impl<'info, T: solana_program::sysvar::Sysvar> Sysvar<'info, T> {
     }
 }
 
+impl<'info, T: solana_program::sysvar::Sysvar> Clone for Sysvar<'info, T> {
+    fn clone(&self) -> Self {
+        Self {
+            info: self.info.clone(),
+            account: T::from_account_info(&self.info).unwrap(),
+        }
+    }
+}
+
 impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info, T> {
     fn try_accounts(
         _program_id: &Pubkey,

+ 410 - 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.
@@ -254,6 +273,12 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                 Ok(())
             }
 
+            #[inline(never)]
+            #[cfg(feature = "no-idl")]
+            pub fn __idl(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+                Err(anchor_lang::solana_program::program_error::ProgramError::Custom(99))
+            }
+
             // One time IDL account initializer. Will faill on subsequent
             // invocations.
             #[inline(never)]
@@ -346,234 +371,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 +700,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 +795,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 +926,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,
             }
         })
     };

+ 1 - 0
spl/Cargo.toml

@@ -9,3 +9,4 @@ description = "CPI clients for SPL programs"
 [dependencies]
 anchor-lang = { path = "../lang", version = "0.2.0", features = ["derive"] }
 spl-token = { version = "3.0.1", features = ["no-entrypoint"] }
+solana-program = "=1.5.0"

+ 1 - 0
spl/src/lib.rs

@@ -1 +1,2 @@
+pub mod shmem;
 pub mod token;

+ 49 - 0
spl/src/shmem.rs

@@ -0,0 +1,49 @@
+//! CPI API for interacting with the SPL shared memory
+//! [program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory).
+
+use anchor_lang::{Accounts, CpiContext};
+use solana_program::account_info::AccountInfo;
+use solana_program::declare_id;
+use solana_program::entrypoint::ProgramResult;
+use solana_program::instruction::{AccountMeta, Instruction};
+use solana_program::program;
+
+// TODO: update this once the final shared memory program gets released.
+//       shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL.
+declare_id!("DynWy94wrWp5RimU49creYMQ5py3Up8BBNS4VA73VCpi");
+
+/// `ret` writes the given `data` field to the shared memory account
+/// acting as a return value that can be used across CPI.
+/// The caleee should use this to write data into the shared memory account.
+/// The caler should use the account directly to pull out and interpret the
+/// bytes. Shared memory serialization is not specified and is up to the
+/// caller and callee.
+pub fn ret<'a, 'b, 'c, 'info>(
+    ctx: CpiContext<'a, 'b, 'c, 'info, Ret<'info>>,
+    data: Vec<u8>,
+) -> ProgramResult {
+    let instruction = Instruction {
+        program_id: *ctx.program.key,
+        accounts: vec![AccountMeta::new(*ctx.accounts.buffer.key, false)],
+        data,
+    };
+    let mut accounts = vec![ctx.accounts.buffer];
+    accounts.push(ctx.program.clone());
+    program::invoke(&instruction, &accounts)
+}
+
+#[derive(Accounts)]
+pub struct Ret<'info> {
+    #[account(mut)]
+    pub buffer: AccountInfo<'info>,
+}
+
+// A set of accounts that can be used with shared memory.
+#[derive(Accounts)]
+pub struct Shmem<'info> {
+    // Shared memory account to write the return value into.
+    #[account(mut, "shmem.owner == shmem_program.key")]
+    pub shmem: AccountInfo<'info>,
+    #[account("shmem_program.key == &ID")]
+    pub shmem_program: AccountInfo<'info>,
+}