-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: improve aztec-nr testing infra (#6611)
Some time ago @sklppy88 created some note getter tests in noir. These could not be placed in the same crate as `NoteInterface` due to noir-lang/noir#4502. However, this prevented us from importing the `TestNote` in the original aztec-nr crate as that would've created cyclical imports, which are not allowed. Now that noir-lang/noir#4502 is fixed, we can have the `TestNote` live alongisde `NoteInterface`. This PR deletes the `test` crate and creates instead a `test` module inside the `aztec_nr` crate. I moved the note getter tests into a `test` submodule, meaning we can access internal functions (i.e. no more `pub` internals) while still having the tests live in separate files. I also created a context builder object, which is something I've had to manually craft multiple times, and is now slightly more complicated due to the new AVM opcodes. Hopefully this will be a better foundation over which to continue writing Noir tests for aztec-nr.
- Loading branch information
Showing
14 changed files
with
334 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,4 @@ members = [ | |
"compressed-string", | ||
"easy-private-state", | ||
"value-note", | ||
"tests", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,5 @@ mod prelude; | |
mod public_storage; | ||
mod encrypted_logs; | ||
use dep::protocol_types; | ||
|
||
mod test; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
use dep::protocol_types::constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL; | ||
use crate::{ | ||
context::PrivateContext, | ||
note::{ | ||
note_header::NoteHeader, | ||
note_getter_options::{NoteGetterOptions, Sort, SortOrder, Comparator, PropertySelector}, | ||
note_getter::constrain_get_notes_internal | ||
} | ||
}; | ||
use dep::protocol_types::address::AztecAddress; | ||
|
||
use crate::test::{helpers::context_builder::ContextBuilder, mocks::mock_note::MockNote}; | ||
|
||
global contract_address = AztecAddress::from_field(69); | ||
global storage_slot: Field = 42; | ||
|
||
fn setup() -> PrivateContext { | ||
ContextBuilder::new().contract_address(contract_address).private() | ||
} | ||
|
||
fn build_valid_note(value: Field) -> MockNote { | ||
MockNote::new(value).contract_address(contract_address).storage_slot(storage_slot).build() | ||
} | ||
|
||
#[test] | ||
fn validates_note_and_returns_it() { | ||
let mut context = setup(); | ||
|
||
let value = 1337; | ||
let test_note = build_valid_note(value); | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
opt_notes[0] = Option::some(test_note); | ||
|
||
let options = NoteGetterOptions::new(); | ||
let returned = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
|
||
assert_eq(returned[0].unwrap().value, value); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn cannot_return_zero_notes() { | ||
let mut context = setup(); | ||
|
||
let mut opt_notes: [Option<MockNote>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn mismatched_address() { | ||
let mut context = setup(); | ||
|
||
let note = MockNote::new(1).storage_slot(storage_slot).build(); // We're not setting the right contract address | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
opt_notes[0] = Option::some(note); | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn mismatched_storage_slot() { | ||
let mut context = setup(); | ||
|
||
let note = MockNote::new(1).storage_slot(storage_slot + 1).build(); // We're not setting the right storage slot | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
opt_notes[0] = Option::some(note); | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn mismatched_selector() { | ||
let mut context = setup(); | ||
|
||
let value = 10; | ||
let note = build_valid_note(value); | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
opt_notes[0] = Option::some(note); | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
options = options.select( | ||
PropertySelector { index: 0, offset: 0, length: 32 }, | ||
value + 1, | ||
Option::some(Comparator.EQ) | ||
); | ||
|
||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn mismatched_desc_sort_order() { | ||
let mut context = setup(); | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
// Notes in ascending order | ||
opt_notes[0] = Option::some(build_valid_note(1)); | ||
opt_notes[1] = Option::some(build_valid_note(2)); | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
options = options.sort( | ||
PropertySelector { index: 0, offset: 0, length: 32 }, | ||
SortOrder.DESC | ||
); | ||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test(should_fail)] | ||
fn mismatched_asc_sort_order() { | ||
let mut context = setup(); | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
// Notes in descending order | ||
opt_notes[0] = Option::some(build_valid_note(2)); | ||
opt_notes[1] = Option::some(build_valid_note(1)); | ||
|
||
let mut options = NoteGetterOptions::new(); | ||
options = options.sort( | ||
PropertySelector { index: 0, offset: 0, length: 32 }, | ||
SortOrder.ASC | ||
); | ||
let _ = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
} | ||
|
||
#[test] | ||
fn sparse_notes_array() { | ||
let mut context = setup(); | ||
|
||
let mut opt_notes = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; | ||
opt_notes[1] = Option::some(build_valid_note(0)); | ||
opt_notes[2] = Option::some(build_valid_note(1)); | ||
opt_notes[3] = Option::some(build_valid_note(2)); | ||
opt_notes[5] = Option::some(build_valid_note(3)); | ||
opt_notes[8] = Option::some(build_valid_note(4)); | ||
opt_notes[13] = Option::some(build_valid_note(5)); | ||
opt_notes[21] = Option::some(build_valid_note(6)); | ||
|
||
let options = NoteGetterOptions::new(); | ||
let returned = constrain_get_notes_internal(&mut context, storage_slot, opt_notes, options); | ||
|
||
for i in 0..7 { | ||
assert(returned[i].unwrap().value == i as Field); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
mod helpers; | ||
mod mocks; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mod context_builder; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use crate::context::{PrivateContext, PublicContext}; | ||
use dep::protocol_types::address::AztecAddress; | ||
use dep::std::test::OracleMock; | ||
|
||
struct ContextBuilder { | ||
block_number: Option<Field>, | ||
contract_address: Option<AztecAddress>, | ||
} | ||
|
||
impl ContextBuilder { | ||
fn new() -> Self { | ||
Self { block_number: Option::none(), contract_address: Option::none() } | ||
} | ||
|
||
fn block_number(&mut self, block_number: Field) -> &mut Self { | ||
self.block_number = Option::some(block_number); | ||
self | ||
} | ||
|
||
fn contract_address(&mut self, contract_address: AztecAddress) -> &mut Self { | ||
self.contract_address = Option::some(contract_address); | ||
self | ||
} | ||
|
||
fn private(&mut self) -> PrivateContext { | ||
let mut context = PrivateContext::empty(); | ||
|
||
if self.block_number.is_some() { | ||
context.inputs.historical_header.global_variables.block_number = self.block_number.unwrap_unchecked(); | ||
} | ||
|
||
if self.contract_address.is_some() { | ||
context.inputs.call_context.storage_contract_address = self.contract_address.unwrap_unchecked(); | ||
} | ||
|
||
context | ||
} | ||
|
||
fn public(&mut self) -> PublicContext { | ||
let mut context = PublicContext::empty(); | ||
|
||
if self.block_number.is_some() { | ||
let _ = OracleMock::mock("avmOpcodeBlockNumber").returns(self.block_number.unwrap()); | ||
} | ||
|
||
if self.contract_address.is_some() { | ||
let _ = OracleMock::mock("avmOpcodeAddress").returns(self.contract_address.unwrap()); | ||
} | ||
|
||
context | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
mod mock_note; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use crate::{context::PrivateContext, note::{note_header::NoteHeader, note_interface::NoteInterface}}; | ||
|
||
use dep::protocol_types::{address::AztecAddress, grumpkin_point::GrumpkinPoint}; | ||
|
||
global MOCK_NOTE_LENGTH = 1; | ||
// MOCK_NOTE_LENGTH * 32 + 32(storage_slot as bytes) + 32(note_type_id as bytes) | ||
global MOCK_NOTE_BYTES_LENGTH: Field = 1 * 32 + 64; | ||
|
||
struct MockNote { | ||
header: NoteHeader, | ||
value: Field, | ||
} | ||
|
||
impl NoteInterface<MOCK_NOTE_LENGTH, MOCK_NOTE_BYTES_LENGTH> for MockNote { | ||
fn serialize_content(self) -> [Field; MOCK_NOTE_LENGTH] { | ||
[self.value] | ||
} | ||
|
||
fn deserialize_content(fields: [Field; MOCK_NOTE_LENGTH]) -> Self { | ||
Self { | ||
value: fields[0], | ||
header: NoteHeader::empty(), | ||
} | ||
} | ||
|
||
fn compute_note_content_hash(self) -> Field { | ||
0 | ||
} | ||
|
||
fn get_header(self) -> NoteHeader { | ||
self.header | ||
} | ||
|
||
fn set_header(&mut self, header: NoteHeader) -> () { | ||
self.header = header; | ||
} | ||
|
||
fn get_note_type_id() -> Field { | ||
0 | ||
} | ||
|
||
fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { | ||
0 | ||
} | ||
|
||
fn compute_nullifier_without_context(self) -> Field { | ||
0 | ||
} | ||
|
||
fn broadcast(self, context: &mut PrivateContext, slot: Field, ivpk_m: GrumpkinPoint) { | ||
assert( | ||
false, "MockNote does not support broadcast." | ||
); | ||
} | ||
|
||
fn to_be_bytes(self, storage_slot: Field) -> [u8; MOCK_NOTE_BYTES_LENGTH] { | ||
let serialized_note = self.serialize_content(); | ||
|
||
let mut buffer: [u8; MOCK_NOTE_BYTES_LENGTH] = [0; MOCK_NOTE_BYTES_LENGTH]; | ||
|
||
let storage_slot_bytes = storage_slot.to_be_bytes(32); | ||
let note_type_id_bytes = MockNote::get_note_type_id().to_be_bytes(32); | ||
|
||
for i in 0..32 { | ||
buffer[i] = storage_slot_bytes[i]; | ||
buffer[32 + i] = note_type_id_bytes[i]; | ||
} | ||
|
||
for i in 0..serialized_note.len() { | ||
let bytes = serialized_note[i].to_be_bytes(32); | ||
for j in 0..32 { | ||
buffer[64 + i * 32 + j] = bytes[j]; | ||
} | ||
} | ||
buffer | ||
} | ||
} | ||
|
||
struct MockNoteBuilder { | ||
value: Field, | ||
contract_address: Option<AztecAddress>, | ||
storage_slot: Option<Field>, | ||
} | ||
|
||
impl MockNoteBuilder { | ||
fn new(value: Field) -> Self { | ||
MockNoteBuilder { value, contract_address: Option::none(), storage_slot: Option::none() } | ||
} | ||
|
||
fn contract_address(&mut self, contract_address: AztecAddress) -> &mut Self { | ||
self.contract_address = Option::some(contract_address); | ||
self | ||
} | ||
|
||
fn storage_slot(&mut self, storage_slot: Field) -> &mut Self { | ||
self.storage_slot = Option::some(storage_slot); | ||
self | ||
} | ||
|
||
fn build(self) -> MockNote { | ||
let mut header = NoteHeader::empty(); | ||
|
||
if self.contract_address.is_some() { | ||
header.contract_address = self.contract_address.unwrap(); | ||
} | ||
|
||
if self.storage_slot.is_some() { | ||
header.storage_slot = self.storage_slot.unwrap(); | ||
} | ||
|
||
MockNote { value: self.value, header } | ||
} | ||
} | ||
|
||
impl MockNote { | ||
pub fn new(value: Field) -> MockNoteBuilder { | ||
MockNoteBuilder::new(value) | ||
} | ||
} |
Oops, something went wrong.