lang: Initialize program derived addresses with instruction data (#386)

This commit is contained in:
Armani Ferrante 2021-06-15 17:15:51 -07:00 committed by GitHub
parent 4ce26d5531
commit a6ebaabac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 853 additions and 454 deletions

View File

@ -24,6 +24,8 @@ incremented for features.
* cli, client, lang: Update solana toolchain to v1.7.1 ([#368](https://github.com/project-serum/anchor/pull/369)).
* ts: Instruction decoding and formatting ([#372](https://github.com/project-serum/anchor/pull/372)).
* lang: Add `#[account(close = <destination>)]` constraint for closing accounts and sending the rent exemption lamports to a specified destination account ([#371](https://github.com/project-serum/anchor/pull/371)).
* lang: Instruction data is now available to accounts constraints ([#386](https://github.com/project-serum/anchor/pull/386)).
* lang: Initialize program derived addresses with accounts constraints ([#386](https://github.com/project-serum/anchor/pull/386)).
### Fixes
@ -32,6 +34,7 @@ incremented for features.
### Breaking
* lang, ts: Framework defined error codes are introduced, reserving error codes 0-300 for Anchor, and 300 and up for user defined error codes ([#354](https://github.com/project-serum/anchor/pull/354)).
* lang: Accounts trait now accepts an additional `&[u8]` parameter ([#386](https://github.com/project-serum/anchor/pull/386)).
## [0.7.0] - 2021-05-31

View File

@ -499,8 +499,11 @@ mod registry {
let signer = &[&seeds[..]];
let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
let cpi_program = ctx.accounts.lockup_program.clone();
let cpi_accounts =
CreateVesting::try_accounts(ctx.accounts.lockup_program.key, &mut remaining_accounts)?;
let cpi_accounts = CreateVesting::try_accounts(
ctx.accounts.lockup_program.key,
&mut remaining_accounts,
&[],
)?;
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
lockup::cpi::create_vesting(
cpi_ctx,

View File

@ -93,6 +93,76 @@ pub mod misc {
pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
Ok(())
}
pub fn test_instruction_constraint(
_ctx: Context<TestInstructionConstraint>,
_nonce: u8,
) -> ProgramResult {
Ok(())
}
pub fn test_pda_init(
ctx: Context<TestPdaInit>,
_domain: String,
_seed: Vec<u8>,
_bump: u8,
) -> ProgramResult {
ctx.accounts.my_pda.data = 6;
Ok(())
}
pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>, bump: u8) -> ProgramResult {
let mut acc = ctx.accounts.my_pda.load_init()?;
acc.data = 9;
acc.bump = bump;
Ok(())
}
pub fn test_pda_mut_zero_copy(ctx: Context<TestPdaMutZeroCopy>) -> ProgramResult {
let mut acc = ctx.accounts.my_pda.load_mut()?;
acc.data = 1234;
Ok(())
}
}
#[derive(Accounts)]
#[instruction(nonce: u8)]
pub struct TestInstructionConstraint<'info> {
#[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
pub my_pda: AccountInfo<'info>,
pub my_account: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(domain: String, seed: Vec<u8>, bump: u8)]
pub struct TestPdaInit<'info> {
#[account(
init,
seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
payer = my_payer,
)]
my_pda: ProgramAccount<'info, DataU16>,
my_payer: AccountInfo<'info>,
foo: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct TestPdaInitZeroCopy<'info> {
#[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
my_pda: Loader<'info, DataZeroCopy>,
my_payer: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestPdaMutZeroCopy<'info> {
#[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
my_pda: Loader<'info, DataZeroCopy>,
my_payer: AccountInfo<'info>,
}
#[derive(Accounts)]
@ -194,6 +264,7 @@ pub struct TestI8<'info> {
}
#[associated]
#[derive(Default)]
pub struct TestData {
data: u64,
}
@ -205,6 +276,7 @@ pub struct Data {
}
#[account]
#[derive(Default)]
pub struct DataU16 {
data: u16,
}
@ -219,6 +291,13 @@ pub struct DataI16 {
data: i16,
}
#[account(zero_copy)]
#[derive(Default)]
pub struct DataZeroCopy {
data: u16,
bump: u8,
}
#[event]
pub struct E1 {
data: u32,

View File

@ -1,4 +1,5 @@
const anchor = require("@project-serum/anchor");
const PublicKey = anchor.web3.PublicKey;
const serumCmn = require("@project-serum/common");
const assert = require("assert");
@ -321,4 +322,84 @@ describe("misc", () => {
);
assert.ok(closedAccount === null);
});
it("Can use instruction data in accounts constraints", async () => {
// b"my-seed"
const seed = Buffer.from([109, 121, 45, 115, 101, 101, 100]);
const [myPda, nonce] = await PublicKey.findProgramAddress(
[seed, anchor.web3.SYSVAR_RENT_PUBKEY.toBuffer()],
program.programId
);
await program.rpc.testInstructionConstraint(nonce, {
accounts: {
myPda,
myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
});
it("Can create a PDA account with instruction data", async () => {
const seed = Buffer.from([1, 2, 3, 4]);
const domain = "my-domain";
const foo = anchor.web3.SYSVAR_RENT_PUBKEY;
const [myPda, nonce] = await PublicKey.findProgramAddress(
[
Buffer.from(anchor.utils.bytes.utf8.encode("my-seed")),
Buffer.from(anchor.utils.bytes.utf8.encode(domain)),
foo.toBuffer(),
seed,
],
program.programId
);
await program.rpc.testPdaInit(domain, seed, nonce, {
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
foo,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
const myPdaAccount = await program.account.dataU16.fetch(myPda);
assert.ok(myPdaAccount.data === 6);
});
it("Can create a zero copy PDA account", async () => {
const [myPda, nonce] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
program.programId
);
await program.rpc.testPdaInitZeroCopy(nonce, {
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
systemProgram: anchor.web3.SystemProgram.programId,
},
});
const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
assert.ok(myPdaAccount.data === 9);
assert.ok((myPdaAccount.bump = nonce));
});
it("Can write to a zero copy PDA account", async () => {
const [myPda, bump] = await PublicKey.findProgramAddress(
[Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
program.programId
);
await program.rpc.testPdaMutZeroCopy({
accounts: {
myPda,
myPayer: program.provider.wallet.publicKey,
},
});
const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
assert.ok(myPdaAccount.data === 1234);
assert.ok((myPdaAccount.bump = bump));
});
});

View File

@ -49,6 +49,7 @@ pub struct Mint {
}
#[associated]
#[derive(Default)]
pub struct Token {
pub amount: u32,
pub authority: Pubkey,

View File

@ -175,6 +175,7 @@ pub struct Foo {
}
#[associated(zero_copy)]
#[derive(Default)]
pub struct Bar {
pub authority: Pubkey,
pub data: u64,

View File

@ -238,7 +238,6 @@ pub fn associated(
let args: proc_macro2::TokenStream = args.into();
proc_macro::TokenStream::from(quote! {
#[anchor_lang::account(#args)]
#[derive(Default)]
#account_strct
impl anchor_lang::Bump for #account_name {

View File

@ -53,7 +53,7 @@ use syn::parse_macro_input;
/// | `#[account(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Whe `init` is provided, creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. When `init` is not provided, then ensures the given associated account has the expected address, defined by the program and the given seeds. |
// TODO: How do we make the markdown render correctly without putting everything
// on absurdly long lines?
#[proc_macro_derive(Accounts, attributes(account))]
#[proc_macro_derive(Accounts, attributes(account, instruction))]
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
parse_macro_input!(item as anchor_syn::AccountsStruct)
.to_token_stream()

View File

@ -10,6 +10,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -10,8 +10,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
) -> Result<Self, ProgramError> {
T::try_accounts(program_id, accounts).map(Box::new)
T::try_accounts(program_id, accounts, ix_data).map(Box::new)
}
}

View File

@ -50,6 +50,7 @@ where
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -66,6 +66,7 @@ where
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -87,6 +87,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
) -> Result<Self, ProgramError>;
}

View File

@ -132,6 +132,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -71,6 +71,7 @@ where
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -58,6 +58,7 @@ where
fn try_accounts(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -37,6 +37,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -1,10 +1,11 @@
use crate::{
CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup, ConstraintSigner,
ConstraintState, Field, Ty,
};
use quote::quote;
use syn::LitInt;
pub fn generate(f: &Field) -> proc_macro2::TokenStream {
let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
@ -58,6 +59,9 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
if let Some(c) = associated {
constraints.push(Constraint::AssociatedGroup(c));
}
if let Some(c) = seeds {
constraints.push(Constraint::Seeds(c));
}
if let Some(c) = init {
constraints.push(Constraint::Init(c));
}
@ -86,9 +90,6 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
if let Some(c) = rent_exempt {
constraints.push(Constraint::RentExempt(c));
}
if let Some(c) = seeds {
constraints.push(Constraint::Seeds(c));
}
if let Some(c) = executable {
constraints.push(Constraint::Executable(c));
}
@ -241,20 +242,230 @@ pub fn generate_constraint_rent_exempt(
}
}
pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2::TokenStream {
pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
if c.is_init {
generate_constraint_seeds_init(f, c)
} else {
generate_constraint_seeds_address(f, c)
}
}
fn generate_constraint_seeds_init(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
let payer = {
let p = &c.payer;
quote! {
let payer = #p.to_account_info();
}
};
let seeds_with_nonce = {
let s = &c.seeds;
let seeds_constraint = generate_constraint_seeds_address(f, c);
quote! {
#seeds_constraint
let seeds = [#s];
}
};
generate_pda(f, seeds_with_nonce, payer, &c.space, false)
}
fn generate_constraint_seeds_address(
f: &Field,
c: &ConstraintSeedsGroup,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let seeds = &c.seeds;
quote! {
let program_signer = Pubkey::create_program_address(
let __program_signer = Pubkey::create_program_address(
&[#seeds],
program_id,
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
if #name.to_account_info().key != &program_signer {
if #name.to_account_info().key != &__program_signer {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
}
pub fn generate_constraint_associated(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
if c.is_init {
generate_constraint_associated_init(f, c)
} else {
generate_constraint_associated_seeds(f, c)
}
}
pub fn generate_constraint_associated_init(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
let associated_target = c.associated_target.clone();
let payer = match &c.payer {
None => quote! {
let payer = #associated_target.to_account_info();
},
Some(p) => quote! {
let payer = #p.to_account_info();
},
};
let associated_seeds_constraint = generate_constraint_associated_seeds(f, c);
let seeds_with_nonce = match c.associated_seeds.len() {
0 => quote! {
#associated_seeds_constraint
let seeds = [
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
&[nonce],
];
},
_ => {
let seeds = to_seeds_tts(&c.associated_seeds);
quote! {
#associated_seeds_constraint
let seeds = [
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
#seeds
&[nonce],
];
}
}
};
generate_pda(f, seeds_with_nonce, payer, &c.space, true)
}
pub fn generate_pda(
f: &Field,
seeds_with_nonce: proc_macro2::TokenStream,
payer: proc_macro2::TokenStream,
space: &Option<LitInt>,
assign_nonce: bool,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let (account_ty, is_zero_copy) = match &f.ty {
Ty::ProgramAccount(ty) => (&ty.account_ident, false),
Ty::Loader(ty) => (&ty.account_ident, true),
_ => panic!("Invalid type for initializing a program derived address"),
};
let space = match space {
// If no explicit space param was given, serialize the type to bytes
// and take the length (with +8 for the discriminator.)
None => match is_zero_copy {
false => {
quote! {
let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
}
}
true => {
quote! {
let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
}
}
},
// Explicit account size given. Use it.
Some(s) => quote! {
let space = #s;
},
};
let account_wrapper_ty = match is_zero_copy {
false => quote! {
anchor_lang::ProgramAccount
},
true => quote! {
anchor_lang::Loader
},
};
let nonce_assignment = match assign_nonce {
false => quote! {},
true => match is_zero_copy {
false => quote! {
pa.__nonce = nonce;
},
// Zero copy is not deserialized, so the data must be lazy loaded.
true => quote! {
pa.load_init()?.__nonce = nonce;
},
},
};
quote! {
let #field: #account_wrapper_ty<#account_ty> = {
#space
#payer
let lamports = rent.minimum_balance(space);
let ix = anchor_lang::solana_program::system_instruction::create_account(
payer.to_account_info().key,
#field.to_account_info().key,
lamports,
space as u64,
program_id,
);
#seeds_with_nonce
let signer = &[&seeds[..]];
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
#field.to_account_info(),
payer.to_account_info(),
system_program.to_account_info(),
],
signer,
).map_err(|e| {
anchor_lang::solana_program::msg!("Unable to create associated account");
e
})?;
// For now, we assume all accounts created with the `associated`
// attribute have a `nonce` field in their account.
let mut pa: #account_wrapper_ty<#account_ty> = #account_wrapper_ty::try_from_init(
&#field.to_account_info(),
)?;
#nonce_assignment
pa
};
}
}
pub fn generate_constraint_associated_seeds(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let associated_target = c.associated_target.clone();
let seeds_no_nonce = match c.associated_seeds.len() {
0 => quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
]
},
_ => {
let seeds = to_seeds_tts(&c.associated_seeds);
quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
#seeds
]
}
}
};
quote! {
let (__associated_field, nonce) = Pubkey::find_program_address(
&#seeds_no_nonce,
program_id,
);
if &__associated_field != #field.to_account_info().key {
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
}
}
}
pub fn generate_constraint_executable(
f: &Field,
_c: &ConstraintExecutable,
@ -286,188 +497,6 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2:
}
}
pub fn generate_constraint_associated(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
if c.is_init {
generate_constraint_associated_init(f, c)
} else {
generate_constraint_associated_seeds(f, c)
}
}
pub fn generate_constraint_associated_init(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
let associated_target = c.associated_target.clone();
let field = &f.ident;
let (account_ty, is_zero_copy) = match &f.ty {
Ty::ProgramAccount(ty) => (&ty.account_ident, false),
Ty::Loader(ty) => (&ty.account_ident, true),
_ => panic!("Invalid associated constraint"),
};
let space = match &c.space {
// If no explicit space param was given, serialize the type to bytes
// and take the length (with +8 for the discriminator.)
None => match is_zero_copy {
false => {
quote! {
let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
}
}
true => {
quote! {
let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
}
}
},
// Explicit account size given. Use it.
Some(s) => quote! {
let space = #s;
},
};
let payer = match &c.payer {
None => quote! {
let payer = #associated_target.to_account_info();
},
Some(p) => quote! {
let payer = #p.to_account_info();
},
};
let associated_pubkey_and_nonce = generate_associated_pubkey(f, c);
let seeds_with_nonce = match c.associated_seeds.len() {
0 => quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
&[nonce],
]
},
_ => {
let seeds = to_seeds_tts(&c.associated_seeds);
quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
#seeds
&[nonce],
]
}
}
};
let account_wrapper_ty = match is_zero_copy {
false => quote! {
anchor_lang::ProgramAccount
},
true => quote! {
anchor_lang::Loader
},
};
let nonce_assignment = match is_zero_copy {
false => quote! {},
// Zero copy is not deserialized, so the data must be lazy loaded.
true => quote! {
.load_init()?
},
};
quote! {
let #field: #account_wrapper_ty<#account_ty> = {
#space
#payer
#associated_pubkey_and_nonce
if &__associated_field != #field.key {
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
}
let lamports = rent.minimum_balance(space);
let ix = anchor_lang::solana_program::system_instruction::create_account(
payer.key,
#field.key,
lamports,
space as u64,
program_id,
);
let seeds = #seeds_with_nonce;
let signer = &[&seeds[..]];
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
#field.clone(),
payer.clone(),
system_program.clone(),
],
signer,
).map_err(|e| {
anchor_lang::solana_program::msg!("Unable to create associated account");
e
})?;
// For now, we assume all accounts created with the `associated`
// attribute have a `nonce` field in their account.
let mut pa: #account_wrapper_ty<#account_ty> = #account_wrapper_ty::try_from_init(
&#field,
)?;
pa#nonce_assignment.__nonce = nonce;
pa
};
}
}
pub fn generate_constraint_associated_seeds(
f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
let generated_associated_pubkey_and_nonce = generate_associated_pubkey(f, c);
let name = &f.ident;
quote! {
#generated_associated_pubkey_and_nonce
if #name.to_account_info().key != &__associated_field {
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociated.into());
}
}
}
pub fn generate_associated_pubkey(
_f: &Field,
c: &ConstraintAssociatedGroup,
) -> proc_macro2::TokenStream {
let associated_target = c.associated_target.clone();
let seeds_no_nonce = match c.associated_seeds.len() {
0 => quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
]
},
_ => {
let seeds = to_seeds_tts(&c.associated_seeds);
quote! {
[
&b"anchor"[..],
#associated_target.to_account_info().key.as_ref(),
#seeds
]
}
}
};
quote! {
let (__associated_field, nonce) = Pubkey::find_program_address(
&#seeds_no_nonce,
program_id,
);
}
}
// Returns the inner part of the seeds slice as a token stream.
fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
assert!(seeds.len() > 0);

View File

@ -2,19 +2,13 @@ use crate::codegen::accounts::{constraints, generics};
use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
use proc_macro2::TokenStream;
use quote::quote;
use syn::Expr;
// Generates the `Accounts` trait implementation.
pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
let name = &accs.ident;
let (combined_generics, trait_generics, strct_generics) = generics(accs);
// All fields without an `#[account(associated)]` attribute.
let non_associated_fields: Vec<&AccountField> = accs
.fields
.iter()
.filter(|af| !is_associated_init(af))
.collect();
// Deserialization for each field
let deser_fields: Vec<proc_macro2::TokenStream> = accs
.fields
@ -27,14 +21,14 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
}
}
AccountField::Field(f) => {
// Associated fields are *first* deserialized into
// AccountInfos, and then later deserialized into
// ProgramAccounts in the "constraint check" phase.
if is_associated_init(af) {
if is_pda_init(af) {
let name = &f.ident;
quote!{
let #name = &accounts[0];
@ -46,7 +40,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
false => quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
},
true => quote! {
#[cfg(feature = "anchor-debug")]
@ -60,81 +54,76 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
})
.collect();
// Deserialization for each *associated* field. This must be after
// the deser_fields.
let deser_associated_fields: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.filter_map(|af| match af {
AccountField::CompositeField(_s) => None,
AccountField::Field(f) => match is_associated_init(af) {
false => None,
true => Some(f),
},
})
.map(|field: &Field| constraints::generate(field))
.collect();
let constraints = generate_constraints(accs);
let accounts_instance = generate_accounts_instance(accs);
// Constraint checks for each account fields.
let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
.iter()
.map(|af: &&AccountField| match af {
AccountField::Field(f) => constraints::generate(f),
AccountField::CompositeField(s) => constraints::generate_composite(s),
})
.collect();
// Each field in the final deserialized accounts struct.
let return_tys: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.map(|f: &AccountField| {
let name = match f {
AccountField::CompositeField(s) => &s.ident,
AccountField::Field(f) => &f.ident,
};
let ix_de = match &accs.instruction_api {
None => quote! {},
Some(ix_api) => {
let strct_inner = &ix_api;
let field_names: Vec<proc_macro2::TokenStream> = ix_api
.iter()
.map(|expr: &Expr| match expr {
Expr::Type(expr_type) => {
let field = &expr_type.expr;
quote! {
#field
}
}
_ => panic!("Invalid instruction declaration"),
})
.collect();
quote! {
#name
let mut ix_data = ix_data;
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct __Args {
#strct_inner
}
let __Args {
#(#field_names),*
} = __Args::deserialize(&mut ix_data)
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
}
})
.collect();
}
};
quote! {
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
#[inline(never)]
fn try_accounts(
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
ix_data: &[u8],
) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
// Deserialize instruction, if declared.
#ix_de
// Deserialize each account.
#(#deser_fields)*
// Deserialize each associated account.
//
// Associated accounts are treated specially, because the fields
// do deserialization + constraint checks in a single go,
// whereas all other fields, i.e. the `deser_fields`, first
// deserialize, and then do constraint checks.
#(#deser_associated_fields)*
// Perform constraint checks on each account.
#(#access_checks)*
// Execute accounts constraints.
#constraints
// Success. Return the validated accounts.
Ok(#name {
#(#return_tys),*
})
Ok(#accounts_instance)
}
}
}
}
// Returns true if the given AccountField has an associated init constraint.
fn is_associated_init(af: &AccountField) -> bool {
fn is_pda_init(af: &AccountField) -> bool {
match af {
AccountField::CompositeField(_s) => false,
AccountField::Field(f) => f
.constraints
.associated
.as_ref()
.map(|f| f.is_init)
.unwrap_or(false),
AccountField::Field(f) => {
f.constraints
.associated
.as_ref()
.map(|f| f.is_init)
.unwrap_or(false)
|| f.constraints
.seeds
.as_ref()
.map(|f| f.is_init)
.unwrap_or(false)
}
}
}
@ -196,3 +185,62 @@ fn typed_ident(field: &Field) -> TokenStream {
#name: #ty
}
}
pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
// All fields without an `#[account(associated)]` attribute.
let non_associated_fields: Vec<&AccountField> =
accs.fields.iter().filter(|af| !is_pda_init(af)).collect();
// Deserialization for each *associated* field. This must be after
// the inital extraction from the accounts slice and before access_checks.
let init_associated_fields: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.filter_map(|af| match af {
AccountField::CompositeField(_s) => None,
AccountField::Field(f) => match is_pda_init(af) {
false => None,
true => Some(f),
},
})
.map(constraints::generate)
.collect();
// Constraint checks for each account fields.
let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
.iter()
.map(|af: &&AccountField| match af {
AccountField::Field(f) => constraints::generate(f),
AccountField::CompositeField(s) => constraints::generate_composite(s),
})
.collect();
quote! {
#(#init_associated_fields)*
#(#access_checks)*
}
}
pub fn generate_accounts_instance(accs: &AccountsStruct) -> proc_macro2::TokenStream {
let name = &accs.ident;
// Each field in the final deserialized accounts struct.
let return_tys: Vec<proc_macro2::TokenStream> = accs
.fields
.iter()
.map(|f: &AccountField| {
let name = match f {
AccountField::CompositeField(s) => &s.ident,
AccountField::Field(f) => &f.ident,
};
quote! {
#name
}
})
.collect();
quote! {
#name {
#(#return_tys),*
}
}
}

View File

@ -1,6 +1,5 @@
use crate::codegen::program::common::*;
use crate::{Program, State};
use heck::CamelCase;
use crate::Program;
use quote::quote;
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
@ -10,19 +9,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
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! {
#sighash_tts => {
let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
__private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
__private::__state::__ctor(
program_id,
accounts,
ix_data,
)
}
}
}
@ -39,23 +35,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
methods
.iter()
.map(|ix: &crate::StateIx| {
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let name = &ix.raw_method.sig.ident.to_string();
let ix_method_name: proc_macro2::TokenStream =
{ format!("__{}", name).parse().unwrap() };
let variant_arm =
generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
{ format!("__{}", name).parse().unwrap() };
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::state::#ix_name::deserialize(&mut ix_data)
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
__private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
__private::__state::#ix_method_name(
program_id,
accounts,
ix_data,
)
}
}
})
@ -78,42 +70,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.methods
.iter()
.map(|m: &crate::StateIx| {
let ix_arg_names: Vec<&syn::Ident> =
m.args.iter().map(|arg| &arg.name).collect();
let name = &m.raw_method.sig.ident.to_string();
let ix_name: proc_macro2::TokenStream = format!("__{}_{}", iface.trait_name, name).parse().unwrap();
let raw_args: Vec<&syn::PatType> = m
.args
.iter()
.map(|arg: &crate::IxArg| &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.is_empty() {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#raw_args),*
}
}
}
};
let name = &m.raw_method.sig.ident.to_string();
let ix_method_name: proc_macro2::TokenStream =
format!("__{}_{}", iface.trait_name, name).parse().unwrap();
quote! {
#sighash_tts => {
#args_struct
let ix = Args::deserialize(&mut ix_data)
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let Args {
#(#ix_arg_names),*
} = ix;
__private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
__private::__interface::#ix_method_name(
program_id,
accounts,
ix_data,
)
}
}
})
@ -121,7 +90,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
})
.collect()
})
.unwrap_or_default()
.unwrap_or_default(),
};
// Dispatch all global instructions.
@ -129,19 +98,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.ixs
.iter()
.map(|ix| {
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
let ix_method_name = &ix.raw_method.sig.ident;
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &ix_method_name.to_string());
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
quote! {
#sighash_tts => {
let ix = instruction::#ix_name::deserialize(&mut ix_data)
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = ix;
__private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
__private::__global::#ix_method_name(
program_id,
accounts,
ix_data,
)
}
}
})
@ -171,7 +138,11 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// instruction, injected into all Anchor programs.
if cfg!(not(feature = "no-idl")) {
if sighash == anchor_lang::idl::IDL_IX_TAG.to_le_bytes() {
return __private::__idl::__idl_dispatch(program_id, accounts, &ix_data);
return __private::__idl::__idl_dispatch(
program_id,
accounts,
&ix_data,
);
}
}
@ -188,28 +159,3 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}
}
}
fn generate_ctor_variant_name() -> String {
"New".to_string()
}
fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
let ctor_args = generate_ctor_args(state);
let ctor_variant_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
if ctor_args.is_empty() {
quote! {
#ctor_variant_name
}
} else {
quote! {
#ctor_variant_name {
#(#ctor_args),*
}
}
}
}
fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
let n = name.to_camel_case();
n.parse().unwrap()
}

View File

@ -1,5 +1,6 @@
use crate::codegen::program::common::*;
use crate::Program;
use crate::{Program, State};
use heck::CamelCase;
use quote::quote;
// Generate non-inlined wrappers for each instruction handler, since Solana's
@ -24,27 +25,32 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
match ix {
anchor_lang::idl::IdlInstruction::Create { data_len } => {
let mut accounts = anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts)?;
let mut accounts =
anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[])?;
__idl_create_account(program_id, &mut accounts, data_len)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::CreateBuffer => {
let mut accounts = anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts)?;
let mut accounts =
anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[])?;
__idl_create_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::Write { data } => {
let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
let mut accounts =
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
__idl_write(program_id, &mut accounts, data)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
let mut accounts =
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
__idl_set_authority(program_id, &mut accounts, new_authority)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetBuffer => {
let mut accounts = anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts)?;
let mut accounts =
anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts, &[])?;
__idl_set_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
@ -167,21 +173,27 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
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;
let variant_arm = generate_ctor_variant(state);
let ix_name: proc_macro2::TokenStream =
generate_ctor_variant_name().parse().unwrap();
if state.is_zero_copy {
quote! {
// One time state account initializer. Will faill on subsequent
// invocations.
#[inline(never)]
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
// Deserialize instruction data.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Deserialize accounts.
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
@ -242,12 +254,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// 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;
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
// Deserialize instruction data.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Deserialize accounts.
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
// Invoke the ctor.
let instance = #mod_name::#name::new(
@ -313,46 +329,56 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
methods
.iter()
.map(|ix| {
let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let private_ix_name: proc_macro2::TokenStream = {
let private_ix_method_name: proc_macro2::TokenStream = {
let n = format!("__{}", &ix.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let ix_name = &ix.raw_method.sig.ident;
let ix_method_name = &ix.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &ix.anchor_ident;
let name = &state.strct.ident;
let mod_name = &program.name;
let variant_arm =
generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
if state.is_zero_copy {
quote! {
#[inline(never)]
pub fn #private_ix_name(
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#ix_params),*
ix_data: &[u8],
) -> ProgramResult {
// Deserialize instruction.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
let state_account = &remaining_accounts[0];
let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from(&state_account)?;
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
{
let mut state = loader.load_mut()?;
state.#ix_name(
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
@ -367,35 +393,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
} else {
quote! {
#[inline(never)]
pub fn #private_ix_name(
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#ix_params),*
ix_data: &[u8],
) -> ProgramResult {
// Deserialize instruction.
let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
// 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.
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#ix_name(
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
@ -431,61 +461,91 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.methods
.iter()
.map(|ix| {
let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let private_ix_name: proc_macro2::TokenStream = {
let n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let ix_name = &ix.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &ix.anchor_ident;
if state.is_zero_copy {
// Easy to implement. Just need to write a test.
// Feel free to open a PR.
panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
}
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
let private_ix_method_name: proc_macro2::TokenStream = {
let n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let ix_method_name = &ix.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &ix.anchor_ident;
let raw_args: Vec<&syn::PatType> = ix
.args
.iter()
.map(|arg: &crate::IxArg| &arg.raw_arg)
.collect();
let args_struct = {
if ix.args.is_empty() {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#raw_args),*
}
}
}
};
let deserialize_instruction = quote! {
#args_struct
let ix = Args::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let Args {
#(#ix_arg_names),*
} = ix;
};
if ix.has_receiver {
quote! {
#[inline(never)]
pub fn #private_ix_name(
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#ix_params),*
ix_data: &[u8],
) -> ProgramResult {
// Deserialize instruction.
#deserialize_instruction
// Deserialize the program state account.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
// 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.
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#ix_name(
state.#ix_method_name(
ctx,
#(#ix_arg_names),*
)?;
// Serialize the state and save it to storage.
// Exit procedures.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
@ -499,20 +559,29 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
quote! {
#[inline(never)]
pub fn #private_ix_name(
pub fn #private_ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#ix_params),*
ix_data: &[u8],
) -> ProgramResult {
// Deserialize instruction.
#deserialize_instruction
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
)?;
#state_name::#ix_name(
// Execute user defined function.
#state_name::#ix_method_name(
Context::new(program_id, &mut accounts, remaining_accounts),
#(#ix_arg_names),*
)?;
// Exit procedure.
accounts.exit(program_id)
}
}
@ -528,24 +597,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.ixs
.iter()
.map(|ix| {
let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
let ix_name = &ix.raw_method.sig.ident;
let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
let ix_method_name = &ix.raw_method.sig.ident;
let anchor = &ix.anchor_ident;
let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
quote! {
#[inline(never)]
pub fn #ix_name(
pub fn #ix_method_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#ix_params),*
ix_data: &[u8],
) -> ProgramResult {
// Deserialize data.
let ix = instruction::#ix_name::deserialize(&mut &ix_data[..])
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = ix;
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
#program_name::#ix_name(
let mut accounts = #anchor::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
)?;
// Invoke user defined handler.
#program_name::#ix_method_name(
Context::new(program_id, &mut accounts, remaining_accounts),
#(#ix_arg_names),*
)?;
// Exit routine.
accounts.exit(program_id)
}
}
@ -590,3 +674,28 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}
}
}
fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
let n = name.to_camel_case();
n.parse().unwrap()
}
fn generate_ctor_variant_name() -> String {
"New".to_string()
}
fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
let ctor_args = generate_ctor_args(state);
let ctor_variant_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
if ctor_args.is_empty() {
quote! {
#ctor_variant_name
}
} else {
quote! {
#ctor_variant_name {
#(#ctor_args),*
}
}
}
}

View File

@ -9,6 +9,7 @@ use syn::ext::IdentExt;
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
LitStr, PatType, Token,
@ -99,6 +100,8 @@ pub struct AccountsStruct {
pub generics: Generics,
// Fields on the accounts struct.
pub fields: Vec<AccountField>,
// Instruction data api expression.
instruction_api: Option<Punctuated<Expr, Comma>>,
}
impl Parse for AccountsStruct {
@ -121,13 +124,18 @@ impl ToTokens for AccountsStruct {
}
impl AccountsStruct {
pub fn new(strct: ItemStruct, fields: Vec<AccountField>) -> Self {
pub fn new(
strct: ItemStruct,
fields: Vec<AccountField>,
instruction_api: Option<Punctuated<Expr, Comma>>,
) -> Self {
let ident = strct.ident.clone();
let generics = strct.generics;
Self {
ident,
generics,
fields,
instruction_api,
}
}
}
@ -142,6 +150,7 @@ pub enum AccountField {
pub struct Field {
pub ident: Ident,
pub constraints: ConstraintGroup,
pub instruction_constraints: ConstraintGroup,
pub ty: Ty,
}
@ -149,6 +158,7 @@ pub struct Field {
pub struct CompositeField {
pub ident: Ident,
pub constraints: ConstraintGroup,
pub instruction_constraints: ConstraintGroup,
pub symbol: String,
pub raw_field: syn::Field,
}
@ -250,7 +260,7 @@ pub struct ConstraintGroup {
signer: Option<ConstraintSigner>,
owner: Option<ConstraintOwner>,
rent_exempt: Option<ConstraintRentExempt>,
seeds: Option<ConstraintSeeds>,
seeds: Option<ConstraintSeedsGroup>,
executable: Option<ConstraintExecutable>,
state: Option<ConstraintState>,
associated: Option<ConstraintAssociatedGroup>,
@ -291,7 +301,7 @@ pub enum Constraint {
Raw(ConstraintRaw),
Owner(ConstraintOwner),
RentExempt(ConstraintRentExempt),
Seeds(ConstraintSeeds),
Seeds(ConstraintSeedsGroup),
Executable(ConstraintExecutable),
State(ConstraintState),
AssociatedGroup(ConstraintAssociatedGroup),
@ -360,6 +370,14 @@ pub enum ConstraintRentExempt {
Skip,
}
#[derive(Debug, Clone)]
pub struct ConstraintSeedsGroup {
pub is_init: bool,
pub seeds: Punctuated<Expr, Token![,]>,
pub payer: Option<Ident>,
pub space: Option<LitInt>,
}
#[derive(Debug, Clone)]
pub struct ConstraintSeeds {
pub seeds: Punctuated<Expr, Token![,]>,
@ -382,22 +400,22 @@ pub struct ConstraintAssociatedGroup {
pub space: Option<LitInt>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ConstraintAssociated {
pub target: Ident,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ConstraintAssociatedPayer {
pub target: Ident,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ConstraintAssociatedWith {
pub target: Ident,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ConstraintAssociatedSpace {
pub space: LitInt,
}

View File

@ -2,8 +2,8 @@ use crate::{
ConstraintAssociated, ConstraintAssociatedGroup, ConstraintAssociatedPayer,
ConstraintAssociatedSpace, ConstraintAssociatedWith, ConstraintBelongsTo, ConstraintClose,
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
ConstraintState, ConstraintToken, Context, Ty,
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSeedsGroup,
ConstraintSigner, ConstraintState, ConstraintToken, Context, Ty,
};
use syn::ext::IdentExt;
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
@ -12,14 +12,34 @@ use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{bracketed, Expr, Ident, LitStr, Token};
pub fn parse(f: &syn::Field, f_ty: Option<&Ty>) -> ParseResult<ConstraintGroup> {
pub fn parse(
f: &syn::Field,
f_ty: Option<&Ty>,
has_instruction_api: bool,
) -> ParseResult<(ConstraintGroup, ConstraintGroup)> {
let mut constraints = ConstraintGroupBuilder::new(f_ty);
for attr in f.attrs.iter().filter(is_account) {
for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
constraints.add(c)?;
}
}
constraints.build()
let account_constraints = constraints.build()?;
let mut constraints = ConstraintGroupBuilder::new(f_ty);
for attr in f.attrs.iter().filter(is_instruction) {
if !has_instruction_api {
return Err(ParseError::new(
attr.span(),
"an instruction api must be declared",
));
}
for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
constraints.add(c)?;
}
}
let instruction_constraints = constraints.build()?;
Ok((account_constraints, instruction_constraints))
}
pub fn is_account(attr: &&syn::Attribute) -> bool {
@ -28,6 +48,12 @@ pub fn is_account(attr: &&syn::Attribute) -> bool {
.map_or(false, |ident| ident == "account")
}
pub fn is_instruction(attr: &&syn::Attribute) -> bool {
attr.path
.get_ident()
.map_or(false, |ident| ident == "instruction")
}
// Parses a single constraint from a parse stream for `#[account(<STREAM>)]`.
pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
let is_lit = stream.peek(LitStr);
@ -198,6 +224,14 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
.replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
}
}
if let Some(i) = &self.seeds {
if self.init.is_some() && self.associated_payer.is_none() {
return Err(ParseError::new(
i.span(),
"payer must be provided when creating a program derived address",
));
}
}
let ConstraintGroupBuilder {
f_ty: _,
@ -224,6 +258,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
($opt:ident) => {
$opt.map(|c| c.into_inner())
};
($opt:expr) => {
$opt.map(|c| c.into_inner())
};
}
// Converts Vec<Context<T>> - Vec<T>.
macro_rules! into_inner_vec {
@ -242,7 +279,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
raw: into_inner_vec!(raw),
owner: into_inner!(owner),
rent_exempt: into_inner!(rent_exempt),
seeds: into_inner!(seeds),
seeds: seeds.map(|c| ConstraintSeedsGroup {
is_init,
seeds: c.into_inner().seeds,
payer: into_inner!(associated_payer.clone()).map(|a| a.target),
space: associated_space.clone().map(|s| s.space.clone()),
}),
executable: into_inner!(executable),
state: into_inner!(state),
associated: associated.map(|associated| ConstraintAssociatedGroup {
@ -370,6 +412,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.seeds.is_some() {
return Err(ParseError::new(c.span(), "seeds already provided"));
}
if self.associated.is_some() {
return Err(ParseError::new(
c.span(),
"both seeds and associated cannot be defined together",
));
}
self.seeds.replace(c);
Ok(())
}
@ -394,15 +442,21 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.associated.is_some() {
return Err(ParseError::new(c.span(), "associated already provided"));
}
if self.seeds.is_some() {
return Err(ParseError::new(
c.span(),
"both seeds and associated cannot be defined together",
));
}
self.associated.replace(c);
Ok(())
}
fn add_associated_payer(&mut self, c: Context<ConstraintAssociatedPayer>) -> ParseResult<()> {
if self.associated.is_none() {
if self.associated.is_none() && self.seeds.is_none() {
return Err(ParseError::new(
c.span(),
"associated must be provided before payer",
"associated or seeds must be provided before payer",
));
}
if self.associated_payer.is_some() {
@ -413,10 +467,10 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
fn add_associated_space(&mut self, c: Context<ConstraintAssociatedSpace>) -> ParseResult<()> {
if self.associated.is_none() {
if self.associated.is_none() && self.seeds.is_none() {
return Err(ParseError::new(
c.span(),
"associated must be provided before space",
"associated or seeds must be provided before space",
));
}
if self.associated_space.is_some() {

View File

@ -3,16 +3,30 @@ use crate::{
ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
};
use syn::parse::{Error as ParseError, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::Expr;
pub mod constraints;
pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
let instruction_api: Option<Punctuated<Expr, Comma>> = strct
.attrs
.iter()
.filter(|a| {
a.path
.get_ident()
.map_or(false, |ident| ident == "instruction")
})
.next()
.map(|ix_attr| ix_attr.parse_args_with(Punctuated::<Expr, Comma>::parse_terminated))
.transpose()?;
let fields = match &strct.fields {
syn::Fields::Named(fields) => fields
.named
.iter()
.map(parse_account_field)
.map(|f| parse_account_field(f, instruction_api.is_some()))
.collect::<ParseResult<Vec<AccountField>>>()?,
_ => {
return Err(ParseError::new_spanned(
@ -21,26 +35,30 @@ pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
))
}
};
Ok(AccountsStruct::new(strct.clone(), fields))
Ok(AccountsStruct::new(strct.clone(), fields, instruction_api))
}
pub fn parse_account_field(f: &syn::Field) -> ParseResult<AccountField> {
pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseResult<AccountField> {
let ident = f.ident.clone().unwrap();
let account_field = match is_field_primitive(f)? {
true => {
let ty = parse_ty(f)?;
let constraints = constraints::parse(f, Some(&ty))?;
let (account_constraints, instruction_constraints) =
constraints::parse(f, Some(&ty), has_instruction_api)?;
AccountField::Field(Field {
ident,
ty,
constraints,
constraints: account_constraints,
instruction_constraints,
})
}
false => {
let constraints = constraints::parse(f, None)?;
let (account_constraints, instruction_constraints) =
constraints::parse(f, None, has_instruction_api)?;
AccountField::CompositeField(CompositeField {
ident,
constraints,
constraints: account_constraints,
instruction_constraints,
symbol: ident_string(f)?,
raw_field: f.clone(),
})

View File

@ -18,36 +18,8 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
_ => None,
})
.map(|method: &syn::ItemFn| {
let mut args: Vec<IxArg> = method
.sig
.inputs
.iter()
.map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(arg) => {
let ident = match &*arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => {
return Err(ParseError::new(
arg.pat.span(),
"expected argument name",
))
}
};
Ok(IxArg {
name: ident.clone(),
raw_arg: arg.clone(),
})
}
syn::FnArg::Receiver(_) => Err(ParseError::new(
arg.span(),
"expected a typed argument not self",
)),
})
.collect::<ParseResult<_>>()?;
// Remove the Context argument
let anchor = args.remove(0);
let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
let (ctx, args) = parse_args(method)?;
let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?;
Ok(Ix {
raw_method: method.clone(),
ident: method.sig.ident.clone(),
@ -57,3 +29,32 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
})
.collect::<ParseResult<Vec<Ix>>>()
}
pub fn parse_args(method: &syn::ItemFn) -> ParseResult<(IxArg, Vec<IxArg>)> {
let mut args: Vec<IxArg> = method
.sig
.inputs
.iter()
.map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(arg) => {
let ident = match &*arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => return Err(ParseError::new(arg.pat.span(), "expected argument name")),
};
Ok(IxArg {
name: ident.clone(),
raw_arg: arg.clone(),
})
}
syn::FnArg::Receiver(_) => Err(ParseError::new(
arg.span(),
"expected a typed argument not self",
)),
})
.collect::<ParseResult<_>>()?;
// Remove the Context argument
let ctx = args.remove(0);
Ok((ctx, args))
}