Просмотр исходного кода

lang: Consistent init constraints (#641)

Armani Ferrante 4 лет назад
Родитель
Сommit
75c20856e4

+ 9 - 5
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 = <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
 

+ 10 - 10
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>,

+ 1 - 1
examples/cfo/deps/stake

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

+ 14 - 4
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(

+ 2 - 1
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>,

+ 16 - 4
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>,

+ 11 - 5
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>,

+ 54 - 75
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 == &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>,
 }

+ 1 - 0
examples/misc/programs/misc/src/account.rs

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

+ 64 - 8
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::<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>,
+}

+ 22 - 0
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<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(())
+    }
 }

+ 84 - 0
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));
+  });
 });

+ 9 - 8
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>,

+ 2 - 1
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 = <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`.|

+ 1 - 26
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<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);

+ 1 - 1
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<CpiAccount<'a, T>, ProgramError> {
+    pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
         Self::try_from(info)
     }
 

+ 2 - 0
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")]

+ 1 - 1
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>,

+ 3 - 15
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<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;

+ 4 - 32
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<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 {

+ 5 - 34
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<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>
 {

+ 70 - 87
lang/syn/src/codegen/accounts/constraints.rs

@@ -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]
-                }
-            }
-            // 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<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

+ 21 - 30
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<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(),
+    }
+}

+ 1 - 1
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

+ 12 - 12
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<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 },

+ 67 - 47
lang/syn/src/parser/accounts/constraints.rs

@@ -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",