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)).
|
* 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)).
|
* 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: 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
|
### Fixes
|
||||||
|
|
||||||
|
@ -32,6 +34,7 @@ incremented for features.
|
||||||
### Breaking
|
### 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, 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
|
## [0.7.0] - 2021-05-31
|
||||||
|
|
||||||
|
|
|
@ -499,8 +499,11 @@ mod registry {
|
||||||
let signer = &[&seeds[..]];
|
let signer = &[&seeds[..]];
|
||||||
let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
|
let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
|
||||||
let cpi_program = ctx.accounts.lockup_program.clone();
|
let cpi_program = ctx.accounts.lockup_program.clone();
|
||||||
let cpi_accounts =
|
let cpi_accounts = CreateVesting::try_accounts(
|
||||||
CreateVesting::try_accounts(ctx.accounts.lockup_program.key, &mut remaining_accounts)?;
|
ctx.accounts.lockup_program.key,
|
||||||
|
&mut remaining_accounts,
|
||||||
|
&[],
|
||||||
|
)?;
|
||||||
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
|
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
|
||||||
lockup::cpi::create_vesting(
|
lockup::cpi::create_vesting(
|
||||||
cpi_ctx,
|
cpi_ctx,
|
||||||
|
|
|
@ -93,6 +93,76 @@ pub mod misc {
|
||||||
pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
|
pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
|
||||||
Ok(())
|
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)]
|
#[derive(Accounts)]
|
||||||
|
@ -194,6 +264,7 @@ pub struct TestI8<'info> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[associated]
|
#[associated]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct TestData {
|
pub struct TestData {
|
||||||
data: u64,
|
data: u64,
|
||||||
}
|
}
|
||||||
|
@ -205,6 +276,7 @@ pub struct Data {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[account]
|
#[account]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct DataU16 {
|
pub struct DataU16 {
|
||||||
data: u16,
|
data: u16,
|
||||||
}
|
}
|
||||||
|
@ -219,6 +291,13 @@ pub struct DataI16 {
|
||||||
data: i16,
|
data: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[account(zero_copy)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DataZeroCopy {
|
||||||
|
data: u16,
|
||||||
|
bump: u8,
|
||||||
|
}
|
||||||
|
|
||||||
#[event]
|
#[event]
|
||||||
pub struct E1 {
|
pub struct E1 {
|
||||||
data: u32,
|
data: u32,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const anchor = require("@project-serum/anchor");
|
const anchor = require("@project-serum/anchor");
|
||||||
|
const PublicKey = anchor.web3.PublicKey;
|
||||||
const serumCmn = require("@project-serum/common");
|
const serumCmn = require("@project-serum/common");
|
||||||
const assert = require("assert");
|
const assert = require("assert");
|
||||||
|
|
||||||
|
@ -321,4 +322,84 @@ describe("misc", () => {
|
||||||
);
|
);
|
||||||
assert.ok(closedAccount === null);
|
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]
|
#[associated]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
pub amount: u32,
|
pub amount: u32,
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
|
|
|
@ -175,6 +175,7 @@ pub struct Foo {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[associated(zero_copy)]
|
#[associated(zero_copy)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Bar {
|
pub struct Bar {
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
pub data: u64,
|
pub data: u64,
|
||||||
|
|
|
@ -238,7 +238,6 @@ pub fn associated(
|
||||||
let args: proc_macro2::TokenStream = args.into();
|
let args: proc_macro2::TokenStream = args.into();
|
||||||
proc_macro::TokenStream::from(quote! {
|
proc_macro::TokenStream::from(quote! {
|
||||||
#[anchor_lang::account(#args)]
|
#[anchor_lang::account(#args)]
|
||||||
#[derive(Default)]
|
|
||||||
#account_strct
|
#account_strct
|
||||||
|
|
||||||
impl anchor_lang::Bump for #account_name {
|
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. |
|
/// | `#[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
|
// TODO: How do we make the markdown render correctly without putting everything
|
||||||
// on absurdly long lines?
|
// 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 {
|
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||||
parse_macro_input!(item as anchor_syn::AccountsStruct)
|
parse_macro_input!(item as anchor_syn::AccountsStruct)
|
||||||
.to_token_stream()
|
.to_token_stream()
|
||||||
|
|
|
@ -10,6 +10,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -10,8 +10,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> 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(
|
fn try_accounts(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -66,6 +66,7 @@ where
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError>;
|
) -> Result<Self, ProgramError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -71,6 +71,7 @@ where
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -58,6 +58,7 @@ where
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -37,6 +37,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &mut &[AccountInfo<'info>],
|
accounts: &mut &[AccountInfo<'info>],
|
||||||
|
_ix_data: &[u8],
|
||||||
) -> Result<Self, ProgramError> {
|
) -> Result<Self, ProgramError> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
|
CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
|
||||||
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
|
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
|
||||||
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
|
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup, ConstraintSigner,
|
||||||
ConstraintState, Field, Ty,
|
ConstraintState, Field, Ty,
|
||||||
};
|
};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::LitInt;
|
||||||
|
|
||||||
pub fn generate(f: &Field) -> proc_macro2::TokenStream {
|
pub fn generate(f: &Field) -> proc_macro2::TokenStream {
|
||||||
let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
|
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 {
|
if let Some(c) = associated {
|
||||||
constraints.push(Constraint::AssociatedGroup(c));
|
constraints.push(Constraint::AssociatedGroup(c));
|
||||||
}
|
}
|
||||||
|
if let Some(c) = seeds {
|
||||||
|
constraints.push(Constraint::Seeds(c));
|
||||||
|
}
|
||||||
if let Some(c) = init {
|
if let Some(c) = init {
|
||||||
constraints.push(Constraint::Init(c));
|
constraints.push(Constraint::Init(c));
|
||||||
}
|
}
|
||||||
|
@ -86,9 +90,6 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
|
||||||
if let Some(c) = rent_exempt {
|
if let Some(c) = rent_exempt {
|
||||||
constraints.push(Constraint::RentExempt(c));
|
constraints.push(Constraint::RentExempt(c));
|
||||||
}
|
}
|
||||||
if let Some(c) = seeds {
|
|
||||||
constraints.push(Constraint::Seeds(c));
|
|
||||||
}
|
|
||||||
if let Some(c) = executable {
|
if let Some(c) = executable {
|
||||||
constraints.push(Constraint::Executable(c));
|
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 name = &f.ident;
|
||||||
let seeds = &c.seeds;
|
let seeds = &c.seeds;
|
||||||
quote! {
|
quote! {
|
||||||
let program_signer = Pubkey::create_program_address(
|
let __program_signer = Pubkey::create_program_address(
|
||||||
&[#seeds],
|
&[#seeds],
|
||||||
program_id,
|
program_id,
|
||||||
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
|
).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());
|
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(
|
pub fn generate_constraint_executable(
|
||||||
f: &Field,
|
f: &Field,
|
||||||
_c: &ConstraintExecutable,
|
_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.
|
// Returns the inner part of the seeds slice as a token stream.
|
||||||
fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
|
fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
|
||||||
assert!(seeds.len() > 0);
|
assert!(seeds.len() > 0);
|
||||||
|
|
|
@ -2,19 +2,13 @@ use crate::codegen::accounts::{constraints, generics};
|
||||||
use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
|
use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::Expr;
|
||||||
|
|
||||||
// Generates the `Accounts` trait implementation.
|
// Generates the `Accounts` trait implementation.
|
||||||
pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
let name = &accs.ident;
|
let name = &accs.ident;
|
||||||
let (combined_generics, trait_generics, strct_generics) = generics(accs);
|
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
|
// Deserialization for each field
|
||||||
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
let deser_fields: Vec<proc_macro2::TokenStream> = accs
|
||||||
.fields
|
.fields
|
||||||
|
@ -27,14 +21,14 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
#[cfg(feature = "anchor-debug")]
|
#[cfg(feature = "anchor-debug")]
|
||||||
::solana_program::log::sol_log(stringify!(#name));
|
::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) => {
|
AccountField::Field(f) => {
|
||||||
// Associated fields are *first* deserialized into
|
// Associated fields are *first* deserialized into
|
||||||
// AccountInfos, and then later deserialized into
|
// AccountInfos, and then later deserialized into
|
||||||
// ProgramAccounts in the "constraint check" phase.
|
// ProgramAccounts in the "constraint check" phase.
|
||||||
if is_associated_init(af) {
|
if is_pda_init(af) {
|
||||||
let name = &f.ident;
|
let name = &f.ident;
|
||||||
quote!{
|
quote!{
|
||||||
let #name = &accounts[0];
|
let #name = &accounts[0];
|
||||||
|
@ -46,7 +40,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
false => quote! {
|
false => quote! {
|
||||||
#[cfg(feature = "anchor-debug")]
|
#[cfg(feature = "anchor-debug")]
|
||||||
::solana_program::log::sol_log(stringify!(#name));
|
::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! {
|
true => quote! {
|
||||||
#[cfg(feature = "anchor-debug")]
|
#[cfg(feature = "anchor-debug")]
|
||||||
|
@ -60,81 +54,76 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Deserialization for each *associated* field. This must be after
|
let constraints = generate_constraints(accs);
|
||||||
// the deser_fields.
|
let accounts_instance = generate_accounts_instance(accs);
|
||||||
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();
|
|
||||||
|
|
||||||
// Constraint checks for each account fields.
|
let ix_de = match &accs.instruction_api {
|
||||||
let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
|
None => quote! {},
|
||||||
.iter()
|
Some(ix_api) => {
|
||||||
.map(|af: &&AccountField| match af {
|
let strct_inner = &ix_api;
|
||||||
AccountField::Field(f) => constraints::generate(f),
|
let field_names: Vec<proc_macro2::TokenStream> = ix_api
|
||||||
AccountField::CompositeField(s) => constraints::generate_composite(s),
|
.iter()
|
||||||
})
|
.map(|expr: &Expr| match expr {
|
||||||
.collect();
|
Expr::Type(expr_type) => {
|
||||||
|
let field = &expr_type.expr;
|
||||||
// Each field in the final deserialized accounts struct.
|
quote! {
|
||||||
let return_tys: Vec<proc_macro2::TokenStream> = accs
|
#field
|
||||||
.fields
|
}
|
||||||
.iter()
|
}
|
||||||
.map(|f: &AccountField| {
|
_ => panic!("Invalid instruction declaration"),
|
||||||
let name = match f {
|
})
|
||||||
AccountField::CompositeField(s) => &s.ident,
|
.collect();
|
||||||
AccountField::Field(f) => &f.ident,
|
|
||||||
};
|
|
||||||
quote! {
|
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! {
|
quote! {
|
||||||
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn try_accounts(
|
fn try_accounts(
|
||||||
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
|
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
|
||||||
accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
|
accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
|
||||||
|
ix_data: &[u8],
|
||||||
) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
|
||||||
|
// Deserialize instruction, if declared.
|
||||||
|
#ix_de
|
||||||
// Deserialize each account.
|
// Deserialize each account.
|
||||||
#(#deser_fields)*
|
#(#deser_fields)*
|
||||||
// Deserialize each associated account.
|
// Execute accounts constraints.
|
||||||
//
|
#constraints
|
||||||
// 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)*
|
|
||||||
// Success. Return the validated accounts.
|
// Success. Return the validated accounts.
|
||||||
Ok(#name {
|
Ok(#accounts_instance)
|
||||||
#(#return_tys),*
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the given AccountField has an associated init constraint.
|
// 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 {
|
match af {
|
||||||
AccountField::CompositeField(_s) => false,
|
AccountField::CompositeField(_s) => false,
|
||||||
AccountField::Field(f) => f
|
AccountField::Field(f) => {
|
||||||
.constraints
|
f.constraints
|
||||||
.associated
|
.associated
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| f.is_init)
|
.map(|f| f.is_init)
|
||||||
.unwrap_or(false),
|
.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
|
#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::codegen::program::common::*;
|
||||||
use crate::{Program, State};
|
use crate::Program;
|
||||||
use heck::CamelCase;
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
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() {
|
Some(state) => match state.ctor_and_anchor.is_some() {
|
||||||
false => quote! {},
|
false => quote! {},
|
||||||
true => {
|
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_arr = sighash_ctor();
|
||||||
let sighash_tts: proc_macro2::TokenStream =
|
let sighash_tts: proc_macro2::TokenStream =
|
||||||
format!("{:?}", sighash_arr).parse().unwrap();
|
format!("{:?}", sighash_arr).parse().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
#sighash_tts => {
|
#sighash_tts => {
|
||||||
let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
|
__private::__state::__ctor(
|
||||||
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
|
program_id,
|
||||||
let instruction::state::#variant_arm = ix;
|
accounts,
|
||||||
__private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
|
ix_data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,23 +35,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
methods
|
methods
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix: &crate::StateIx| {
|
.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 name = &ix.raw_method.sig.ident.to_string();
|
||||||
let ix_method_name: proc_macro2::TokenStream =
|
let ix_method_name: proc_macro2::TokenStream =
|
||||||
{ format!("__{}", name).parse().unwrap() };
|
{ 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());
|
|
||||||
let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
|
let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
|
||||||
let sighash_tts: proc_macro2::TokenStream =
|
let sighash_tts: proc_macro2::TokenStream =
|
||||||
format!("{:?}", sighash_arr).parse().unwrap();
|
format!("{:?}", sighash_arr).parse().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
#sighash_tts => {
|
#sighash_tts => {
|
||||||
let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
|
__private::__state::#ix_method_name(
|
||||||
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
|
program_id,
|
||||||
let instruction::state::#variant_arm = ix;
|
accounts,
|
||||||
__private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
|
ix_data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -78,42 +70,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
.methods
|
.methods
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m: &crate::StateIx| {
|
.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_arr = sighash(&iface.trait_name, &m.ident.to_string());
|
||||||
let sighash_tts: proc_macro2::TokenStream =
|
let sighash_tts: proc_macro2::TokenStream =
|
||||||
format!("{:?}", sighash_arr).parse().unwrap();
|
format!("{:?}", sighash_arr).parse().unwrap();
|
||||||
let args_struct = {
|
let name = &m.raw_method.sig.ident.to_string();
|
||||||
if m.args.is_empty() {
|
let ix_method_name: proc_macro2::TokenStream =
|
||||||
quote! {
|
format!("__{}_{}", iface.trait_name, name).parse().unwrap();
|
||||||
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
|
|
||||||
struct Args;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
|
|
||||||
struct Args {
|
|
||||||
#(#raw_args),*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
quote! {
|
quote! {
|
||||||
#sighash_tts => {
|
#sighash_tts => {
|
||||||
#args_struct
|
__private::__interface::#ix_method_name(
|
||||||
let ix = Args::deserialize(&mut ix_data)
|
program_id,
|
||||||
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
|
accounts,
|
||||||
let Args {
|
ix_data,
|
||||||
#(#ix_arg_names),*
|
)
|
||||||
} = ix;
|
|
||||||
__private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -121,7 +90,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dispatch all global instructions.
|
// Dispatch all global instructions.
|
||||||
|
@ -129,19 +98,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
.ixs
|
.ixs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix| {
|
.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_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_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &ix_method_name.to_string());
|
||||||
let sighash_tts: proc_macro2::TokenStream =
|
let sighash_tts: proc_macro2::TokenStream =
|
||||||
format!("{:?}", sighash_arr).parse().unwrap();
|
format!("{:?}", sighash_arr).parse().unwrap();
|
||||||
let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
|
|
||||||
quote! {
|
quote! {
|
||||||
#sighash_tts => {
|
#sighash_tts => {
|
||||||
let ix = instruction::#ix_name::deserialize(&mut ix_data)
|
__private::__global::#ix_method_name(
|
||||||
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
|
program_id,
|
||||||
let instruction::#variant_arm = ix;
|
accounts,
|
||||||
__private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
|
ix_data,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -171,7 +138,11 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
// instruction, injected into all Anchor programs.
|
// instruction, injected into all Anchor programs.
|
||||||
if cfg!(not(feature = "no-idl")) {
|
if cfg!(not(feature = "no-idl")) {
|
||||||
if sighash == anchor_lang::idl::IDL_IX_TAG.to_le_bytes() {
|
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::codegen::program::common::*;
|
||||||
use crate::Program;
|
use crate::{Program, State};
|
||||||
|
use heck::CamelCase;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
// Generate non-inlined wrappers for each instruction handler, since Solana's
|
// 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 {
|
match ix {
|
||||||
anchor_lang::idl::IdlInstruction::Create { data_len } => {
|
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)?;
|
__idl_create_account(program_id, &mut accounts, data_len)?;
|
||||||
accounts.exit(program_id)?;
|
accounts.exit(program_id)?;
|
||||||
},
|
},
|
||||||
anchor_lang::idl::IdlInstruction::CreateBuffer => {
|
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)?;
|
__idl_create_buffer(program_id, &mut accounts)?;
|
||||||
accounts.exit(program_id)?;
|
accounts.exit(program_id)?;
|
||||||
},
|
},
|
||||||
anchor_lang::idl::IdlInstruction::Write { data } => {
|
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)?;
|
__idl_write(program_id, &mut accounts, data)?;
|
||||||
accounts.exit(program_id)?;
|
accounts.exit(program_id)?;
|
||||||
},
|
},
|
||||||
anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
|
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)?;
|
__idl_set_authority(program_id, &mut accounts, new_authority)?;
|
||||||
accounts.exit(program_id)?;
|
accounts.exit(program_id)?;
|
||||||
},
|
},
|
||||||
anchor_lang::idl::IdlInstruction::SetBuffer => {
|
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)?;
|
__idl_set_buffer(program_id, &mut accounts)?;
|
||||||
accounts.exit(program_id)?;
|
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() {
|
Some(state) => match state.ctor_and_anchor.as_ref() {
|
||||||
None => quote! {},
|
None => quote! {},
|
||||||
Some((_ctor, anchor_ident)) => {
|
Some((_ctor, anchor_ident)) => {
|
||||||
let ctor_typed_args = generate_ctor_typed_args(state);
|
|
||||||
let ctor_untyped_args = generate_ctor_args(state);
|
let ctor_untyped_args = generate_ctor_args(state);
|
||||||
let name = &state.strct.ident;
|
let name = &state.strct.ident;
|
||||||
let mod_name = &program.name;
|
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 {
|
if state.is_zero_copy {
|
||||||
quote! {
|
quote! {
|
||||||
// One time state account initializer. Will faill on subsequent
|
// One time state account initializer. Will faill on subsequent
|
||||||
// invocations.
|
// invocations.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
|
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
|
||||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
// 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.
|
// Deserialize accounts.
|
||||||
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_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.
|
// Create the solana account for the ctor data.
|
||||||
let from = ctor_accounts.from.key;
|
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
|
// One time state account initializer. Will faill on subsequent
|
||||||
// invocations.
|
// invocations.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
|
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
|
||||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
// 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.
|
// Deserialize accounts.
|
||||||
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_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.
|
// Invoke the ctor.
|
||||||
let instance = #mod_name::#name::new(
|
let instance = #mod_name::#name::new(
|
||||||
|
@ -313,46 +329,56 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
methods
|
methods
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix| {
|
.map(|ix| {
|
||||||
let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
|
|
||||||
let ix_arg_names: Vec<&syn::Ident> =
|
let ix_arg_names: Vec<&syn::Ident> =
|
||||||
ix.args.iter().map(|arg| &arg.name).collect();
|
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());
|
let n = format!("__{}", &ix.raw_method.sig.ident.to_string());
|
||||||
n.parse().unwrap()
|
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 state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
|
||||||
let anchor_ident = &ix.anchor_ident;
|
let anchor_ident = &ix.anchor_ident;
|
||||||
let name = &state.strct.ident;
|
let name = &state.strct.ident;
|
||||||
let mod_name = &program.name;
|
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 {
|
if state.is_zero_copy {
|
||||||
quote! {
|
quote! {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn #private_ix_name(
|
pub fn #private_ix_method_name(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
#(#ix_params),*
|
ix_data: &[u8],
|
||||||
) -> ProgramResult {
|
) -> 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;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
if remaining_accounts.is_empty() {
|
if remaining_accounts.is_empty() {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let state_account = &remaining_accounts[0];
|
let state_account = &remaining_accounts[0];
|
||||||
let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from(&state_account)?;
|
let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from(&state_account)?;
|
||||||
remaining_accounts = &remaining_accounts[1..];
|
remaining_accounts = &remaining_accounts[1..];
|
||||||
|
|
||||||
// Deserialize the program's execution context.
|
// Deserialize accounts.
|
||||||
let mut accounts = #anchor_ident::try_accounts(
|
let mut accounts = #anchor_ident::try_accounts(
|
||||||
program_id,
|
program_id,
|
||||||
&mut remaining_accounts,
|
&mut remaining_accounts,
|
||||||
|
ix_data,
|
||||||
)?;
|
)?;
|
||||||
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
||||||
|
|
||||||
// Execute user defined function.
|
// Execute user defined function.
|
||||||
{
|
{
|
||||||
let mut state = loader.load_mut()?;
|
let mut state = loader.load_mut()?;
|
||||||
state.#ix_name(
|
state.#ix_method_name(
|
||||||
ctx,
|
ctx,
|
||||||
#(#ix_arg_names),*
|
#(#ix_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
|
@ -367,35 +393,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn #private_ix_name(
|
pub fn #private_ix_method_name(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
#(#ix_params),*
|
ix_data: &[u8],
|
||||||
) -> ProgramResult {
|
) -> 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;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
if remaining_accounts.is_empty() {
|
if remaining_accounts.is_empty() {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the program state account.
|
|
||||||
let state_account = &remaining_accounts[0];
|
let state_account = &remaining_accounts[0];
|
||||||
let mut state: #state_ty = {
|
let mut state: #state_ty = {
|
||||||
let data = state_account.try_borrow_data()?;
|
let data = state_account.try_borrow_data()?;
|
||||||
let mut sliced: &[u8] = &data;
|
let mut sliced: &[u8] = &data;
|
||||||
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
|
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
|
||||||
};
|
};
|
||||||
|
|
||||||
remaining_accounts = &remaining_accounts[1..];
|
remaining_accounts = &remaining_accounts[1..];
|
||||||
|
|
||||||
// Deserialize the program's execution context.
|
// Deserialize accounts.
|
||||||
let mut accounts = #anchor_ident::try_accounts(
|
let mut accounts = #anchor_ident::try_accounts(
|
||||||
program_id,
|
program_id,
|
||||||
&mut remaining_accounts,
|
&mut remaining_accounts,
|
||||||
|
ix_data,
|
||||||
)?;
|
)?;
|
||||||
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
||||||
|
|
||||||
// Execute user defined function.
|
// Execute user defined function.
|
||||||
state.#ix_name(
|
state.#ix_method_name(
|
||||||
ctx,
|
ctx,
|
||||||
#(#ix_arg_names),*
|
#(#ix_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
|
@ -431,61 +461,91 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
.methods
|
.methods
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix| {
|
.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 {
|
if state.is_zero_copy {
|
||||||
// Easy to implement. Just need to write a test.
|
// Easy to implement. Just need to write a test.
|
||||||
// Feel free to open a PR.
|
// Feel free to open a PR.
|
||||||
panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
|
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 {
|
if ix.has_receiver {
|
||||||
quote! {
|
quote! {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn #private_ix_name(
|
pub fn #private_ix_method_name(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
#(#ix_params),*
|
ix_data: &[u8],
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
|
// Deserialize instruction.
|
||||||
|
#deserialize_instruction
|
||||||
|
|
||||||
|
// Deserialize the program state account.
|
||||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
if remaining_accounts.is_empty() {
|
if remaining_accounts.is_empty() {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the program state account.
|
|
||||||
let state_account = &remaining_accounts[0];
|
let state_account = &remaining_accounts[0];
|
||||||
let mut state: #state_ty = {
|
let mut state: #state_ty = {
|
||||||
let data = state_account.try_borrow_data()?;
|
let data = state_account.try_borrow_data()?;
|
||||||
let mut sliced: &[u8] = &data;
|
let mut sliced: &[u8] = &data;
|
||||||
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
|
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
|
||||||
};
|
};
|
||||||
|
|
||||||
remaining_accounts = &remaining_accounts[1..];
|
remaining_accounts = &remaining_accounts[1..];
|
||||||
|
|
||||||
// Deserialize the program's execution context.
|
// Deserialize accounts.
|
||||||
let mut accounts = #anchor_ident::try_accounts(
|
let mut accounts = #anchor_ident::try_accounts(
|
||||||
program_id,
|
program_id,
|
||||||
&mut remaining_accounts,
|
&mut remaining_accounts,
|
||||||
|
ix_data,
|
||||||
)?;
|
)?;
|
||||||
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
|
||||||
|
|
||||||
// Execute user defined function.
|
// Execute user defined function.
|
||||||
state.#ix_name(
|
state.#ix_method_name(
|
||||||
ctx,
|
ctx,
|
||||||
#(#ix_arg_names),*
|
#(#ix_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Serialize the state and save it to storage.
|
// Exit procedures.
|
||||||
accounts.exit(program_id)?;
|
accounts.exit(program_id)?;
|
||||||
let mut data = state_account.try_borrow_mut_data()?;
|
let mut data = state_account.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &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();
|
let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
|
||||||
quote! {
|
quote! {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn #private_ix_name(
|
pub fn #private_ix_method_name(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
#(#ix_params),*
|
ix_data: &[u8],
|
||||||
) -> ProgramResult {
|
) -> ProgramResult {
|
||||||
|
// Deserialize instruction.
|
||||||
|
#deserialize_instruction
|
||||||
|
|
||||||
|
// Deserialize accounts.
|
||||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||||
let mut accounts = #anchor_ident::try_accounts(
|
let mut accounts = #anchor_ident::try_accounts(
|
||||||
program_id,
|
program_id,
|
||||||
&mut remaining_accounts,
|
&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),
|
Context::new(program_id, &mut accounts, remaining_accounts),
|
||||||
#(#ix_arg_names),*
|
#(#ix_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Exit procedure.
|
||||||
accounts.exit(program_id)
|
accounts.exit(program_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -528,24 +597,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
.ixs
|
.ixs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix| {
|
.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_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 anchor = &ix.anchor_ident;
|
||||||
|
let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn #ix_name(
|
pub fn #ix_method_name(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
#(#ix_params),*
|
ix_data: &[u8],
|
||||||
) -> ProgramResult {
|
) -> 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 remaining_accounts: &[AccountInfo] = accounts;
|
||||||
let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
|
let mut accounts = #anchor::try_accounts(
|
||||||
#program_name::#ix_name(
|
program_id,
|
||||||
|
&mut remaining_accounts,
|
||||||
|
ix_data,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Invoke user defined handler.
|
||||||
|
#program_name::#ix_method_name(
|
||||||
Context::new(program_id, &mut accounts, remaining_accounts),
|
Context::new(program_id, &mut accounts, remaining_accounts),
|
||||||
#(#ix_arg_names),*
|
#(#ix_arg_names),*
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Exit routine.
|
||||||
accounts.exit(program_id)
|
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::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::Comma;
|
||||||
use syn::{
|
use syn::{
|
||||||
Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
|
Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
|
||||||
LitStr, PatType, Token,
|
LitStr, PatType, Token,
|
||||||
|
@ -99,6 +100,8 @@ pub struct AccountsStruct {
|
||||||
pub generics: Generics,
|
pub generics: Generics,
|
||||||
// Fields on the accounts struct.
|
// Fields on the accounts struct.
|
||||||
pub fields: Vec<AccountField>,
|
pub fields: Vec<AccountField>,
|
||||||
|
// Instruction data api expression.
|
||||||
|
instruction_api: Option<Punctuated<Expr, Comma>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for AccountsStruct {
|
impl Parse for AccountsStruct {
|
||||||
|
@ -121,13 +124,18 @@ impl ToTokens for AccountsStruct {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 ident = strct.ident.clone();
|
||||||
let generics = strct.generics;
|
let generics = strct.generics;
|
||||||
Self {
|
Self {
|
||||||
ident,
|
ident,
|
||||||
generics,
|
generics,
|
||||||
fields,
|
fields,
|
||||||
|
instruction_api,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +150,7 @@ pub enum AccountField {
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub ident: Ident,
|
pub ident: Ident,
|
||||||
pub constraints: ConstraintGroup,
|
pub constraints: ConstraintGroup,
|
||||||
|
pub instruction_constraints: ConstraintGroup,
|
||||||
pub ty: Ty,
|
pub ty: Ty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +158,7 @@ pub struct Field {
|
||||||
pub struct CompositeField {
|
pub struct CompositeField {
|
||||||
pub ident: Ident,
|
pub ident: Ident,
|
||||||
pub constraints: ConstraintGroup,
|
pub constraints: ConstraintGroup,
|
||||||
|
pub instruction_constraints: ConstraintGroup,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
pub raw_field: syn::Field,
|
pub raw_field: syn::Field,
|
||||||
}
|
}
|
||||||
|
@ -250,7 +260,7 @@ pub struct ConstraintGroup {
|
||||||
signer: Option<ConstraintSigner>,
|
signer: Option<ConstraintSigner>,
|
||||||
owner: Option<ConstraintOwner>,
|
owner: Option<ConstraintOwner>,
|
||||||
rent_exempt: Option<ConstraintRentExempt>,
|
rent_exempt: Option<ConstraintRentExempt>,
|
||||||
seeds: Option<ConstraintSeeds>,
|
seeds: Option<ConstraintSeedsGroup>,
|
||||||
executable: Option<ConstraintExecutable>,
|
executable: Option<ConstraintExecutable>,
|
||||||
state: Option<ConstraintState>,
|
state: Option<ConstraintState>,
|
||||||
associated: Option<ConstraintAssociatedGroup>,
|
associated: Option<ConstraintAssociatedGroup>,
|
||||||
|
@ -291,7 +301,7 @@ pub enum Constraint {
|
||||||
Raw(ConstraintRaw),
|
Raw(ConstraintRaw),
|
||||||
Owner(ConstraintOwner),
|
Owner(ConstraintOwner),
|
||||||
RentExempt(ConstraintRentExempt),
|
RentExempt(ConstraintRentExempt),
|
||||||
Seeds(ConstraintSeeds),
|
Seeds(ConstraintSeedsGroup),
|
||||||
Executable(ConstraintExecutable),
|
Executable(ConstraintExecutable),
|
||||||
State(ConstraintState),
|
State(ConstraintState),
|
||||||
AssociatedGroup(ConstraintAssociatedGroup),
|
AssociatedGroup(ConstraintAssociatedGroup),
|
||||||
|
@ -360,6 +370,14 @@ pub enum ConstraintRentExempt {
|
||||||
Skip,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintSeeds {
|
pub struct ConstraintSeeds {
|
||||||
pub seeds: Punctuated<Expr, Token![,]>,
|
pub seeds: Punctuated<Expr, Token![,]>,
|
||||||
|
@ -382,22 +400,22 @@ pub struct ConstraintAssociatedGroup {
|
||||||
pub space: Option<LitInt>,
|
pub space: Option<LitInt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintAssociated {
|
pub struct ConstraintAssociated {
|
||||||
pub target: Ident,
|
pub target: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintAssociatedPayer {
|
pub struct ConstraintAssociatedPayer {
|
||||||
pub target: Ident,
|
pub target: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintAssociatedWith {
|
pub struct ConstraintAssociatedWith {
|
||||||
pub target: Ident,
|
pub target: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConstraintAssociatedSpace {
|
pub struct ConstraintAssociatedSpace {
|
||||||
pub space: LitInt,
|
pub space: LitInt,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@ use crate::{
|
||||||
ConstraintAssociated, ConstraintAssociatedGroup, ConstraintAssociatedPayer,
|
ConstraintAssociated, ConstraintAssociatedGroup, ConstraintAssociatedPayer,
|
||||||
ConstraintAssociatedSpace, ConstraintAssociatedWith, ConstraintBelongsTo, ConstraintClose,
|
ConstraintAssociatedSpace, ConstraintAssociatedWith, ConstraintBelongsTo, ConstraintClose,
|
||||||
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
|
ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
|
||||||
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
|
ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSeedsGroup,
|
||||||
ConstraintState, ConstraintToken, Context, Ty,
|
ConstraintSigner, ConstraintState, ConstraintToken, Context, Ty,
|
||||||
};
|
};
|
||||||
use syn::ext::IdentExt;
|
use syn::ext::IdentExt;
|
||||||
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
|
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::token::Comma;
|
||||||
use syn::{bracketed, Expr, Ident, LitStr, Token};
|
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);
|
let mut constraints = ConstraintGroupBuilder::new(f_ty);
|
||||||
for attr in f.attrs.iter().filter(is_account) {
|
for attr in f.attrs.iter().filter(is_account) {
|
||||||
for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
|
for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
|
||||||
constraints.add(c)?;
|
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 {
|
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")
|
.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>)]`.
|
// Parses a single constraint from a parse stream for `#[account(<STREAM>)]`.
|
||||||
pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
||||||
let is_lit = stream.peek(LitStr);
|
let is_lit = stream.peek(LitStr);
|
||||||
|
@ -198,6 +224,14 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
.replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
|
.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 {
|
let ConstraintGroupBuilder {
|
||||||
f_ty: _,
|
f_ty: _,
|
||||||
|
@ -224,6 +258,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
($opt:ident) => {
|
($opt:ident) => {
|
||||||
$opt.map(|c| c.into_inner())
|
$opt.map(|c| c.into_inner())
|
||||||
};
|
};
|
||||||
|
($opt:expr) => {
|
||||||
|
$opt.map(|c| c.into_inner())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// Converts Vec<Context<T>> - Vec<T>.
|
// Converts Vec<Context<T>> - Vec<T>.
|
||||||
macro_rules! into_inner_vec {
|
macro_rules! into_inner_vec {
|
||||||
|
@ -242,7 +279,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
raw: into_inner_vec!(raw),
|
raw: into_inner_vec!(raw),
|
||||||
owner: into_inner!(owner),
|
owner: into_inner!(owner),
|
||||||
rent_exempt: into_inner!(rent_exempt),
|
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),
|
executable: into_inner!(executable),
|
||||||
state: into_inner!(state),
|
state: into_inner!(state),
|
||||||
associated: associated.map(|associated| ConstraintAssociatedGroup {
|
associated: associated.map(|associated| ConstraintAssociatedGroup {
|
||||||
|
@ -370,6 +412,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
if self.seeds.is_some() {
|
if self.seeds.is_some() {
|
||||||
return Err(ParseError::new(c.span(), "seeds already provided"));
|
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);
|
self.seeds.replace(c);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -394,15 +442,21 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
if self.associated.is_some() {
|
if self.associated.is_some() {
|
||||||
return Err(ParseError::new(c.span(), "associated already provided"));
|
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);
|
self.associated.replace(c);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_associated_payer(&mut self, c: Context<ConstraintAssociatedPayer>) -> ParseResult<()> {
|
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(
|
return Err(ParseError::new(
|
||||||
c.span(),
|
c.span(),
|
||||||
"associated must be provided before payer",
|
"associated or seeds must be provided before payer",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.associated_payer.is_some() {
|
if self.associated_payer.is_some() {
|
||||||
|
@ -413,10 +467,10 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_associated_space(&mut self, c: Context<ConstraintAssociatedSpace>) -> ParseResult<()> {
|
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(
|
return Err(ParseError::new(
|
||||||
c.span(),
|
c.span(),
|
||||||
"associated must be provided before space",
|
"associated or seeds must be provided before space",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.associated_space.is_some() {
|
if self.associated_space.is_some() {
|
||||||
|
|
|
@ -3,16 +3,30 @@ use crate::{
|
||||||
ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
|
ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
|
||||||
};
|
};
|
||||||
use syn::parse::{Error as ParseError, Result as ParseResult};
|
use syn::parse::{Error as ParseError, Result as ParseResult};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
|
use syn::token::Comma;
|
||||||
|
use syn::Expr;
|
||||||
|
|
||||||
pub mod constraints;
|
pub mod constraints;
|
||||||
|
|
||||||
pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
|
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 {
|
let fields = match &strct.fields {
|
||||||
syn::Fields::Named(fields) => fields
|
syn::Fields::Named(fields) => fields
|
||||||
.named
|
.named
|
||||||
.iter()
|
.iter()
|
||||||
.map(parse_account_field)
|
.map(|f| parse_account_field(f, instruction_api.is_some()))
|
||||||
.collect::<ParseResult<Vec<AccountField>>>()?,
|
.collect::<ParseResult<Vec<AccountField>>>()?,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ParseError::new_spanned(
|
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 ident = f.ident.clone().unwrap();
|
||||||
let account_field = match is_field_primitive(f)? {
|
let account_field = match is_field_primitive(f)? {
|
||||||
true => {
|
true => {
|
||||||
let ty = parse_ty(f)?;
|
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 {
|
AccountField::Field(Field {
|
||||||
ident,
|
ident,
|
||||||
ty,
|
ty,
|
||||||
constraints,
|
constraints: account_constraints,
|
||||||
|
instruction_constraints,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
let constraints = constraints::parse(f, None)?;
|
let (account_constraints, instruction_constraints) =
|
||||||
|
constraints::parse(f, None, has_instruction_api)?;
|
||||||
AccountField::CompositeField(CompositeField {
|
AccountField::CompositeField(CompositeField {
|
||||||
ident,
|
ident,
|
||||||
constraints,
|
constraints: account_constraints,
|
||||||
|
instruction_constraints,
|
||||||
symbol: ident_string(f)?,
|
symbol: ident_string(f)?,
|
||||||
raw_field: f.clone(),
|
raw_field: f.clone(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,36 +18,8 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map(|method: &syn::ItemFn| {
|
.map(|method: &syn::ItemFn| {
|
||||||
let mut args: Vec<IxArg> = method
|
let (ctx, args) = parse_args(method)?;
|
||||||
.sig
|
let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?;
|
||||||
.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)?;
|
|
||||||
|
|
||||||
Ok(Ix {
|
Ok(Ix {
|
||||||
raw_method: method.clone(),
|
raw_method: method.clone(),
|
||||||
ident: method.sig.ident.clone(),
|
ident: method.sig.ident.clone(),
|
||||||
|
@ -57,3 +29,32 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
|
||||||
})
|
})
|
||||||
.collect::<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