diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba87e57f..66e54b5ee 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/Cargo.lock b/Cargo.lock index c0c1b097f..f3c32b764 100644 --- a/Cargo.lock +++ b/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", ] diff --git a/examples/interface/programs/counter-auth/src/lib.rs b/examples/interface/programs/counter-auth/src/lib.rs index 8057455fe..e6127caa9 100644 --- a/examples/interface/programs/counter-auth/src/lib.rs +++ b/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) -> Result { - Ok(Self {}) - } - } + pub struct CounterAuth; impl<'info> Auth<'info, Empty> for CounterAuth { fn is_authorized(_ctx: Context, current: u64, new: u64) -> ProgramResult { diff --git a/examples/lockup/programs/lockup/src/calculator.rs b/examples/lockup/programs/lockup/src/calculator.rs index 5b564924c..c897fbd6f 100644 --- a/examples/lockup/programs/lockup/src/calculator.rs +++ b/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 { diff --git a/examples/lockup/programs/lockup/src/lib.rs b/examples/lockup/programs/lockup/src/lib.rs index f59c8058f..a555b658a 100644 --- a/examples/lockup/programs/lockup/src/lib.rs +++ b/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, } @@ -70,25 +71,19 @@ pub mod lockup { pub fn create_vesting( ctx: Context, beneficiary: Pubkey, - end_ts: i64, - period_count: u64, deposit_amount: u64, nonce: u8, + start_ts: i64, + end_ts: i64, + period_count: u64, realizor: Option, ) -> 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) -> 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) -> Result<()> { +fn is_realized(ctx: &Context) -> Result<()> { if let Some(realizor) = &ctx.accounts.vesting.realizor { let cpi_program = { let p = ctx.remaining_accounts[0].clone(); diff --git a/examples/lockup/programs/registry/src/lib.rs b/examples/lockup/programs/registry/src/lib.rs index 83f4a4ba6..472e58cb9 100644 --- a/examples/lockup/programs/registry/src/lib.rs +++ b/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>> diff --git a/examples/lockup/tests/lockup.js b/examples/lockup/tests/lockup.js index e35521e75..6cbc3b4ef 100644 --- a/examples/lockup/tests/lockup.js +++ b/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), }, diff --git a/lang/attribute/access-control/Cargo.toml b/lang/attribute/access-control/Cargo.toml index 73a5d9303..d89fbb97e 100644 --- a/lang/attribute/access-control/Cargo.toml +++ b/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" } \ No newline at end of file +anchor-syn = { path = "../../syn", version = "0.2.0" } +regex = "1.0" \ No newline at end of file diff --git a/lang/attribute/access-control/src/lib.rs b/lang/attribute/access-control/src/lib.rs index f53af1a06..da2daeed1 100644 --- a/lang/attribute/access-control/src/lib.rs +++ b/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 = 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)* } diff --git a/lang/src/sysvar.rs b/lang/src/sysvar.rs index b45223ced..8cf9bb50d 100644 --- a/lang/src/sysvar.rs +++ b/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, diff --git a/lang/syn/src/codegen/program.rs b/lang/syn/src/codegen/program.rs index 2b068ed21..68f2cc5f1 100644 --- a/lang/syn/src/codegen/program.rs +++ b/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 = 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,11 +84,50 @@ 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),*) } } + } + }, + }; + + // Dispatch the state impl instructions. + let state_dispatch_arms: Vec = match &program.state { + None => vec![], + Some(s) => s + .impl_block_and_methods + .as_ref() + .map(|(_impl_block, methods)| { + 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); + 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::#rpc_name(program_id, accounts, #(#rpc_arg_names),*) + } + } + }) + .collect() }) - .collect(), + .unwrap_or(vec![]), }; // Dispatch all trait interface implementations. @@ -121,54 +135,59 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream { None => vec![], Some(s) => s .interfaces - .iter() - .flat_map(|iface: &crate::StateInterface| { - iface - .methods + .as_ref() + .map(|interfaces| { + interfaces .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 + .flat_map(|iface: &crate::StateInterface| { + iface + .methods .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 { + .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! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] - struct Args; - } - } else { - quote! { - #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] - struct Args { - #(#raw_args),* + #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),*) } } - } - }; - 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::>() }) - .collect::>() + .collect() }) - .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 = 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)? - }; + // 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)?; - remaining_accounts = &remaining_accounts[1..]; - - // Deserialize the program's execution context. - let mut 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 = match &program.state { - None => Vec::new(), + let non_inlined_state_handlers: Vec = 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 { - 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) + 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(()) } } }) - .collect::>() + .collect() }) - .collect(), + .unwrap_or(vec![]), + }; + let non_inlined_state_trait_handlers: Vec = 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::>() + }) + .collect() + }) + .unwrap_or(Vec::new()), }; let non_inlined_handlers: Vec = 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 { 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> { 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 = 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 = 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(); + .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 = 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 { - let mut d = #sighash_tts.to_vec(); - d.append(&mut self.try_to_vec().expect("Should always serialize")); - d + 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 { + 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; + + #ix_data_trait + } + } else { + quote! { + #[derive(AnchorSerialize, AnchorDeserialize)] + pub struct #rpc_name_camel { + #(#raw_args),* + } + + #ix_data_trait } } - } - }; - - // 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 - } - } + }) + .collect() }) - .collect(), + .unwrap_or(Vec::new()), }; let variants: Vec = 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); + } } } diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 33f0d989e..75cac0bcb 100644 --- a/lang/syn/src/lib.rs +++ b/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, - pub interfaces: Vec, - 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)>, + pub interfaces: Option>, } #[derive(Debug)] diff --git a/lang/syn/src/parser/file.rs b/lang/syn/src/parser/file.rs index 2e3aa922a..2101fbef6 100644 --- a/lang/syn/src/parser/file.rs +++ b/lang/syn/src/parser/file.rs @@ -33,102 +33,112 @@ pub fn parse(filename: impl AsRef) -> Result { 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::>(); + let accounts_strct = + accs.get(&method.anchor_ident.to_string()).unwrap(); + let accounts = accounts_strct.idl_accounts(&accs); + IdlStateMethod { + name, + args, + accounts, + } + }) + .collect::>() }) - .collect::>(); - let accounts_strct = accs.get(&method.anchor_ident.to_string()).unwrap(); - let accounts = accounts_strct.idl_accounts(&accs); - IdlStateMethod { - name, - args, - accounts, - } - }) - .collect::>(); - 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) + .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, } - _ => 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, - } + }; + + 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::>(), + _ => panic!("State must be a struct"), + }; + IdlTypeDef { + name: state.name, + ty: IdlTypeDefTy::Struct { fields }, } - _ => 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, + }; + + Some(IdlState { strct, methods }) } - }; - - 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::>(), - _ => panic!("State must be a struct"), - }; - IdlTypeDef { - name: state.name, - ty: IdlTypeDefTy::Struct { fields }, - } - }; - - 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 diff --git a/lang/syn/src/parser/program.rs b/lang/syn/src/parser/program.rs index 4c92fca40..32a983c71 100644 --- a/lang/syn/src/parser/program.rs +++ b/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 = 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::>(); - item_impls[0] - }); + .next(), + }; // All program interface implementations. let trait_impls: Option> = 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 = 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::>(); - // 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 = 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::>(); + // 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, } }) }; diff --git a/spl/Cargo.toml b/spl/Cargo.toml index 639cedfa2..d7b68ed2f 100644 --- a/spl/Cargo.toml +++ b/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" \ No newline at end of file diff --git a/spl/src/lib.rs b/spl/src/lib.rs index 79c66ba63..e5c4fadf7 100644 --- a/spl/src/lib.rs +++ b/spl/src/lib.rs @@ -1 +1,2 @@ +pub mod shmem; pub mod token; diff --git a/spl/src/shmem.rs b/spl/src/shmem.rs new file mode 100644 index 000000000..8e7a6b8f5 --- /dev/null +++ b/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, +) -> 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>, +}