lang: Initialize program derived addresses with instruction data (#386)
This commit is contained in:
parent
4ce26d5531
commit
a6ebaabac4
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -49,6 +49,7 @@ pub struct Mint {
|
|||
}
|
||||
|
||||
#[associated]
|
||||
#[derive(Default)]
|
||||
pub struct Token {
|
||||
pub amount: u32,
|
||||
pub authority: Pubkey,
|
||||
|
|
|
@ -175,6 +175,7 @@ pub struct Foo {
|
|||
}
|
||||
|
||||
#[associated(zero_copy)]
|
||||
#[derive(Default)]
|
||||
pub struct Bar {
|
||||
pub authority: Pubkey,
|
||||
pub data: u64,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue