From 75c20856e4c8cfb0e4fd2b63c8d35a2b88e4a26a Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Sun, 29 Aug 2021 14:25:38 -0700 Subject: [PATCH] lang: Consistent init constraints (#641) --- CHANGELOG.md | 14 +- .../programs/cashiers-check/src/lib.rs | 20 +-- examples/cfo/deps/stake | 2 +- examples/cfo/programs/cfo/src/lib.rs | 18 +- examples/chat/programs/chat/src/lib.rs | 3 +- .../ido-pool/programs/ido-pool/src/lib.rs | 20 ++- examples/lockup/programs/lockup/src/lib.rs | 16 +- examples/lockup/programs/registry/src/lib.rs | 129 ++++++-------- examples/misc/programs/misc/src/account.rs | 1 + examples/misc/programs/misc/src/context.rs | 72 +++++++- examples/misc/programs/misc/src/lib.rs | 22 +++ examples/misc/tests/misc.js | 84 ++++++++++ .../multisig/programs/multisig/src/lib.rs | 17 +- lang/derive/accounts/src/lib.rs | 3 +- lang/src/account_info.rs | 27 +-- lang/src/cpi_account.rs | 2 +- lang/src/error.rs | 2 + lang/src/idl.rs | 2 +- lang/src/lib.rs | 18 +- lang/src/loader.rs | 36 +--- lang/src/program_account.rs | 39 +---- lang/syn/src/codegen/accounts/constraints.rs | 157 ++++++++---------- lang/syn/src/codegen/accounts/try_accounts.rs | 51 +++--- lang/syn/src/codegen/program/handlers.rs | 2 +- lang/syn/src/lib.rs | 24 +-- lang/syn/src/parser/accounts/constraints.rs | 114 +++++++------ 26 files changed, 487 insertions(+), 408 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e3d06a4a1..7b94072c30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 = , mint::authority = )]` ([#](https://github.com/project-serum/anchor/pull/562)). +* lang: Add constraints for initializing mint accounts as pdas, `#[account(init, seeds = [...], mint::decimals = , mint::authority = )]` ([#562](https://github.com/project-serum/anchor/pull/562)). ### Breaking Changes -* lang: Change `#[account(init, seeds = [...], token = , authority = )]` to `#[account(init, token::mint = token::authority = )]` ([#](https://github.com/project-serum/anchor/pull/562)). -* lang: `#[associated]` and `#[account(associated = , with = )]` are both removed. -* cli: Removed `anchor launch` command +* lang: Change `#[account(init, seeds = [...], token = , authority = )]` to `#[account(init, token::mint = token::authority = )]` ([#562](https://github.com/project-serum/anchor/pull/562)). +* lang: `#[associated]` and `#[account(associated = , with = )]` 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 diff --git a/examples/cashiers-check/programs/cashiers-check/src/lib.rs b/examples/cashiers-check/programs/cashiers-check/src/lib.rs index bc9a4947d5..e71f51e25e 100644 --- a/examples/cashiers-check/programs/cashiers-check/src/lib.rs +++ b/examples/cashiers-check/programs/cashiers-check/src/lib.rs @@ -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>, diff --git a/examples/cfo/deps/stake b/examples/cfo/deps/stake index 3dc83f47b6..9a257678df 160000 --- a/examples/cfo/deps/stake +++ b/examples/cfo/deps/stake @@ -1 +1 @@ -Subproject commit 3dc83f47b66a8a4189a637368c49dca1341c6b23 +Subproject commit 9a257678dfd0bda0c222e516e8c1a778b401d71e diff --git a/examples/cfo/programs/cfo/src/lib.rs b/examples/cfo/programs/cfo/src/lib.rs index ecd05c2579..b67ccada8e 100644 --- a/examples/cfo/programs/cfo/src/lib.rs +++ b/examples/cfo/programs/cfo/src/lib.rs @@ -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( diff --git a/examples/chat/programs/chat/src/lib.rs b/examples/chat/programs/chat/src/lib.rs index 05e29c86e9..8f0835239c 100644 --- a/examples/chat/programs/chat/src/lib.rs +++ b/examples/chat/programs/chat/src/lib.rs @@ -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>, diff --git a/examples/ido-pool/programs/ido-pool/src/lib.rs b/examples/ido-pool/programs/ido-pool/src/lib.rs index a079a61873..7286950b44 100644 --- a/examples/ido-pool/programs/ido-pool/src/lib.rs +++ b/examples/ido-pool/programs/ido-pool/src/lib.rs @@ -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>, diff --git a/examples/lockup/programs/lockup/src/lib.rs b/examples/lockup/programs/lockup/src/lib.rs index 63b65891e6..00d1f5440f 100644 --- a/examples/lockup/programs/lockup/src/lib.rs +++ b/examples/lockup/programs/lockup/src/lib.rs @@ -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>, diff --git a/examples/lockup/programs/registry/src/lib.rs b/examples/lockup/programs/registry/src/lib.rs index 2068f85504..a05e68fd1c 100644 --- a/examples/lockup/programs/registry/src/lib.rs +++ b/examples/lockup/programs/registry/src/lib.rs @@ -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 == ®istry.lockup_program", - "vesting.beneficiary == member.beneficiary" + constraint = vesting.to_account_info().owner == ®istry.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 == ®istry.lockup_program", - "vesting.beneficiary == member.beneficiary" + constraint = vesting.to_account_info().owner == ®istry.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>, } diff --git a/examples/misc/programs/misc/src/account.rs b/examples/misc/programs/misc/src/account.rs index 381904f063..d65f93be7c 100644 --- a/examples/misc/programs/misc/src/account.rs +++ b/examples/misc/programs/misc/src/account.rs @@ -13,6 +13,7 @@ pub struct DataU16 { } #[account] +#[derive(Default)] pub struct DataI8 { pub data: i8, } diff --git a/examples/misc/programs/misc/src/context.rs b/examples/misc/programs/misc/src/context.rs index 7a1a2525f5..c271c4cff1 100644 --- a/examples/misc/programs/misc/src/context.rs +++ b/examples/misc/programs/misc/src/context.rs @@ -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::())] + 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>, +} diff --git a/examples/misc/programs/misc/src/lib.rs b/examples/misc/programs/misc/src/lib.rs index e580f4ed6b..d80f45641b 100644 --- a/examples/misc/programs/misc/src/lib.rs +++ b/examples/misc/programs/misc/src/lib.rs @@ -128,4 +128,26 @@ pub mod misc { ) -> ProgramResult { Err(ProgramError::Custom(1234)) } + + pub fn test_init(ctx: Context) -> ProgramResult { + ctx.accounts.data.data = 3; + Ok(()) + } + + pub fn test_init_zero_copy(ctx: Context) -> ProgramResult { + let mut data = ctx.accounts.data.load_init()?; + data.data = 10; + data.bump = 2; + Ok(()) + } + + pub fn test_init_mint(ctx: Context) -> ProgramResult { + assert!(ctx.accounts.mint.decimals == 6); + Ok(()) + } + + pub fn test_init_token(ctx: Context) -> ProgramResult { + assert!(ctx.accounts.token.mint == ctx.accounts.mint.key()); + Ok(()) + } } diff --git a/examples/misc/tests/misc.js b/examples/misc/tests/misc.js index 2095bc573c..ead0ed537e 100644 --- a/examples/misc/tests/misc.js +++ b/examples/misc/tests/misc.js @@ -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)); + }); }); diff --git a/examples/multisig/programs/multisig/src/lib.rs b/examples/multisig/programs/multisig/src/lib.rs index 9942abf0c3..06ec89440c 100644 --- a/examples/multisig/programs/multisig/src/lib.rs +++ b/examples/multisig/programs/multisig/src/lib.rs @@ -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>, diff --git a/lang/derive/accounts/src/lib.rs b/lang/derive/accounts/src/lib.rs index 7410c4f19a..1cfe636b77 100644 --- a/lang/derive/accounts/src/lib.rs +++ b/lang/derive/accounts/src/lib.rs @@ -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 = )]` | 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 . | /// | `#[account(has_one = )]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. | /// | `#[account(seeds = [], bump? = , payer? = , space? = , owner? = )]` | 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`.| diff --git a/lang/src/account_info.rs b/lang/src/account_info.rs index 9308fa14b9..b92dda33a0 100644 --- a/lang/src/account_info.rs +++ b/lang/src/account_info.rs @@ -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 { - 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) -> Vec { let is_signer = is_signer.unwrap_or(self.is_signer); diff --git a/lang/src/cpi_account.rs b/lang/src/cpi_account.rs index 7a2cedf64a..a69fd8401e 100644 --- a/lang/src/cpi_account.rs +++ b/lang/src/cpi_account.rs @@ -30,7 +30,7 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> { )) } - pub fn try_from_init(info: &AccountInfo<'a>) -> Result, ProgramError> { + pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result, ProgramError> { Self::try_from(info) } diff --git a/lang/src/error.rs b/lang/src/error.rs index efd0f1fe7c..7f7fea74f1 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -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")] diff --git a/lang/src/idl.rs b/lang/src/idl.rs index 9535651106..8e79b8b8f8 100644 --- a/lang/src/idl.rs +++ b/lang/src/idl.rs @@ -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>, diff --git a/lang/src/lib.rs b/lang/src/lib.rs index aa4090f966..bb3648d486 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -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; -} - /// 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; diff --git a/lang/src/loader.rs b/lang/src/loader.rs index 48e96a103e..e4bdb4535b 100644 --- a/lang/src/loader.rs +++ b/lang/src/loader.rs @@ -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, 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, 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 { - 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 { diff --git a/lang/src/program_account.rs b/lang/src/program_account.rs index d74135befd..814c67ce5c 100644 --- a/lang/src/program_account.rs +++ b/lang/src/program_account.rs @@ -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, ProgramError> { + pub fn try_from_unchecked( + info: &AccountInfo<'a>, + ) -> Result, 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 { - 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> { diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 21fab6f41e..9b4289eb97 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -60,14 +60,14 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec { 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] - } - } - // Bump keyword given. - Some(bump) => match bump { - // Bump target not given. Find it. - None => { - quote! { - [ - #s, - &[ - Pubkey::find_program_address( - &[#s], - program_id, - ).1 - ] + [ + #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, - 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 diff --git a/lang/syn/src/codegen/accounts/try_accounts.rs b/lang/syn/src/codegen/accounts/try_accounts.rs index f378ead7b8..f3daddf781 100644 --- a/lang/syn/src/codegen/accounts/try_accounts.rs +++ b/lang/syn/src/codegen/accounts/try_accounts.rs @@ -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 = accs + let init_fields: Vec = 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 = non_pda_fields + let access_checks: Vec = 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(), + } +} diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index 7148d179b5..f36a303368 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -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 diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 0e69da6d8a..b148e9fd82 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -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, + init: Option, zeroed: Option, mutable: Option, signer: Option, @@ -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, + pub payer: Option, + pub space: Option, + pub kind: InitKind, +} + #[derive(Debug, Clone)] pub struct ConstraintSeedsGroup { pub is_init: bool, pub seeds: Punctuated, - pub payer: Option, - pub space: Option, - pub kind: PdaKind, - // Some(None) => bump was given without a target. - pub bump: Option>, + pub bump: Option, // 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 }, Token { owner: Expr, mint: Expr }, Mint { owner: Expr, decimals: Expr }, diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index 6cea62da18..791350fd5b 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -279,8 +279,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> { bump: None, } } + pub fn build(mut self) -> ParseResult { - // 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) -> 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) -> ParseResult<()> { - if self.seeds.is_none() { + if self.init.is_none() { return Err(ParseError::new( c.span(), "init must be provided before space",