lang: Consistent init constraints (#641)

This commit is contained in:
Armani Ferrante 2021-08-29 14:25:38 -07:00 committed by GitHub
parent 2604a442fd
commit 75c20856e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 487 additions and 408 deletions

View File

@ -14,13 +14,17 @@ incremented for features.
### Features
* lang: Ignore `Unnamed` structs instead of panic ([#605](https://github.com/project-serum/anchor/pull/605)).
* lang: Add constraints for initializing mint accounts as pdas, `#[account(init, seeds = [...], mint::decimals = <expr>, mint::authority = <expr>)]` ([#](https://github.com/project-serum/anchor/pull/562)).
* lang: Add constraints for initializing mint accounts as pdas, `#[account(init, seeds = [...], mint::decimals = <expr>, mint::authority = <expr>)]` ([#562](https://github.com/project-serum/anchor/pull/562)).
### Breaking Changes
* lang: Change `#[account(init, seeds = [...], token = <expr>, authority = <expr>)]` to `#[account(init, token::mint = <expr> token::authority = <expr>)]` ([#](https://github.com/project-serum/anchor/pull/562)).
* lang: `#[associated]` and `#[account(associated = <target>, with = <target>)]` are both removed.
* cli: Removed `anchor launch` command
* lang: Change `#[account(init, seeds = [...], token = <expr>, authority = <expr>)]` to `#[account(init, token::mint = <expr> token::authority = <expr>)]` ([#562](https://github.com/project-serum/anchor/pull/562)).
* lang: `#[associated]` and `#[account(associated = <target>, with = <target>)]` are both removed ([#612](https://github.com/project-serum/anchor/pull/612)).
* cli: Removed `anchor launch` command ([#634](https://github.com/project-serum/anchor/pull/634)).
* lang: `#[account(init)]` now creates the account inside the same instruction to be consistent with initializing PDAs. To maintain the old behavior of `init`, replace it with `#[account(zero)]` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: `bump` must be provided when using the `seeds` constraint. This has been added as an extra safety constraint to ensure that whenever a PDA is initialized via a constraint the bump used is the one created by `Pubkey::find_program_address` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: `try_from_init` has been removed from `Loader`, `ProgramAccount`, and `CpiAccount` and replaced with `try_from_unchecked` ([#641](https://github.com/project-serum/anchor/pull/641)).
* lang: Remove `AccountsInit` trait ([#641](https://github.com/project-serum/anchor/pull/641)).
## [0.13.2] - 2021-08-11
@ -32,7 +36,7 @@ incremented for features.
### Features
* cli: Programs embedded into genesis during tests will produce program logs.
* cli: Programs embedded into genesis during tests will produce program logs ([#594](https://github.com/project-serum/anchor/pull/594)).
### Fixes

View File

@ -86,7 +86,7 @@ pub struct CreateCheck<'info> {
#[account(zero)]
check: ProgramAccount<'info, Check>,
// Check's token vault.
#[account(mut, "&vault.owner == check_signer.key")]
#[account(mut, constraint = &vault.owner == check_signer.key)]
vault: CpiAccount<'info, TokenAccount>,
// Program derived address for the check.
check_signer: AccountInfo<'info>,
@ -94,7 +94,7 @@ pub struct CreateCheck<'info> {
#[account(mut, has_one = owner)]
from: CpiAccount<'info, TokenAccount>,
// Token account the check is made to.
#[account("from.mint == to.mint")]
#[account(constraint = from.mint == to.mint)]
to: CpiAccount<'info, TokenAccount>,
// Owner of the `from` token account.
owner: AccountInfo<'info>,
@ -121,10 +121,10 @@ pub struct CashCheck<'info> {
check: ProgramAccount<'info, Check>,
#[account(mut)]
vault: AccountInfo<'info>,
#[account(seeds = [
check.to_account_info().key.as_ref(),
&[check.nonce],
])]
#[account(
seeds = [check.to_account_info().key.as_ref()],
bump = check.nonce,
)]
check_signer: AccountInfo<'info>,
#[account(mut, has_one = owner)]
to: CpiAccount<'info, TokenAccount>,
@ -139,10 +139,10 @@ pub struct CancelCheck<'info> {
check: ProgramAccount<'info, Check>,
#[account(mut)]
vault: AccountInfo<'info>,
#[account(seeds = [
check.to_account_info().key.as_ref(),
&[check.nonce],
])]
#[account(
seeds = [check.to_account_info().key.as_ref()],
bump = check.nonce,
)]
check_signer: AccountInfo<'info>,
#[account(mut, has_one = owner)]
from: CpiAccount<'info, TokenAccount>,

@ -1 +1 @@
Subproject commit 3dc83f47b66a8a4189a637368c49dca1341c6b23
Subproject commit 9a257678dfd0bda0c222e516e8c1a778b401d71e

View File

@ -383,7 +383,10 @@ pub struct SetDistribution<'info> {
#[derive(Accounts)]
pub struct SweepFees<'info> {
#[account(seeds = [dex.dex_program.key.as_ref(), &[officer.bumps.bump]])]
#[account(
seeds = [dex.dex_program.key.as_ref()],
bump = officer.bumps.bump,
)]
officer: ProgramAccount<'info, Officer>,
#[account(
mut,
@ -411,7 +414,10 @@ pub struct Dex<'info> {
#[derive(Accounts)]
pub struct SwapToUsdc<'info> {
#[account(seeds = [dex_program.key().as_ref(), &[officer.bumps.bump]])]
#[account(
seeds = [dex_program.key().as_ref()],
bump = officer.bumps.bump,
)]
officer: ProgramAccount<'info, Officer>,
market: DexMarketAccounts<'info>,
#[account(
@ -437,7 +443,10 @@ pub struct SwapToUsdc<'info> {
#[derive(Accounts)]
pub struct SwapToSrm<'info> {
#[account(seeds = [dex_program.key().as_ref(), &[officer.bumps.bump]])]
#[account(
seeds = [dex_program.key().as_ref()],
bump = officer.bumps.bump,
)]
officer: ProgramAccount<'info, Officer>,
market: DexMarketAccounts<'info>,
#[account(
@ -529,7 +538,8 @@ pub struct DropStakeReward<'info> {
)]
officer: ProgramAccount<'info, Officer>,
#[account(
seeds = [b"stake", officer.key().as_ref(), &[officer.bumps.stake]]
seeds = [b"stake", officer.key().as_ref()],
bump = officer.bumps.stake,
)]
stake: CpiAccount<'info, TokenAccount>,
#[cfg_attr(

View File

@ -60,7 +60,8 @@ pub struct CreateChatRoom<'info> {
#[derive(Accounts)]
pub struct SendMessage<'info> {
#[account(
seeds = [authority.key().as_ref(), &[user.bump]],
seeds = [authority.key().as_ref()],
bump = user.bump,
has_one = authority,
)]
user: ProgramAccount<'info, User>,

View File

@ -232,7 +232,10 @@ impl<'info> InitializePool<'info> {
pub struct ExchangeUsdcForRedeemable<'info> {
#[account(has_one = redeemable_mint, has_one = pool_usdc)]
pub pool_account: ProgramAccount<'info, PoolAccount>,
#[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
#[account(
seeds = [pool_account.watermelon_mint.as_ref()],
bump = pool_account.nonce,
)]
pool_signer: AccountInfo<'info>,
#[account(
mut,
@ -256,7 +259,10 @@ pub struct ExchangeUsdcForRedeemable<'info> {
pub struct ExchangeRedeemableForUsdc<'info> {
#[account(has_one = redeemable_mint, has_one = pool_usdc)]
pub pool_account: ProgramAccount<'info, PoolAccount>,
#[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
#[account(
seeds = [pool_account.watermelon_mint.as_ref()],
bump = pool_account.nonce,
)]
pool_signer: AccountInfo<'info>,
#[account(
mut,
@ -280,7 +286,10 @@ pub struct ExchangeRedeemableForUsdc<'info> {
pub struct ExchangeRedeemableForWatermelon<'info> {
#[account(has_one = redeemable_mint, has_one = pool_watermelon)]
pub pool_account: ProgramAccount<'info, PoolAccount>,
#[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
#[account(
seeds = [pool_account.watermelon_mint.as_ref()],
bump = pool_account.nonce,
)]
pool_signer: AccountInfo<'info>,
#[account(
mut,
@ -304,7 +313,10 @@ pub struct ExchangeRedeemableForWatermelon<'info> {
pub struct WithdrawPoolUsdc<'info> {
#[account(has_one = pool_usdc, has_one = distribution_authority)]
pub pool_account: ProgramAccount<'info, PoolAccount>,
#[account(seeds = [pool_account.watermelon_mint.as_ref(), &[pool_account.nonce]])]
#[account(
seeds = [pool_account.watermelon_mint.as_ref()],
bump = pool_account.nonce,
)]
pub pool_signer: AccountInfo<'info>,
#[account(mut, constraint = pool_usdc.owner == *pool_signer.key)]
pub pool_usdc: CpiAccount<'info, TokenAccount>,

View File

@ -217,7 +217,7 @@ pub struct CreateVesting<'info> {
#[account(signer)]
depositor_authority: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
@ -251,13 +251,16 @@ pub struct Withdraw<'info> {
beneficiary: AccountInfo<'info>,
#[account(mut)]
vault: CpiAccount<'info, TokenAccount>,
#[account(seeds = [vesting.to_account_info().key.as_ref(), &[vesting.nonce]])]
#[account(
seeds = [vesting.to_account_info().key.as_ref()],
bump = vesting.nonce,
)]
vesting_signer: AccountInfo<'info>,
// Withdraw receiving target..
#[account(mut)]
token: CpiAccount<'info, TokenAccount>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
@ -282,9 +285,12 @@ pub struct WhitelistTransfer<'info> {
// Whitelist interface.
#[account(mut, has_one = beneficiary, has_one = vault)]
vesting: ProgramAccount<'info, Vesting>,
#[account(mut, "&vault.owner == vesting_signer.key")]
#[account(mut, constraint = &vault.owner == vesting_signer.key)]
vault: CpiAccount<'info, TokenAccount>,
#[account(seeds = [vesting.to_account_info().key.as_ref(), &[vesting.nonce]])]
#[account(
seeds = [vesting.to_account_info().key.as_ref()],
bump = vesting.nonce,
)]
vesting_signer: AccountInfo<'info>,
#[account("token_program.key == &token::ID")]
token_program: AccountInfo<'info>,

View File

@ -643,15 +643,15 @@ impl<'info> CreateMember<'info> {
pub struct BalanceSandboxAccounts<'info> {
#[account(mut)]
spt: CpiAccount<'info, TokenAccount>,
#[account(mut, "vault.owner == spt.owner")]
#[account(mut, constraint = vault.owner == spt.owner)]
vault: CpiAccount<'info, TokenAccount>,
#[account(
mut,
"vault_stake.owner == spt.owner",
"vault_stake.mint == vault.mint"
constraint = vault_stake.owner == spt.owner,
constraint = vault_stake.mint == vault.mint
)]
vault_stake: CpiAccount<'info, TokenAccount>,
#[account(mut, "vault_pw.owner == spt.owner", "vault_pw.mint == vault.mint")]
#[account(mut, constraint = vault_pw.owner == spt.owner, constraint = vault_pw.mint == vault.mint)]
vault_pw: CpiAccount<'info, TokenAccount>,
}
@ -669,8 +669,8 @@ pub struct SetLockupProgram<'info> {
#[derive(Accounts)]
pub struct IsRealized<'info> {
#[account(
"&member.balances.spt == member_spt.to_account_info().key",
"&member.balances_locked.spt == member_spt_locked.to_account_info().key"
constraint = &member.balances.spt == member_spt.to_account_info().key,
constraint = &member.balances_locked.spt == member_spt_locked.to_account_info().key
)]
member: ProgramAccount<'info, Member>,
member_spt: CpiAccount<'info, TokenAccount>,
@ -692,15 +692,15 @@ pub struct Deposit<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account(mut, "vault.to_account_info().key == &member.balances.vault")]
#[account(mut, constraint = vault.to_account_info().key == &member.balances.vault)]
vault: CpiAccount<'info, TokenAccount>,
// Depositor.
#[account(mut)]
depositor: AccountInfo<'info>,
#[account(signer, "depositor_authority.key == &member.beneficiary")]
#[account(signer, constraint = depositor_authority.key == &member.beneficiary)]
depositor_authority: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
}
@ -708,29 +708,26 @@ pub struct Deposit<'info> {
pub struct DepositLocked<'info> {
// Lockup whitelist relay interface.
#[account(
"vesting.to_account_info().owner == &registry.lockup_program",
"vesting.beneficiary == member.beneficiary"
constraint = vesting.to_account_info().owner == &registry.lockup_program,
constraint = vesting.beneficiary == member.beneficiary
)]
vesting: CpiAccount<'info, Vesting>,
#[account(mut, "vesting_vault.key == &vesting.vault")]
#[account(mut, constraint = vesting_vault.key == &vesting.vault)]
vesting_vault: AccountInfo<'info>,
// Note: no need to verify the depositor_authority since the SPL program
// will fail the transaction if it's not correct.
#[account(signer)]
depositor_authority: AccountInfo<'info>,
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
#[account(
mut,
"member_vault.to_account_info().key == &member.balances_locked.vault"
constraint = member_vault.to_account_info().key == &member.balances_locked.vault
)]
member_vault: CpiAccount<'info, TokenAccount>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
@ -757,26 +754,26 @@ pub struct Stake<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account("BalanceSandbox::from(&balances) == member.balances")]
#[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
balances: BalanceSandboxAccounts<'info>,
#[account("BalanceSandbox::from(&balances_locked) == member.balances_locked")]
#[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
balances_locked: BalanceSandboxAccounts<'info>,
// Program signers.
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
#[account(seeds = [registrar.to_account_info().key.as_ref(), &[registrar.nonce]])]
#[account(
seeds = [registrar.to_account_info().key.as_ref()],
bump = registrar.nonce,
)]
registrar_signer: AccountInfo<'info>,
// Misc.
clock: Sysvar<'info, Clock>,
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
}
@ -796,23 +793,20 @@ pub struct StartUnstake<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account("BalanceSandbox::from(&balances) == member.balances")]
#[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
balances: BalanceSandboxAccounts<'info>,
#[account("BalanceSandbox::from(&balances_locked) == member.balances_locked")]
#[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
balances_locked: BalanceSandboxAccounts<'info>,
// Programmatic signers.
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
@ -825,7 +819,7 @@ pub struct EndUnstake<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account(mut, has_one = registrar, has_one = member, "!pending_withdrawal.burned")]
#[account(mut, has_one = registrar, has_one = member, constraint = !pending_withdrawal.burned)]
pending_withdrawal: ProgramAccount<'info, PendingWithdrawal>,
// If we had ordered maps implementing Accounts we could do a constraint like
@ -838,16 +832,13 @@ pub struct EndUnstake<'info> {
vault_pw: AccountInfo<'info>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
}
@ -860,21 +851,18 @@ pub struct Withdraw<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account(mut, "vault.to_account_info().key == &member.balances.vault")]
#[account(mut, constraint = vault.to_account_info().key == &member.balances.vault)]
vault: CpiAccount<'info, TokenAccount>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
// Receiver.
#[account(mut)]
depositor: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
}
@ -882,27 +870,24 @@ pub struct Withdraw<'info> {
pub struct WithdrawLocked<'info> {
// Lockup whitelist relay interface.
#[account(
"vesting.to_account_info().owner == &registry.lockup_program",
"vesting.beneficiary == member.beneficiary"
constraint = vesting.to_account_info().owner == &registry.lockup_program,
constraint = vesting.beneficiary == member.beneficiary,
)]
vesting: CpiAccount<'info, Vesting>,
#[account(mut, "vesting_vault.key == &vesting.vault")]
#[account(mut, constraint = vesting_vault.key == &vesting.vault)]
vesting_vault: AccountInfo<'info>,
#[account(signer)]
vesting_signer: AccountInfo<'info>,
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
#[account(
mut,
"member_vault.to_account_info().key == &member.balances_locked.vault"
constraint = member_vault.to_account_info().key == &member.balances_locked.vault
)]
member_vault: CpiAccount<'info, TokenAccount>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
member.to_account_info().key.as_ref(),
&[member.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), member.to_account_info().key.as_ref()],
bump = member.nonce,
)]
member_signer: AccountInfo<'info>,
@ -934,7 +919,7 @@ pub struct DropReward<'info> {
#[account(signer)]
depositor_authority: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
@ -984,9 +969,9 @@ pub struct ClaimRewardCommon<'info> {
member: ProgramAccount<'info, Member>,
#[account(signer)]
beneficiary: AccountInfo<'info>,
#[account("BalanceSandbox::from(&balances) == member.balances")]
#[account(constraint = BalanceSandbox::from(&balances) == member.balances)]
balances: BalanceSandboxAccounts<'info>,
#[account("BalanceSandbox::from(&balances_locked) == member.balances_locked")]
#[account(constraint = BalanceSandbox::from(&balances_locked) == member.balances_locked)]
balances_locked: BalanceSandboxAccounts<'info>,
// Vendor.
#[account(has_one = registrar, has_one = vault)]
@ -994,15 +979,12 @@ pub struct ClaimRewardCommon<'info> {
#[account(mut)]
vault: AccountInfo<'info>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
vendor.to_account_info().key.as_ref(),
&[vendor.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), vendor.to_account_info().key.as_ref()],
bump = vendor.nonce,
)]
vendor_signer: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}
@ -1017,11 +999,8 @@ pub struct ExpireReward<'info> {
#[account(mut)]
vault: CpiAccount<'info, TokenAccount>,
#[account(
seeds = [
registrar.to_account_info().key.as_ref(),
vendor.to_account_info().key.as_ref(),
&[vendor.nonce],
]
seeds = [registrar.to_account_info().key.as_ref(), vendor.to_account_info().key.as_ref()],
bump = vendor.nonce
)]
vendor_signer: AccountInfo<'info>,
// Receiver.
@ -1030,7 +1009,7 @@ pub struct ExpireReward<'info> {
#[account(mut)]
expiry_receiver_token: AccountInfo<'info>,
// Misc.
#[account("token_program.key == &token::ID")]
#[account(constraint = token_program.key == &token::ID)]
token_program: AccountInfo<'info>,
clock: Sysvar<'info, Clock>,
}

View File

@ -13,6 +13,7 @@ pub struct DataU16 {
}
#[account]
#[derive(Default)]
pub struct DataI8 {
pub data: i8,
}

View File

@ -1,15 +1,16 @@
use crate::account::*;
use crate::misc::MyState;
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, TokenAccount};
use misc2::misc2::MyState as Misc2State;
use std::mem::size_of;
#[derive(Accounts)]
#[instruction(token_bump: u8, mint_bump: u8)]
pub struct TestTokenSeedsInit<'info> {
#[account(
init,
seeds = [b"my-mint-seed".as_ref(), &[mint_bump]],
seeds = [b"my-mint-seed".as_ref()],
bump = mint_bump,
payer = authority,
mint::decimals = 6,
mint::authority = authority,
@ -17,7 +18,8 @@ pub struct TestTokenSeedsInit<'info> {
pub mint: CpiAccount<'info, Mint>,
#[account(
init,
seeds = [b"my-token-seed".as_ref(), &[token_bump]],
seeds = [b"my-token-seed".as_ref()],
bump = token_bump,
payer = authority,
token::mint = mint,
token::authority = authority,
@ -32,7 +34,10 @@ pub struct TestTokenSeedsInit<'info> {
#[derive(Accounts)]
#[instruction(nonce: u8)]
pub struct TestInstructionConstraint<'info> {
#[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
#[account(
seeds = [b"my-seed", my_account.key.as_ref()],
bump = nonce,
)]
pub my_pda: AccountInfo<'info>,
pub my_account: AccountInfo<'info>,
}
@ -42,7 +47,8 @@ pub struct TestInstructionConstraint<'info> {
pub struct TestPdaInit<'info> {
#[account(
init,
seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed],
bump = bump,
payer = my_payer,
)]
pub my_pda: ProgramAccount<'info, DataU16>,
@ -54,7 +60,12 @@ pub struct TestPdaInit<'info> {
#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct TestPdaInitZeroCopy<'info> {
#[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
#[account(
init,
seeds = [b"my-seed".as_ref()],
bump = bump,
payer = my_payer,
)]
pub my_pda: Loader<'info, DataZeroCopy>,
pub my_payer: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
@ -62,7 +73,11 @@ pub struct TestPdaInitZeroCopy<'info> {
#[derive(Accounts)]
pub struct TestPdaMutZeroCopy<'info> {
#[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
#[account(
mut,
seeds = [b"my-seed".as_ref()],
bump = my_pda.load()?.bump,
)]
pub my_pda: Loader<'info, DataZeroCopy>,
pub my_payer: AccountInfo<'info>,
}
@ -126,6 +141,47 @@ pub struct TestSimulate {}
#[derive(Accounts)]
pub struct TestI8<'info> {
#[account(init)]
#[account(zero)]
pub data: ProgramAccount<'info, DataI8>,
}
#[derive(Accounts)]
pub struct TestInit<'info> {
#[account(init, payer = payer)]
pub data: ProgramAccount<'info, DataI8>,
#[account(signer)]
pub payer: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestInitZeroCopy<'info> {
#[account(init, payer = payer, space = 8 + size_of::<DataZeroCopy>())]
pub data: Loader<'info, DataZeroCopy>,
#[account(signer)]
pub payer: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestInitMint<'info> {
#[account(init, mint::decimals = 6, mint::authority = payer, payer = payer)]
pub mint: CpiAccount<'info, Mint>,
#[account(signer)]
pub payer: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
pub token_program: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct TestInitToken<'info> {
#[account(init, token::mint = mint, token::authority = payer, payer = payer)]
pub token: CpiAccount<'info, TokenAccount>,
pub mint: CpiAccount<'info, Mint>,
#[account(signer)]
pub payer: AccountInfo<'info>,
pub rent: Sysvar<'info, Rent>,
pub system_program: AccountInfo<'info>,
pub token_program: AccountInfo<'info>,
}

View File

@ -128,4 +128,26 @@ pub mod misc {
) -> ProgramResult {
Err(ProgramError::Custom(1234))
}
pub fn test_init(ctx: Context<TestInit>) -> ProgramResult {
ctx.accounts.data.data = 3;
Ok(())
}
pub fn test_init_zero_copy(ctx: Context<TestInitZeroCopy>) -> ProgramResult {
let mut data = ctx.accounts.data.load_init()?;
data.data = 10;
data.bump = 2;
Ok(())
}
pub fn test_init_mint(ctx: Context<TestInitMint>) -> ProgramResult {
assert!(ctx.accounts.mint.decimals == 6);
Ok(())
}
pub fn test_init_token(ctx: Context<TestInitToken>) -> ProgramResult {
assert!(ctx.accounts.token.mint == ctx.accounts.mint.key());
Ok(())
}
}

View File

@ -370,4 +370,88 @@ describe("misc", () => {
}
);
});
it("Can init a random account", async () => {
const data = anchor.web3.Keypair.generate();
await program.rpc.testInit({
accounts: {
data: data.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
},
signers: [data],
});
const account = await program.account.dataI8.fetch(data.publicKey);
assert.ok(account.data === 3);
});
it("Can init a random zero copy account", async () => {
const data = anchor.web3.Keypair.generate();
await program.rpc.testInitZeroCopy({
accounts: {
data: data.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
},
signers: [data],
});
const account = await program.account.dataZeroCopy.fetch(data.publicKey);
assert.ok(account.data === 10);
assert.ok(account.bump === 2);
});
let mint = undefined;
it("Can create a random mint account", async () => {
mint = anchor.web3.Keypair.generate();
await program.rpc.testInitMint({
accounts: {
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [mint],
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const mintAccount = await client.getMintInfo();
assert.ok(mintAccount.decimals === 6);
assert.ok(
mintAccount.mintAuthority.equals(program.provider.wallet.publicKey)
);
});
it("Can create a random token account", async () => {
const token = anchor.web3.Keypair.generate();
await program.rpc.testInitToken({
accounts: {
token: token.publicKey,
mint: mint.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
signers: [token],
});
const client = new Token(
program.provider.connection,
mint.publicKey,
TOKEN_PROGRAM_ID,
program.provider.wallet.payer
);
const account = await client.getAccountInfo(token.publicKey);
assert.ok(account.state === 1);
assert.ok(account.amount.toNumber() === 0);
assert.ok(account.isInitialized);
assert.ok(account.owner.equals(program.provider.wallet.publicKey));
assert.ok(account.mint.equals(mint.publicKey));
});
});

View File

@ -192,20 +192,21 @@ pub struct Approve<'info> {
pub struct Auth<'info> {
#[account(mut)]
multisig: ProgramAccount<'info, Multisig>,
#[account(signer, seeds = [
multisig.to_account_info().key.as_ref(),
&[multisig.nonce],
])]
#[account(
signer,
seeds = [multisig.to_account_info().key.as_ref()],
bump = multisig.nonce,
)]
multisig_signer: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct ExecuteTransaction<'info> {
multisig: ProgramAccount<'info, Multisig>,
#[account(seeds = [
multisig.to_account_info().key.as_ref(),
&[multisig.nonce],
])]
#[account(
seeds = [multisig.to_account_info().key.as_ref()],
bump = multisig.nonce,
)]
multisig_signer: AccountInfo<'info>,
#[account(mut, has_one = multisig)]
transaction: ProgramAccount<'info, Transaction>,

View File

@ -39,7 +39,8 @@ use syn::parse_macro_input;
/// |:--|:--|:--|
/// | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
/// | `#[account(mut)]` | On `AccountInfo`, `ProgramAccount` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, creating the account via the system program. |
/// | `#[account(zero)]` | On `ProgramAccount` structs. | Asserts the account discriminator is zero. |
/// | `#[account(close = <target>)]` | On `ProgramAccount` and `Loader` structs. | Marks the account as being closed at the end of the instruction's execution, sending the rent exemption lamports to the specified <target>. |
/// | `#[account(has_one = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
/// | `#[account(seeds = [<seeds>], bump? = <target>, payer? = <target>, space? = <target>, owner? = <target>)]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. If bump is provided, then appends it to the seeds. On initialization, validates the given bump is the bump provided by `Pubkey::find_program_address`.|

View File

@ -1,5 +1,5 @@
use crate::error::ErrorCode;
use crate::{Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use crate::{Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
@ -21,31 +21,6 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
}
}
impl<'info> AccountsInit<'info> for AccountInfo<'info> {
fn try_accounts_init(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
// The discriminator should be zero, since we're initializing.
let data: &[u8] = &account.try_borrow_data()?;
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(account.clone())
}
}
impl<'info> ToAccountMetas for AccountInfo<'info> {
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
let is_signer = is_signer.unwrap_or(self.is_signer);

View File

@ -30,7 +30,7 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
))
}
pub fn try_from_init(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
Self::try_from(info)
}

View File

@ -46,6 +46,8 @@ pub enum ErrorCode {
ConstraintClose,
#[msg("An address constraint was violated")]
ConstraintAddress,
#[msg("Expected zero account discriminant")]
ConstraintZero,
// Accounts.
#[msg("The account discriminator was already set on this account")]

View File

@ -56,7 +56,7 @@ pub struct IdlAccounts<'info> {
// Accounts for creating an idl buffer.
#[derive(Accounts)]
pub struct IdlCreateBuffer<'info> {
#[account(init)]
#[account(zero)]
pub buffer: ProgramAccount<'info, IdlAccount>,
#[account(signer, constraint = authority.key != &Pubkey::new_from_array([0u8; 32]))]
pub authority: AccountInfo<'info>,

View File

@ -105,17 +105,6 @@ pub trait AccountsClose<'info>: ToAccountInfos<'info> {
fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult;
}
/// A data structure of accounts providing a one time deserialization upon
/// account initialization, i.e., when the data array for a given account is
/// zeroed. Any subsequent call to `try_accounts_init` should fail. For all
/// subsequent deserializations, it's expected that [`Accounts`] is used.
pub trait AccountsInit<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
fn try_accounts_init(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError>;
}
/// Transformation to
/// [`AccountMeta`](../solana_program/instruction/struct.AccountMeta.html)
/// structs.
@ -234,10 +223,9 @@ impl Key for Pubkey {
pub mod prelude {
pub use super::{
access_control, account, emit, error, event, interface, program, require, state, zero_copy,
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AccountsInit,
AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, CpiState,
CpiStateContext, Key, Loader, ProgramAccount, ProgramState, Sysvar, ToAccountInfo,
ToAccountInfos, ToAccountMetas,
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AnchorDeserialize,
AnchorSerialize, Context, CpiAccount, CpiContext, CpiState, CpiStateContext, Key, Loader,
ProgramAccount, ProgramState, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
pub use borsh;

View File

@ -1,7 +1,6 @@
use crate::error::ErrorCode;
use crate::{
Accounts, AccountsClose, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos,
ToAccountMetas, ZeroCopy,
Accounts, AccountsClose, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas, ZeroCopy,
};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
@ -54,17 +53,9 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
/// Constructs a new `Loader` from an uninitialized account.
#[inline(never)]
pub fn try_from_init(acc_info: &AccountInfo<'info>) -> Result<Loader<'info, T>, ProgramError> {
let data = acc_info.try_borrow_data()?;
// The discriminator should be zero, since we're initializing.
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
pub fn try_from_unchecked(
acc_info: &AccountInfo<'info>,
) -> Result<Loader<'info, T>, ProgramError> {
Ok(Loader::new(acc_info.clone()))
}
@ -147,25 +138,6 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
}
}
impl<'info, T: ZeroCopy> AccountsInit<'info> for Loader<'info, T> {
#[inline(never)]
fn try_accounts_init(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let l = Loader::try_from_init(account)?;
if l.acc_info.owner != program_id {
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(l)
}
}
impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
// The account *cannot* be loaded when this is called.
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {

View File

@ -1,7 +1,7 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, AccountsInit,
CpiAccount, ToAccountInfo, ToAccountInfos, ToAccountMetas,
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, CpiAccount,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
@ -45,17 +45,10 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
/// initialization (since the entire account data array is zeroed and thus
/// no account type is set).
#[inline(never)]
pub fn try_from_init(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
pub fn try_from_unchecked(
info: &AccountInfo<'a>,
) -> Result<ProgramAccount<'a, T>, ProgramError> {
let mut data: &[u8] = &info.try_borrow_data()?;
// The discriminator should be zero, since we're initializing.
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(ProgramAccount::new(
info.clone(),
T::try_deserialize_unchecked(&mut data)?,
@ -90,28 +83,6 @@ where
}
}
impl<'info, T> AccountsInit<'info> for ProgramAccount<'info, T>
where
T: AccountSerialize + AccountDeserialize + Clone,
{
#[inline(never)]
fn try_accounts_init(
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let pa = ProgramAccount::try_from_init(account)?;
if pa.inner.info.owner != program_id {
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(pa)
}
}
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
for ProgramAccount<'info, T>
{

View File

@ -60,14 +60,14 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
let mut constraints = Vec::new();
if let Some(c) = seeds {
constraints.push(Constraint::Seeds(c));
if let Some(c) = zeroed {
constraints.push(Constraint::Zeroed(c));
}
if let Some(c) = init {
constraints.push(Constraint::Init(c));
}
if let Some(c) = zeroed {
constraints.push(Constraint::Zeroed(c));
if let Some(c) = seeds {
constraints.push(Constraint::Seeds(c));
}
if let Some(c) = mutable {
constraints.push(Constraint::Mut(c));
@ -136,14 +136,27 @@ fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2:
}
}
pub fn generate_constraint_init(_f: &Field, _c: &ConstraintInit) -> proc_macro2::TokenStream {
quote! {}
pub fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
generate_constraint_init_group(f, c)
}
pub fn generate_constraint_zeroed(_f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
// No constraint. The zero discriminator is checked in `try_accounts_init`
// currently.
quote! {}
pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
let field = &f.ident;
let (account_ty, account_wrapper_ty, _) = parse_ty(f);
quote! {
let #field: #account_wrapper_ty<#account_ty> = {
let mut __data: &[u8] = &#field.try_borrow_data()?;
let mut __disc_bytes = [0u8; 8];
__disc_bytes.copy_from_slice(&__data[..8]);
let __discriminator = u64::from_le_bytes(__disc_bytes);
if __discriminator != 0 {
return Err(anchor_lang::__private::ErrorCode::ConstraintZero.into());
}
#account_wrapper_ty::try_from_unchecked(
&#field,
)?
};
}
}
pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream {
@ -184,6 +197,8 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
let info = match f.ty {
Ty::AccountInfo => quote! { #ident },
Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
Ty::Loader(_) => quote! { #ident.to_account_info() },
Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
_ => panic!("Invalid syntax: signer cannot be specified."),
};
quote! {
@ -255,31 +270,19 @@ pub fn generate_constraint_rent_exempt(
}
}
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 {
fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
let payer = {
let p = &c.payer;
quote! {
let payer = #p.to_account_info();
}
};
let seeds_constraint = generate_constraint_seeds_address(f, c);
let seeds_with_nonce = {
let s = &c.seeds;
match c.bump.as_ref() {
// Bump keyword not given. Just use the seeds.
None => quote! {
[#s]
},
// Bump keyword given.
Some(bump) => match bump {
let seeds_with_nonce = match &c.seeds {
None => quote! {},
Some(c) => {
let s = &c.seeds;
let inner = match c.bump.as_ref() {
// Bump target not given. Use the canonical bump.
None => {
quote! {
@ -298,30 +301,23 @@ fn generate_constraint_seeds_init(f: &Field, c: &ConstraintSeedsGroup) -> proc_m
Some(b) => quote! {
[#s, &[#b]]
},
},
};
quote! {
&#inner[..]
}
}
};
generate_pda(
f,
seeds_constraint,
seeds_with_nonce,
payer,
&c.space,
&c.kind,
)
generate_pda(f, seeds_with_nonce, payer, &c.space, &c.kind)
}
fn generate_constraint_seeds_address(
f: &Field,
c: &ConstraintSeedsGroup,
) -> proc_macro2::TokenStream {
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
let name = &f.ident;
let s = &c.seeds;
// If the bump is provided on *initialization*, then force it to be the
// canonical nonce.
if c.is_init && c.bump.is_some() && c.bump.as_ref().unwrap().is_some() {
let b = c.bump.as_ref().unwrap().as_ref().unwrap();
// If the bump is provided with init *and target*, then force it to be the
// canonical bump.
if c.is_init && c.bump.is_some() {
let b = c.bump.as_ref().unwrap();
quote! {
let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
&[#s],
@ -336,35 +332,26 @@ fn generate_constraint_seeds_address(
}
} else {
let seeds = match c.bump.as_ref() {
// Bump keyword not given, so just use the seeds.
// Bump target not given. Find it.
None => {
quote! {
[#s]
[
#s,
&[
Pubkey::find_program_address(
&[#s],
program_id,
).1
]
]
}
}
// Bump keyword given.
Some(bump) => match bump {
// Bump target not given. Find it.
None => {
quote! {
[
#s,
&[
Pubkey::find_program_address(
&[#s],
program_id,
).1
]
]
}
// Bump target given. Use it.
Some(b) => {
quote! {
[#s, &[#b]]
}
// Bump target given. Use it.
Some(b) => {
quote! {
[#s, &[#b]]
}
}
},
}
};
quote! {
let __program_signer = Pubkey::create_program_address(
@ -429,11 +416,10 @@ fn parse_ty(f: &Field) -> (proc_macro2::TokenStream, proc_macro2::TokenStream, b
pub fn generate_pda(
f: &Field,
seeds_constraint: proc_macro2::TokenStream,
seeds_with_nonce: proc_macro2::TokenStream,
payer: proc_macro2::TokenStream,
space: &Option<Expr>,
kind: &PdaKind,
kind: &InitKind,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let (account_ty, account_wrapper_ty, is_zero_copy) = parse_ty(f);
@ -452,7 +438,7 @@ pub fn generate_pda(
#account_wrapper_ty<#account_ty>
},
quote! {
#account_wrapper_ty::try_from_init(
#account_wrapper_ty::try_from_unchecked(
&#field.to_account_info(),
)?
},
@ -460,10 +446,9 @@ pub fn generate_pda(
};
match kind {
PdaKind::Token { owner, mint } => quote! {
InitKind::Token { owner, mint } => quote! {
let #field: #combined_account_ty = {
#payer
#seeds_constraint
// Fund the account for rent exemption.
let required_lamports = __anchor_rent
@ -485,7 +470,7 @@ pub fn generate_pda(
#field.to_account_info(),
system_program.to_account_info().clone(),
],
&[&#seeds_with_nonce[..]],
&[#seeds_with_nonce],
)?;
// Initialize the token account.
@ -498,15 +483,14 @@ pub fn generate_pda(
};
let cpi_ctx = CpiContext::new(cpi_program, accounts);
anchor_spl::token::initialize_account(cpi_ctx)?;
anchor_lang::CpiAccount::try_from_init(
anchor_lang::CpiAccount::try_from_unchecked(
&#field.to_account_info(),
)?
};
},
PdaKind::Mint { owner, decimals } => quote! {
InitKind::Mint { owner, decimals } => quote! {
let #field: #combined_account_ty = {
#payer
#seeds_constraint
// Fund the account for rent exemption.
let required_lamports = rent
@ -528,7 +512,7 @@ pub fn generate_pda(
#field.to_account_info(),
system_program.to_account_info().clone(),
],
&[&#seeds_with_nonce[..]],
&[#seeds_with_nonce],
)?;
// Initialize the mint account.
@ -539,30 +523,30 @@ pub fn generate_pda(
};
let cpi_ctx = CpiContext::new(cpi_program, accounts);
anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, None)?;
anchor_lang::CpiAccount::try_from_init(
anchor_lang::CpiAccount::try_from_unchecked(
&#field.to_account_info(),
)?
};
},
PdaKind::Program { owner } => {
InitKind::Program { owner } => {
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();
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();
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 space = #s;
},
};
@ -580,7 +564,6 @@ pub fn generate_pda(
let #field = {
#space
#payer
#seeds_constraint
let lamports = __anchor_rent.minimum_balance(space);
let ix = anchor_lang::solana_program::system_instruction::create_account(
@ -599,7 +582,7 @@ pub fn generate_pda(
payer.to_account_info(),
system_program.to_account_info(),
],
&[&#seeds_with_nonce[..]]
&[#seeds_with_nonce],
).map_err(|e| {
anchor_lang::solana_program::msg!("Unable to create associated account");
e

View File

@ -30,7 +30,10 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
}
}
AccountField::Field(f) => {
if is_pda_init(af) {
// `init` and `zero` acccounts are special cased as they are
// deserialized by constraints. Here, we just take out the
// AccountInfo for later use at constraint validation time.
if is_init(af) || f.constraints.zeroed.is_some() {
let name = &f.ident;
quote!{
let #name = &accounts[0];
@ -38,17 +41,10 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
}
} else {
let name = typed_ident(f);
match f.constraints.is_init() || f.constraints.is_zeroed() {
false => quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
},
true => quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
},
quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
}
}
}
@ -111,18 +107,6 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
}
}
fn is_pda_init(af: &AccountField) -> bool {
match af {
AccountField::CompositeField(_s) => false,
AccountField::Field(f) => f
.constraints
.seeds
.as_ref()
.map(|f| f.is_init)
.unwrap_or(false),
}
}
fn typed_ident(field: &Field) -> TokenStream {
let name = &field.ident;
@ -183,17 +167,17 @@ fn typed_ident(field: &Field) -> TokenStream {
}
pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
let non_pda_fields: Vec<&AccountField> =
accs.fields.iter().filter(|af| !is_pda_init(af)).collect();
let non_init_fields: Vec<&AccountField> =
accs.fields.iter().filter(|af| !is_init(af)).collect();
// Deserialization for each pda init field. This must be after
// the inital extraction from the accounts slice and before access_checks.
let init_pda_fields: Vec<proc_macro2::TokenStream> = accs
let init_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) {
AccountField::Field(f) => match is_init(af) {
false => None,
true => Some(f),
},
@ -202,7 +186,7 @@ pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.collect();
// Constraint checks for each account fields.
let access_checks: Vec<proc_macro2::TokenStream> = non_pda_fields
let access_checks: Vec<proc_macro2::TokenStream> = non_init_fields
.iter()
.map(|af: &&AccountField| match af {
AccountField::Field(f) => constraints::generate(f),
@ -211,7 +195,7 @@ pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.collect();
quote! {
#(#init_pda_fields)*
#(#init_fields)*
#(#access_checks)*
}
}
@ -239,3 +223,10 @@ pub fn generate_accounts_instance(accs: &AccountsStruct) -> proc_macro2::TokenSt
}
}
}
fn is_init(af: &AccountField) -> bool {
match af {
AccountField::CompositeField(_s) => false,
AccountField::Field(f) => f.constraints.init.is_some(),
}
}

View File

@ -227,7 +227,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
)?;
// Zero copy deserialize.
let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from_init(&ctor_accounts.to)?;
let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from_unchecked(&ctor_accounts.to)?;
// Invoke the ctor in a new lexical scope so that
// the zero-copy RefMut gets dropped. Required

View File

@ -262,7 +262,7 @@ pub struct ErrorCode {
// All well formed constraints on a single `Accounts` field.
#[derive(Debug, Default, Clone)]
pub struct ConstraintGroup {
init: Option<ConstraintInit>,
init: Option<ConstraintInitGroup>,
zeroed: Option<ConstraintZeroed>,
mutable: Option<ConstraintMut>,
signer: Option<ConstraintSigner>,
@ -279,10 +279,6 @@ pub struct ConstraintGroup {
}
impl ConstraintGroup {
pub fn is_init(&self) -> bool {
self.init.is_some()
}
pub fn is_zeroed(&self) -> bool {
self.zeroed.is_some()
}
@ -306,7 +302,7 @@ impl ConstraintGroup {
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Constraint {
Init(ConstraintInit),
Init(ConstraintInitGroup),
Zeroed(ConstraintZeroed),
Mut(ConstraintMut),
Signer(ConstraintSigner),
@ -398,15 +394,19 @@ pub enum ConstraintRentExempt {
Skip,
}
#[derive(Debug, Clone)]
pub struct ConstraintInitGroup {
pub seeds: Option<ConstraintSeedsGroup>,
pub payer: Option<Ident>,
pub space: Option<Expr>,
pub kind: InitKind,
}
#[derive(Debug, Clone)]
pub struct ConstraintSeedsGroup {
pub is_init: bool,
pub seeds: Punctuated<Expr, Token![,]>,
pub payer: Option<Ident>,
pub space: Option<Expr>,
pub kind: PdaKind,
// Some(None) => bump was given without a target.
pub bump: Option<Option<Expr>>,
pub bump: Option<Expr>, // None => bump was given without a target.
}
#[derive(Debug, Clone)]
@ -434,7 +434,7 @@ pub struct ConstraintSpace {
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum PdaKind {
pub enum InitKind {
Program { owner: Option<Expr> },
Token { owner: Expr, mint: Expr },
Mint { owner: Expr, decimals: Expr },

View File

@ -279,8 +279,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
bump: None,
}
}
pub fn build(mut self) -> ParseResult<ConstraintGroup> {
// Init implies mutable and rent exempt.
// Init.
if let Some(i) = &self.init {
match self.mutable {
Some(m) => {
@ -298,9 +299,22 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
self.rent_exempt
.replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
}
if self.payer.is_none() {
return Err(ParseError::new(
i.span(),
"payer must be provided when initializing an account",
));
}
// When initializing a non-PDA account, the account being
// initialized must sign to invoke the system program's create
// account instruction.
if self.signer.is_none() && self.seeds.is_none() {
self.signer
.replace(Context::new(i.span(), ConstraintSigner {}));
}
}
// Seeds.
// Zero.
if let Some(z) = &self.zeroed {
match self.mutable {
Some(m) => {
@ -320,6 +334,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
}
// Seeds.
if let Some(i) = &self.seeds {
if self.init.is_some() && self.payer.is_none() {
return Err(ParseError::new(
@ -327,6 +342,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
"payer must be provided when creating a program derived address",
));
}
if self.bump.is_none() {
return Err(ParseError::new(
i.span(),
"bump must be provided with seeds",
));
}
}
// Token.
@ -338,7 +359,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
));
}
if self.init.is_none() || self.seeds.is_none() {
if self.init.is_none() {
return Err(ParseError::new(
token_mint.span(),
"init is required for a pda token",
@ -434,9 +455,46 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
};
let is_init = init.is_some();
let seeds = seeds.map(|c| ConstraintSeedsGroup {
is_init: init.is_some(),
seeds: c.seeds.clone(),
bump: into_inner!(bump)
.map(|b| b.bump)
.expect("bump must be provided with seeds"),
});
Ok(ConstraintGroup {
init: into_inner!(init),
init: init.as_ref().map(|_| Ok(ConstraintInitGroup {
seeds: seeds.clone(),
payer: into_inner!(payer.clone()).map(|a| a.target),
space: space.clone().map(|s| s.space.clone()),
kind: if let Some(tm) = &token_mint {
InitKind::Token {
mint: tm.clone().into_inner().mint,
owner: match &token_authority {
Some(a) => a.clone().into_inner().auth,
None => return Err(ParseError::new(
tm.span(),
"authority must be provided to initialize a token program derived address"
)),
},
}
} else if let Some(d) = &mint_decimals {
InitKind::Mint {
decimals: d.clone().into_inner().decimals,
owner: match &mint_authority {
Some(a) => a.clone().into_inner().mint_auth,
None => return Err(ParseError::new(
d.span(),
"authority must be provided to initialize a mint program derived address"
))
}
}
} else {
InitKind::Program {
owner: pda_owner.clone(),
}
},
})).transpose()?,
zeroed: into_inner!(zeroed),
mutable: into_inner!(mutable),
signer: into_inner!(signer),
@ -449,45 +507,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
state: into_inner!(state),
close: into_inner!(close),
address: into_inner!(address),
seeds: seeds
.map(|c| {
Ok(ConstraintSeedsGroup {
is_init,
seeds: c.into_inner().seeds,
payer: into_inner!(payer.clone()).map(|a| a.target),
space: space.clone().map(|s| s.space.clone()),
kind: if let Some(tm) = &token_mint {
PdaKind::Token {
mint: tm.clone().into_inner().mint,
owner: match &token_authority {
Some(a) => a.clone().into_inner().auth,
None => return Err(ParseError::new(
tm.span(),
"authority must be provided to initialize a token program derived address"
)),
},
}
} else if let Some(d) = &mint_decimals {
PdaKind::Mint {
decimals: d.clone().into_inner().decimals,
owner: match &mint_authority {
Some(a) => a.clone().into_inner().mint_auth,
None => return Err(ParseError::new(
d.span(),
"authority must be provided to initialize a mint program derived address"
))
}
}
} else {
PdaKind::Program {
owner: pda_owner.clone(),
}
},
bump: into_inner!(bump).map(|b| b.bump),
})
})
.transpose()?,
seeds,
})
}
@ -723,10 +743,10 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
fn add_payer(&mut self, c: Context<ConstraintPayer>) -> ParseResult<()> {
if self.seeds.is_none() {
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"seeds must be provided before payer",
"init must be provided before payer",
));
}
if self.payer.is_some() {
@ -737,7 +757,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
fn add_space(&mut self, c: Context<ConstraintSpace>) -> ParseResult<()> {
if self.seeds.is_none() {
if self.init.is_none() {
return Err(ParseError::new(
c.span(),
"init must be provided before space",