# 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](https://github.com/AztecProtocol/aztec-packages/blob/b5a5a0b5e2f99992e5083d1d8044169961b0210e/yarn-project/noir-contracts/contracts/unit_test/src/main.nr) **`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 * Write unit test for the uniswap portal fn and see how to improve * Use Manas' debugger * Integration tests: https://noir-lang.org/docs/dev/how_to/how-to-oracles/#step-2---write-an-rpc-server --- # 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 1. ``` #[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 2. Inconsistency if I am mocking 3+ oracles at once. E.g. [this doesn't work](https://github.com/AztecProtocol/aztec-packages/blob/4b973309cceb4da49f9364ceff4afa2532b01b6e/yarn-project/noir-contracts/contracts/unit_test/src/main.nr#L209-L212) 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!) ---