Skip to content

Commit

Permalink
lang: Deduplicate zero accounts against init accounts (#3422)
Browse files Browse the repository at this point in the history
  • Loading branch information
acheroncrypto authored Dec 12, 2024
1 parent 955e7ea commit 2ae3774
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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! {
Expand Down
11 changes: 11 additions & 0 deletions tests/misc/programs/misc-optional/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 4 additions & 0 deletions tests/misc/programs/misc-optional/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 11 additions & 0 deletions tests/misc/programs/misc/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 4 additions & 0 deletions tests/misc/programs/misc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
53 changes: 40 additions & 13 deletions tests/misc/tests/misc/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down

0 comments on commit 2ae3774

Please sign in to comment.