Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: Deduplicate zero accounts against init accounts #3422

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading