From e3e5a87fe533c68386b8d926e30ad34c715fc495 Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 10 Dec 2024 17:04:22 +0100 Subject: [PATCH 1/4] lang: Deduplicate `zero` accounts against `init` accounts --- lang/syn/src/codegen/accounts/constraints.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 53a883a47d..5625c51172 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/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! { From 1225e7e17c94a40fd871febd4fc7e2a171c76ff3 Mon Sep 17 00:00:00 2001 From: acheron Date: Tue, 10 Dec 2024 17:36:59 +0100 Subject: [PATCH 2/4] tests: Add tests --- .../programs/misc-optional/src/context.rs | 11 ++++ tests/misc/programs/misc-optional/src/lib.rs | 4 ++ tests/misc/programs/misc/src/context.rs | 11 ++++ tests/misc/programs/misc/src/lib.rs | 4 ++ tests/misc/tests/misc/misc.ts | 53 ++++++++++++++----- 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/tests/misc/programs/misc-optional/src/context.rs b/tests/misc/programs/misc-optional/src/context.rs index a4799ecf85..1a06393a95 100644 --- a/tests/misc/programs/misc-optional/src/context.rs +++ b/tests/misc/programs/misc-optional/src/context.rs @@ -752,3 +752,14 @@ pub struct TestMultipleZeroConstraint<'info> { #[account(zero)] pub two: Option>, } + +#[derive(Accounts)] +pub struct TestInitAndZero<'info> { + #[account(init, payer = payer, space = Data::DISCRIMINATOR.len() + Data::LEN)] + pub init: Option>, + #[account(zero)] + pub zero: Option>, + #[account(mut)] + pub payer: Option>, + pub system_program: Option>, +} diff --git a/tests/misc/programs/misc-optional/src/lib.rs b/tests/misc/programs/misc-optional/src/lib.rs index 886f2a6847..52285c8939 100644 --- a/tests/misc/programs/misc-optional/src/lib.rs +++ b/tests/misc/programs/misc-optional/src/lib.rs @@ -406,4 +406,8 @@ pub mod misc_optional { pub fn test_multiple_zero_constraint(_ctx: Context) -> Result<()> { Ok(()) } + + pub fn test_init_and_zero(_ctx: Context) -> Result<()> { + Ok(()) + } } diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index 6b540e9c33..d19c84c562 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/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>, +} diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index a97ddfdef9..1bd8a82fb3 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -405,4 +405,8 @@ pub mod misc { pub fn test_multiple_zero_constraint(_ctx: Context) -> Result<()> { Ok(()) } + + pub fn test_init_and_zero(_ctx: Context) -> Result<()> { + Ok(()) + } } diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index d4f96320a5..7b2837eeac 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -3241,8 +3241,8 @@ const miscTest = ( }); }); - describe("Multiple `zero` constraint", () => { - it("Passing different accounts works", async () => { + describe.only("`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; From c594f149ac98d17408061a50722fd1436a8a2027 Mon Sep 17 00:00:00 2001 From: acheron Date: Wed, 11 Dec 2024 06:42:52 +0100 Subject: [PATCH 3/4] tests: Remove `describe.only` --- tests/misc/tests/misc/misc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/misc/tests/misc/misc.ts b/tests/misc/tests/misc/misc.ts index 7b2837eeac..257f3507cc 100644 --- a/tests/misc/tests/misc/misc.ts +++ b/tests/misc/tests/misc/misc.ts @@ -3241,7 +3241,7 @@ const miscTest = ( }); }); - describe.only("`zero` constraint unique account checks", () => { + 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(); From f6cafa9075d47eaf70f10de8e1ba077cdc9f8a77 Mon Sep 17 00:00:00 2001 From: acheron Date: Thu, 12 Dec 2024 05:52:48 +0100 Subject: [PATCH 4/4] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc9d6c05ee..e15cb1432d 100644 --- a/CHANGELOG.md +++ b/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