Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// 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;
use dep::aztec::prelude::{AztecAddress, Map, NoteGetterOptions, PrivateSet, PublicMutable};

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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unconstrained fn note_flow() {
unconstrained fn note_validity() {

I think note_validity is a better name here as it communicates what is being tested.

// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this?


// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that block with number 0 is used. Is that the current block? That's kinda weird. Also as far as I remember block 0 is not a legitimate block number (we start from 1).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see that the InclusionProof contract ignores it.

Please nuke the use_block_number param from InclusionProofs contract and always use the block number.

It used to be there because from TS we don't have a direct control over block numbers (which we do in TXE tests)

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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test can be merged with the previous one as both test note validity.

  1. create note,
  2. test note validity at current block,
  3. nullify note,
  4. test note validity at historical block.

This test is only slightly more strict by checking the the nullifier non-inclusion is handled correctly.

// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is weird. It says that you nullify the note and then you prove that it's not 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);

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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unconstrained fn note_not_nullified_after_nullified() {
unconstrained fn nullifier_non_inclusion_failure() {

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we automine now? And if it's not yet implemente do we have an issue?

If we have an issue would include a todo everywhere here.


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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test looks like the same thing as the previous one and hence would merge it. Note that we are not testing the InclusionsProof contract but the history library and it triggers the same code paths.

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());
}
Loading
Loading