Try   HackMD

Unit Testing Aztec.nr Contracts

Contents:

  • Rahul's High level thoughts on how a good unit test framework could look.
  • Notes for noir team on bugs/feature requests

Rahul's High level thoughts on how a good unit test framework could look.

ignore this section. It is just my ramblings and notes

I wrote unit tests within the contract scope. Ref this file

beforeAll()/beforeEach()/afterEach/afterAll

  • analyse all oracle calls being made by functions called in this block (e.g. constructor). HOW???
    • Is this really needed? To abstract away mocking the oracle calls? Or are we okay forcing devs to know about it. i think its fine for v1.
  • Before all fns, call them

normal test functions:

  • Wrappers around values coming from priv_circ_pub_inputs - example wrappers could be getReturnValues(), getNewNullifiers(), getNewReadRequests()
  • Add good cheatcodes to set Private/PublicContextInputs e.g. set msg.sender()
  • Other forge based cheatcodes - setting public or private storage,
  • Good wrappers around oracle mocker:
    • Create a dedicated struct for each aztec.nr oracle call
    • only provide params that are needed
    • easier mocking of return values - storageWrite requires hash values to provided that isn't used.
    • easier mocking of return values - for oracles like getNotes which require 14+ values to be mocked:
let mut returnVal = [0; VIEW_NOTE_ORACLE_RETURN_LENGTH];
returnVal[0] = 1; // return header - num_notes
returnVal[1] = this_address; // return header - contract_address
returnVal[2] = 1; // return header - nonce
returnVal[3] = 0; // return header - is_transient
returnVal[4] = note1.value1
returnVal[5] = note1.value2
....
OracleMock::mock("getNotes").withParams(...).returns(returnVal)
  • Abstract away need to provide storage slots
  • Working with nonce, sideEffectCounter feels like a footgun

TODOs


Notes for noir team on bugs/feature requests

This is really powerful and great! A lot of Aztec.nr is currently untested and as is, we can use it for testing! However, there are some inconsistencies/bugs/faeture requests that I have based on my hacking:

Noir: Feature Requests

Better failure errors:

let pub_circ_pub_inputs.return_values[0] = 11;
assert_eq(pub_circ_pub_inputs.return_values[0], 10); 

this says Failed to solve brillig function, reason: explicit trap hit in brillig.
Proposed Improvement: expected 10. got 11

Check how many times a mock was called.

Needed for checking that only x many storage slots were written or external function was only called x times. Current work around is mock.times(x) which can test if oracle was called more than x times, but not less than x times.

  • setups like beforeEach/beforeAll

Oracle mocker to mock some params but not others. Useful for various oracles like

a.

#[oracle(notifyCreatedNote)]
fn notify_created_note_oracle<N>(_storage_slot: Field, _serialized_note: [Field; N], _inner_note_hash: Field) -> Field {}

We can expect devs to know _storage_slot and _serialized_note but not _inner_note_hash
b. Or consider this oracle with so many parameters:

#[oracle(getNotes)]
fn get_notes_oracle<N, S>(
    _storage_slot: Field,
    _num_selects: u8,
    _select_by: [u8; N],
    _select_values: [Field; N],
    _sort_by: [u8; N],
    _sort_order: [u2; N],
    _limit: u32,
    _offset: u32,
    _return_size: u32,
    _placeholder_fields: [Field; S]
) -> [Field; S] {}

ability to write tests outside of contract scope.

[Unsure how this changes with the contract keyword being removed]
e.g. in a new file. May require new type, TestContract. Depends on how we handle deprecation of the contract keyword

Noir: Bugs

 #[aztec(private)]
fn add_rand(b: Field) -> Field {
    rand() + b
}
#[test]
unconstrained fn test_add_rand() {
    OracleMock::mock("getRandomField").returns(3).times(1);
    let priv_circ_pub_inputs = add_rand(PrivateContextInputs::empty(), 2);
    assert(2.lt(priv_circ_pub_inputs.return_values[0]), "Is not less than!!");
}

Crashes. but if I change times(2) then works

  1. Inconsistency if I am mocking 3+ oracles at once. E.g. this doesn't work
    If I comment out the last Oracle Mocker (enqueuePublicFunctionCall, then the test works (just mocks callPrivateFunction but if I uncomment, then it says callPrivateFunction not mocked but it is with the expected params!)