diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index e6a101e15238..dc6b15ccf087 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -1,6 +1,8 @@ // A demonstration of inclusion and non-inclusion proofs. use dep::aztec::macros::aztec; +mod test; + #[aztec] pub contract InclusionProofs { use dep::aztec::encrypted_logs::log_assembly_strategies::default_aes128::note::encode_and_encrypt_note; @@ -8,7 +10,7 @@ pub contract InclusionProofs { use dep::aztec::{ macros::{functions::{initializer, private, public}, storage::storage}, - note::note_getter_options::NoteStatus, + note::{note_getter_options::NoteStatus, note_viewer_options::NoteViewerOptions}, }; // docs:start:value_note_imports use dep::value_note::value_note::ValueNote; @@ -40,6 +42,13 @@ pub contract InclusionProofs { } // docs:end:create_note + unconstrained fn get_note(owner: AztecAddress) -> ValueNote { + let options = NoteViewerOptions::new().set_limit(1); + let note = storage.private_values.at(owner).view_notes(options).get(0); + + note + } + #[private] fn test_note_inclusion( owner: AztecAddress, diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/test.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/test.nr new file mode 100644 index 000000000000..54e3a26fc45c --- /dev/null +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/test.nr @@ -0,0 +1,366 @@ +use crate::InclusionProofs; +use dep::aztec::{ + oracle::execution::get_contract_address, + prelude::AztecAddress, + test::helpers::{cheatcodes, test_environment::TestEnvironment}, +}; + +global INITIAL_VALUE: Field = 69; + +pub unconstrained fn setup( + initial_value: Field, +) -> (&mut TestEnvironment, AztecAddress, AztecAddress) { + // Setup env, generate keys + let mut env = TestEnvironment::new(); + let owner = env.create_account(1); + env.impersonate(owner); + + // We advance one block here, because we want to deploy our contract in block 3. + // This is because we will do tests later that prove the non inclusion of values and of the contract itself at block 2. + env.advance_block_by(1); + + // Deploy contract and initialize + let initializer = InclusionProofs::interface().constructor(initial_value); + let inclusion_proofs_contract = + env.deploy_self("InclusionProofs").with_public_void_initializer(initializer); + let contract_address = inclusion_proofs_contract.to_address(); + env.advance_block_by(1); + (&mut env, contract_address, owner) +} + +#[test] +unconstrained fn note_flow() { + // This test creates a note and checks certain properties about it, namely its inclusion in the trees, + // the non-inclusion of its nullifier, and its validity (the combination of the previous two) + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let NOTE_VALUE = 69; + InclusionProofs::at(contract_address).create_note(owner, NOTE_VALUE).call(&mut env.private()); + + env.advance_block_by(1); + + let note_creation_block_number = env.block_number(); + + // We advance by another block to make sure that the note creation block number != our current block number + env.advance_block_by(1); + + let current_contract_address = get_contract_address(); + cheatcodes::set_contract_address(contract_address); + + // We fetch the note we created and make sure that it is valid. + let note = InclusionProofs::get_note(owner); + cheatcodes::set_contract_address(current_contract_address); + + assert(note.owner.eq(owner)); + assert(note.value.eq(NOTE_VALUE)); + + // Each of these tests (note inclusion, note non-nullification, and validity (inclusion & non-nullification)) check the assertion at the block of creation of note, as well as at the "current" block + InclusionProofs::at(contract_address) + .test_note_inclusion(owner, true, note_creation_block_number, false) + .call(&mut env.private()); + InclusionProofs::at(contract_address).test_note_inclusion(owner, false, 0, false).call( + &mut env.private(), + ); + + InclusionProofs::at(contract_address) + .test_note_not_nullified(owner, true, note_creation_block_number, false) + .call(&mut env.private()); + InclusionProofs::at(contract_address).test_note_not_nullified(owner, false, 0, false).call( + &mut env.private(), + ); + + InclusionProofs::at(contract_address) + .test_note_validity(owner, true, note_creation_block_number, false) + .call(&mut env.private()); + InclusionProofs::at(contract_address).test_note_validity(owner, false, 0, false).call( + &mut env.private(), + ); +} + +#[test] +unconstrained fn nullify_note_flow() { + // This test creates a note, nullifies it, and checks certain properties about it, namely its inclusion in the trees, + // the non-inclusion of its nullifier, and its validity (the combination of the previous two). These properties are checked before nullification. + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + InclusionProofs::at(contract_address).create_note(owner, 5).call(&mut env.private()); + + env.advance_block_by(1); + + let note_valid_block_number = env.block_number(); + + InclusionProofs::at(contract_address).nullify_note(owner).call(&mut env.private()); + + env.advance_block_by(1); + + // We test note inclusion at the note creation block and at current block + InclusionProofs::at(contract_address) + .test_note_inclusion(owner, true, note_valid_block_number, true) + .call(&mut env.private()); + + InclusionProofs::at(contract_address).test_note_inclusion(owner, false, 0, true).call( + &mut env.private(), + ); + + // We test note non-nullification and validity at the note creation block + InclusionProofs::at(contract_address) + .test_note_not_nullified(owner, true, note_valid_block_number, true) + .call(&mut env.private()); + InclusionProofs::at(contract_address) + .test_note_validity(owner, true, note_valid_block_number, true) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed")] +unconstrained fn note_not_nullified_after_nullified() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + InclusionProofs::at(contract_address).create_note(owner, 5).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).nullify_note(owner).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address) + .test_note_not_nullified(owner, true, env.block_number(), true) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed")] +unconstrained fn note_not_nullified_after_nullified_no_block_number() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + InclusionProofs::at(contract_address).create_note(owner, 5).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).nullify_note(owner).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).test_note_not_nullified(owner, false, 0, true).call( + &mut env.private(), + ); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed")] +unconstrained fn validity_after_nullified() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + InclusionProofs::at(contract_address).create_note(owner, 5).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).nullify_note(owner).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address) + .test_note_validity(owner, true, env.block_number(), true) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed")] +unconstrained fn validity_after_nullified_no_block_number() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + InclusionProofs::at(contract_address).create_note(owner, 5).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).nullify_note(owner).call(&mut env.private()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address).test_note_validity(owner, false, 0, true).call( + &mut env.private(), + ); +} + +#[test(should_fail_with = "not found in NOTE_HASH_TREE")] +unconstrained fn note_inclusion_fail_case() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let random_owner = AztecAddress::from_field(dep::aztec::oracle::random::random()); + + let block_number = env.block_number(); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address) + .test_note_inclusion_fail_case(random_owner, true, block_number) + .call(&mut env.private()); +} + +#[test(should_fail_with = "not found in NOTE_HASH_TREE")] +unconstrained fn note_inclusion_fail_case_no_block_number() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let random_owner = AztecAddress::from_field(dep::aztec::oracle::random::random()); + + env.advance_block_by(1); + + InclusionProofs::at(contract_address) + .test_note_inclusion_fail_case(random_owner, false, 0) + .call(&mut env.private()); +} + +#[test] +unconstrained fn nullifier_inclusion() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + // The first nullifier emitted + let nullifier = 6969 + 1; + + InclusionProofs::at(contract_address) + .test_nullifier_inclusion(nullifier, true, env.block_number() - 1) + .call(&mut env.private()); + + InclusionProofs::at(contract_address).test_nullifier_inclusion(nullifier, false, 0).call( + &mut env.private(), + ); +} + +#[test] +unconstrained fn nullifier_inclusion_public() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let unsiloed_nullifier = 0xffffff; + InclusionProofs::at(contract_address).push_nullifier_public(unsiloed_nullifier).call( + &mut env.public(), + ); + + env.advance_block_by(1); + InclusionProofs::at(contract_address) + .test_nullifier_inclusion_from_public(unsiloed_nullifier) + .call(&mut env.public()); +} + +#[test(should_fail_with = "Nullifier witness not found for nullifier")] +unconstrained fn nullifier_non_existence() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let block_number = env.block_number() - 1; + + let random_nullifier = dep::aztec::oracle::random::random(); + + InclusionProofs::at(contract_address) + .test_nullifier_inclusion(random_nullifier, true, block_number) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Nullifier witness not found for nullifier")] +unconstrained fn nullifier_non_existence_no_block_number() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let random_nullifier = dep::aztec::oracle::random::random(); + + InclusionProofs::at(contract_address).test_nullifier_inclusion(random_nullifier, false, 0).call( + &mut env.private(), + ); +} + +#[test] +unconstrained fn historical_reads() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let block_number = env.block_number(); + + InclusionProofs::at(contract_address) + .test_storage_historical_read(INITIAL_VALUE, true, block_number) + .call(&mut env.private()); + InclusionProofs::at(contract_address) + .test_storage_historical_read(INITIAL_VALUE, false, 0) + .call(&mut env.private()); + + // We are using block number 2 because we know the public value has not been set at this point. + InclusionProofs::at(contract_address).test_storage_historical_read(0, true, 2).call( + &mut env.private(), + ); + + InclusionProofs::at(contract_address) + .test_storage_historical_read_unset_slot(block_number) + .call(&mut env.private()); +} + +#[test] +unconstrained fn contract_flow() { + // This test deploys a contract and tests for its inclusion of deployment and initialization nullifier. It also checks non-inclusion before its deployment. + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let block_number: u32 = env.block_number(); + + InclusionProofs::at(contract_address) + .test_contract_inclusion(contract_address, block_number, true, true) + .call(&mut env.private()); + + // We are using block number 2 because we know the contract has not been deployed nor initialized at this point. + InclusionProofs::at(contract_address) + .test_contract_non_inclusion(contract_address, 2, true, true) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Nullifier witness not found for nullifier")] +unconstrained fn contract_not_initialized() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + // We are using block number 2 because we know the contract has not been deployed nor initialized at this point. + // We split the deployment check and the initialization check (true, false), because each one checks for the inclusion of the deployment / initialization nullifier specifically; + // We can't set `true, true` to check for both in one test because the first failing condition won't let us check the other in isolation. + // The above statements apply to the rest of the contract inclusion tests. + InclusionProofs::at(contract_address) + .test_contract_inclusion(contract_address, 2, true, false) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Nullifier witness not found for nullifier")] +unconstrained fn contract_not_deployed() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + // This checks for the inclusion of the initializer nullifier specifically. + InclusionProofs::at(contract_address) + .test_contract_inclusion(contract_address, 2, false, true) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed")] +unconstrained fn contract_deployed_non_inclusion() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let block_number: u32 = env.block_number(); + + InclusionProofs::at(contract_address) + .test_contract_non_inclusion(contract_address, block_number, true, false) + .call(&mut env.private()); +} + +#[test(should_fail_with = "Assertion failed: Proving nullifier non-inclusion failed")] +unconstrained fn contract_initialized_non_inclusion() { + let (env, contract_address, owner) = setup(INITIAL_VALUE); + env.impersonate(owner); + + let block_number: u32 = env.block_number(); + + InclusionProofs::at(contract_address) + .test_contract_non_inclusion(contract_address, block_number, false, true) + .call(&mut env.private()); +} diff --git a/yarn-project/end-to-end/src/flakey_e2e_inclusion_proofs_contract.test.ts b/yarn-project/end-to-end/src/flakey_e2e_inclusion_proofs_contract.test.ts deleted file mode 100644 index 678d2f548f79..000000000000 --- a/yarn-project/end-to-end/src/flakey_e2e_inclusion_proofs_contract.test.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { - type AccountWallet, - AztecAddress, - type ExtendedNote, - Fr, - INITIAL_L2_BLOCK_NUM, - type PXE, - getContractInstanceFromDeployParams, -} from '@aztec/aztec.js'; -import { deployInstance, registerContractClass } from '@aztec/aztec.js/deployment'; -import { randomInt } from '@aztec/foundation/crypto'; -import { InclusionProofsContract } from '@aztec/noir-contracts.js/InclusionProofs'; -import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js/StatefulTest'; - -import { jest } from '@jest/globals'; -import { type MemDown, default as memdown } from 'memdown'; - -import { setup } from './fixtures/utils.js'; - -export const createMemDown = () => (memdown as any)() as MemDown; - -const TIMEOUT = 90_000; - -describe('e2e_inclusion_proofs_contract', () => { - jest.setTimeout(TIMEOUT); - - let pxe: PXE; - let teardown: () => Promise; - let wallets: AccountWallet[]; - - let contract: InclusionProofsContract; - let deploymentBlockNumber: number; - const publicValue = 236n; - const contractAddressSalt = Fr.random(); - - beforeAll(async () => { - ({ pxe, teardown, wallets } = await setup(1)); - - const receipt = await InclusionProofsContract.deploy(wallets[0], publicValue).send({ contractAddressSalt }).wait(); - contract = receipt.contract; - deploymentBlockNumber = receipt.blockNumber!; - }); - - afterAll(() => teardown()); - - describe('note inclusion and nullifier non-inclusion', () => { - let owner: AztecAddress; - - beforeAll(() => { - owner = wallets[0].getAddress(); - }); - - describe('proves note existence and its nullifier non-existence and nullifier non-existence failure case', () => { - // Owner of a note - let noteCreationBlockNumber: number; - let noteHashes, visibleNotes: ExtendedNote[]; - const value = 100n; - let validNoteBlockNumber: any; - - it('should return the correct values for creating a note', async () => { - // Create a note - const receipt = await contract.methods.create_note(owner, value).send().wait({ debug: true }); - - noteCreationBlockNumber = receipt.blockNumber!; - ({ noteHashes } = receipt.debugInfo!); - - await contract.methods.sync_notes().simulate(); - visibleNotes = await wallets[0].getNotes({ txHash: receipt.txHash }); - }); - - it('should return the correct values for creating a note', () => { - expect(noteHashes.length).toBe(1); - expect(visibleNotes.length).toBe(1); - const [receivedValue, receivedOwner, _randomness] = visibleNotes[0].note.items; - expect(receivedValue.toBigInt()).toBe(value); - expect(receivedOwner).toEqual(owner.toField()); - }); - - it('should not throw because the note is included', async () => { - // Prove note inclusion in a given block. - await contract.methods.test_note_inclusion(owner, true, noteCreationBlockNumber, false).send().wait(); - - await contract.methods.test_note_inclusion(owner, false, 0n, false).send().wait(); - }); - - it('should not throw because the note is not nullified', async () => { - // Prove that the note has not been nullified with block_number - // TODO(#3535): Prove the nullifier non-inclusion at older block to test archival node. This is currently not - // possible because of issue https://github.com/AztecProtocol/aztec-packages/issues/3535 - const blockNumber = await pxe.getBlockNumber(); - await contract.methods.test_note_not_nullified(owner, true, blockNumber, false).send().wait(); - await contract.methods.test_note_not_nullified(owner, false, 0n, false).send().wait(); - }); - - it('should not throw because is both included, not nullified, and therefore valid', async () => { - validNoteBlockNumber = await pxe.getBlockNumber(); - await contract.methods.test_note_validity(owner, true, validNoteBlockNumber, false).send().wait(); - await contract.methods.test_note_validity(owner, false, 0n, false).send().wait(); - }); - - describe('we will test the failure case by nullifying a note', () => { - let currentBlockNumber: number; - - // We test the failure case now --> The proof should fail when the nullifier already exists - it('nullifies a note and grabs block number', async () => { - const { debugInfo } = await contract.methods.nullify_note(owner).send().wait({ debug: true }); - currentBlockNumber = await pxe.getBlockNumber(); - - expect(debugInfo!.nullifiers.length).toBe(2); - }); - - // Note: getLowNullifierMembershipWitness returns the membership witness of the nullifier itself and not - // the low nullifier when the nullifier already exists in the tree and for this reason the execution fails - // on low_nullifier.value < nullifier.value check. - it('should throw when testing if note is not nullified at the current block', async () => { - await expect( - contract.methods.test_note_not_nullified(owner, true, currentBlockNumber, true).send().wait(), - ).rejects.toThrow( - /Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/, - ); - await expect(contract.methods.test_note_not_nullified(owner, false, 0n, true).send().wait()).rejects.toThrow( - /Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/, - ); - }); - - it('should not throw when we test inclusion of nullified note', async () => { - await contract.methods.test_note_inclusion(owner, true, noteCreationBlockNumber, true).send().wait(); - - await contract.methods.test_note_inclusion(owner, false, 0n, true).send().wait(); - }); - - it('should throw when we test validity', async () => { - const blockNumber = await pxe.getBlockNumber(); - await expect( - contract.methods.test_note_validity(owner, true, blockNumber, true).send().wait(), - ).rejects.toThrow( - /Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/, - ); - await expect(contract.methods.test_note_validity(owner, false, 0n, true).send().wait()).rejects.toThrow( - /Proving nullifier non-inclusion failed: low_nullifier.value < nullifier.value check failed/, - ); - }); - - it('should not throw because the note was not nullified yet at validNoteBlockNumber', async () => { - await contract.methods.test_note_not_nullified(owner, true, validNoteBlockNumber, true).send().wait(); - await contract.methods.test_note_validity(owner, true, validNoteBlockNumber, true).send().wait(); - }); - }); - }); - - it('proves note validity (note hash inclusion and nullifier non-inclusion)', async () => { - // Owner of a note - const owner = wallets[0].getAddress(); - let noteCreationBlockNumber: number; - { - // Create a note - const value = 100n; - const receipt = await contract.methods.create_note(owner, value).send().wait({ debug: true }); - - noteCreationBlockNumber = receipt.blockNumber!; - const { noteHashes } = receipt.debugInfo!; - - await contract.methods.sync_notes().simulate(); - const visibleNotes = await wallets[0].getNotes({ txHash: receipt.txHash }); - - expect(noteHashes.length).toBe(1); - expect(visibleNotes.length).toBe(1); - const [receivedValue, receivedOwner, _randomness] = visibleNotes[0].note.items; - expect(receivedValue.toBigInt()).toBe(value); - expect(receivedOwner).toEqual(owner.toField()); - } - - { - // Prove note validity - await contract.methods.test_note_validity(owner, true, noteCreationBlockNumber, false).send().wait(); - await contract.methods.test_note_validity(owner, false, 0n, false).send().wait(); - } - }); - - it('note existence failure case', async () => { - // Owner of a note - ignored in the contract since the note won't be found and the spare random note commitment - // will be used instead - const owner = AztecAddress.fromField(new Fr(88n)); - - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); - - await expect( - contract.methods.test_note_inclusion_fail_case(owner, true, blockNumber).send().wait(), - ).rejects.toThrow(/Leaf value: .* not found in NOTE_HASH_TREE/); - - await expect(contract.methods.test_note_inclusion_fail_case(owner, false, 0n).send().wait()).rejects.toThrow( - /Leaf value: .* not found in NOTE_HASH_TREE/, - ); - }); - }); - - describe('historical storage reads', () => { - it('reads a historical public value in private context', async () => { - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); - - await contract.methods.test_storage_historical_read(publicValue, true, blockNumber).send().wait(); - await contract.methods.test_storage_historical_read(publicValue, false, 0n).send().wait(); - }); - - it('reads an older (unset) public value', async () => { - const blockNumber = getRandomBlockNumberBeforeDeployment(); - await contract.methods.test_storage_historical_read(0, true, blockNumber).send().wait(); - }); - - it('reads a historical unset public value in private context', async () => { - // This test scenario is interesting because the codepath for storage values that were never set is different - // (since they don't exist in the tree). - const blockNumber = await getRandomBlockNumber(); - await contract.methods.test_storage_historical_read_unset_slot(blockNumber).send().wait(); - }); - }); - - describe('nullifier inclusion', () => { - it('proves existence of a nullifier in private context', async () => { - // Choose random block number between deployment and current block number to test archival node - const blockNumber = await getRandomBlockNumberSinceDeployment(); - const block = await pxe.getBlock(blockNumber); - const nullifier = block?.body.txEffects[0].nullifiers[0]; - - await contract.methods.test_nullifier_inclusion(nullifier!, true, blockNumber).send().wait(); - await contract.methods.test_nullifier_inclusion(nullifier!, false, 0n).send().wait(); - }); - - it('proves existence of a nullifier in public context', async () => { - const unsiloedNullifier = new Fr(123456789n); - await contract.methods.push_nullifier_public(unsiloedNullifier).send().wait(); - await contract.methods.test_nullifier_inclusion_from_public(unsiloedNullifier).send().wait(); - }); - - it('nullifier existence failure case', async () => { - // Choose random block number between first block and current block number to test archival node - const blockNumber = await getRandomBlockNumber(); - const randomNullifier = Fr.random(); - - await expect( - contract.methods.test_nullifier_inclusion(randomNullifier, true, blockNumber).send().wait(), - ).rejects.toThrow(`Nullifier witness not found for nullifier ${randomNullifier.toString()} at block`); - - await expect(contract.methods.test_nullifier_inclusion(randomNullifier, false, 0n).send().wait()).rejects.toThrow( - `Nullifier witness not found for nullifier ${randomNullifier.toString()} at block`, - ); - }); - }); - - describe('contract inclusion', () => { - const assertInclusion = async ( - address: AztecAddress, - blockNumber: number, - opts: { testDeploy: boolean; testInit: boolean }, - ) => { - const { testDeploy, testInit } = opts; - // Assert contract was publicly deployed or initialized in the block in which it was deployed - await contract.methods.test_contract_inclusion(address, blockNumber, testDeploy, testInit).send().wait(); - - // And prove that it was not before that - const olderBlock = blockNumber - 2; - await contract.methods.test_contract_non_inclusion(address, olderBlock, testDeploy, testInit).send().wait(); - - // Or that the positive call fails when trying to prove in the older block - await expect( - contract.methods.test_contract_inclusion(address, olderBlock, testDeploy, testInit).prove(), - ).rejects.toThrow(/not found/); - }; - - it('proves public deployment of a contract', async () => { - // Publicly deploy another contract (so we don't test on the same contract) - const initArgs = [wallets[0].getAddress(), 42n]; - const instance = await getContractInstanceFromDeployParams(StatefulTestContractArtifact, { - constructorArgs: initArgs, - }); - const registerMethod = await registerContractClass(wallets[0], StatefulTestContractArtifact); - await registerMethod.send().wait(); - const deployMethod = await deployInstance(wallets[0], instance); - const receipt = await deployMethod.send().wait(); - - await assertInclusion(instance.address, receipt.blockNumber!, { testDeploy: true, testInit: false }); - }); - - it('proves initialization of a contract', async () => { - // Initialize (but not deploy) a test contract - const receipt = await StatefulTestContract.deploy( - wallets[0], - wallets[0].getAddress(), - wallets[0].getAddress(), - 42n, - ) - .send({ skipClassRegistration: true, skipPublicDeployment: true }) - .wait(); - - await assertInclusion(receipt.contract.address, receipt.blockNumber!, { testDeploy: false, testInit: true }); - }); - }); - - const getRandomBlockNumberSinceDeployment = async () => { - return deploymentBlockNumber + randomInt((await pxe.getBlockNumber()) - deploymentBlockNumber); - }; - - const getRandomBlockNumber = async () => { - return deploymentBlockNumber + randomInt((await pxe.getBlockNumber()) - INITIAL_L2_BLOCK_NUM); - }; - - const getRandomBlockNumberBeforeDeployment = () => { - return randomInt(deploymentBlockNumber - INITIAL_L2_BLOCK_NUM) + INITIAL_L2_BLOCK_NUM; - }; -});