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

This commit is contained in:
Armani Ferrante 2021-02-10 21:49:07 +08:00 committed by GitHub
commit d20363fb2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 751 additions and 595 deletions

View File

@ -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
Cargo.lock generated
View File

@ -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",
]

View File

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

View File

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

View File

@ -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();

View File

@ -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>>

View File

@ -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),
},

View File

@ -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"

View File

@ -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)*
}

View File

@ -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,

View File

@ -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,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<proc_macro2::TokenStream> = 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::<Vec<proc_macro2::TokenStream>>()
})
.collect::<Vec<proc_macro2::TokenStream>>()
.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<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)?
};
// 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<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 {
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::<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();
.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
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;
#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<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);
}
}
}

View File

@ -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)]

View File

@ -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)
.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::<Vec<IdlField>>(),
_ => 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::<Vec<IdlField>>(),
_ => 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

View File

@ -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,
}
})
};

View File

@ -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"

View File

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

49
spl/src/shmem.rs Normal file
View File

@ -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>,
}