Browse Source

lang: Deduplicate `zero` accounts against `init` accounts (#3422)

acheron 10 tháng trước cách đây
mục cha
commit
2ae3774639

+ 1 - 0
CHANGELOG.md

@@ -101,6 +101,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - idl: Ignore compiler warnings during builds ([#3396](https://github.com/coral-xyz/anchor/pull/3396)).
 - cli: Avoid extra IDL generation during `verify` ([#3398](https://github.com/coral-xyz/anchor/pull/3398)).
 - lang: Require `zero` accounts to be unique ([#3409](https://github.com/coral-xyz/anchor/pull/3409)).
+- lang: Deduplicate `zero` accounts against `init` accounts ([#3422](https://github.com/coral-xyz/anchor/pull/3422)).
 
 ### Breaking
 

+ 3 - 3
lang/syn/src/codegen/accounts/constraints.rs

@@ -212,8 +212,8 @@ pub fn generate_constraint_zeroed(
 
     // Require `zero` constraint accounts to be unique by:
     //
-    // 1. Getting the names of all accounts that have the `zero` constraint and are declared before
-    //    the current field (in order to avoid checking the same field).
+    // 1. Getting the names of all accounts that have the `zero` or the `init` constraints and are
+    //    declared before the current field (in order to avoid checking the same field).
     // 2. Comparing the key of the current field with all the previous fields' keys.
     // 3. Returning an error if a match is found.
     let unique_account_checks = accs
@@ -224,7 +224,7 @@ pub fn generate_constraint_zeroed(
             _ => None,
         })
         .take_while(|field| field.ident != f.ident)
-        .filter(|field| field.constraints.is_zeroed())
+        .filter(|field| field.constraints.is_zeroed() || field.constraints.init.is_some())
         .map(|other_field| {
             let other = &other_field.ident;
             let err = quote! {

+ 11 - 0
tests/misc/programs/misc-optional/src/context.rs

@@ -752,3 +752,14 @@ pub struct TestMultipleZeroConstraint<'info> {
     #[account(zero)]
     pub two: Option<Account<'info, Data>>,
 }
+
+#[derive(Accounts)]
+pub struct TestInitAndZero<'info> {
+    #[account(init, payer = payer, space = Data::DISCRIMINATOR.len() + Data::LEN)]
+    pub init: Option<Account<'info, Data>>,
+    #[account(zero)]
+    pub zero: Option<Account<'info, Data>>,
+    #[account(mut)]
+    pub payer: Option<Signer<'info>>,
+    pub system_program: Option<Program<'info, System>>,
+}

+ 4 - 0
tests/misc/programs/misc-optional/src/lib.rs

@@ -406,4 +406,8 @@ pub mod misc_optional {
     pub fn test_multiple_zero_constraint(_ctx: Context<TestMultipleZeroConstraint>) -> Result<()> {
         Ok(())
     }
+
+    pub fn test_init_and_zero(_ctx: Context<TestInitAndZero>) -> Result<()> {
+        Ok(())
+    }
 }

+ 11 - 0
tests/misc/programs/misc/src/context.rs

@@ -824,3 +824,14 @@ pub struct TestMultipleZeroConstraint<'info> {
     #[account(zero)]
     pub two: Account<'info, Data>,
 }
+
+#[derive(Accounts)]
+pub struct TestInitAndZero<'info> {
+    #[account(init, payer = payer, space = Data::DISCRIMINATOR.len() + Data::LEN)]
+    pub init: Account<'info, Data>,
+    #[account(zero)]
+    pub zero: Account<'info, Data>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}

+ 4 - 0
tests/misc/programs/misc/src/lib.rs

@@ -405,4 +405,8 @@ pub mod misc {
     pub fn test_multiple_zero_constraint(_ctx: Context<TestMultipleZeroConstraint>) -> Result<()> {
         Ok(())
     }
+
+    pub fn test_init_and_zero(_ctx: Context<TestInitAndZero>) -> Result<()> {
+        Ok(())
+    }
 }

+ 40 - 13
tests/misc/tests/misc/misc.ts

@@ -3241,8 +3241,8 @@ const miscTest = (
       });
     });
 
-    describe("Multiple `zero` constraint", () => {
-      it("Passing different accounts works", async () => {
+    describe("`zero` constraint unique account checks", () => {
+      it("Works with different accounts (multiple `zero`)", async () => {
         const oneKp = anchor.web3.Keypair.generate();
         const twoKp = anchor.web3.Keypair.generate();
         await program.methods
@@ -3258,21 +3258,48 @@ const miscTest = (
           .rpc();
       });
 
-      it("Passing the same account throws", async () => {
-        const oneKp = anchor.web3.Keypair.generate();
+      it("Throws with the same account (multiple `zero`)", async () => {
+        const kp = anchor.web3.Keypair.generate();
         try {
           await program.methods
             .testMultipleZeroConstraint()
-            .preInstructions([
-              await program.account.data.createInstruction(oneKp),
-            ])
-            .accounts({
-              one: oneKp.publicKey,
-              two: oneKp.publicKey,
-            })
-            .signers([oneKp])
+            .preInstructions([await program.account.data.createInstruction(kp)])
+            .accounts({ one: kp.publicKey, two: kp.publicKey })
+            .signers([kp])
+            .rpc();
+          assert.fail("Transaction did not fail!");
+        } catch (e) {
+          assert(e instanceof AnchorError);
+          const err: AnchorError = e;
+          assert.strictEqual(
+            err.error.errorCode.number,
+            anchor.LangErrorCode.ConstraintZero
+          );
+        }
+      });
+
+      it("Works with different accounts (`init` and `zero`)", async () => {
+        const initKp = anchor.web3.Keypair.generate();
+        const zeroKp = anchor.web3.Keypair.generate();
+        await program.methods
+          .testInitAndZero()
+          .preInstructions([
+            await program.account.data.createInstruction(zeroKp),
+          ])
+          .accounts({ init: initKp.publicKey, zero: zeroKp.publicKey })
+          .signers([initKp, zeroKp])
+          .rpc();
+      });
+
+      it("Throws with the same account (`init` and `zero`)", async () => {
+        const kp = anchor.web3.Keypair.generate();
+        try {
+          await program.methods
+            .testInitAndZero()
+            .accounts({ init: kp.publicKey, zero: kp.publicKey })
+            .signers([kp])
             .rpc();
-          throw new Error("Transaction did not fail!");
+          assert.fail("Transaction did not fail!");
         } catch (e) {
           assert(e instanceof AnchorError);
           const err: AnchorError = e;