Skip to content

Commit

Permalink
chore: refactor e2e tests to use the new simulate fn (#5854)
Browse files Browse the repository at this point in the history
With #5762 we can
now use `simulate()` to directly get values out of contract calls (i.e.
#2665). This PR
refactors some e2e tests that relied on events to get values out and
replaces those with proper `simulate()` calls.

Co-authored-by: ludamad <[email protected]>
  • Loading branch information
nventuro and ludamad authored Apr 19, 2024
1 parent ab9fe78 commit e7d2aff
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 81 deletions.
21 changes: 5 additions & 16 deletions noir-projects/noir-contracts/contracts/auth_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,21 @@ contract Auth {
}

#[aztec(public)]
fn get_authorized() {
// We emit logs because we cannot otherwise return these values
emit_unencrypted_log(
&mut context,
storage.authorized.get_current_value_in_public()
);
fn get_authorized() -> AztecAddress {
storage.authorized.get_current_value_in_public()
}

#[aztec(public)]
fn get_scheduled_authorized() {
// We emit logs because we cannot otherwise return these values
emit_unencrypted_log(
&mut context,
storage.authorized.get_scheduled_value_in_public().0
);
fn get_scheduled_authorized() -> AztecAddress {
storage.authorized.get_scheduled_value_in_public().0
}

#[aztec(private)]
fn do_private_authorized_thing(value: Field) {
fn do_private_authorized_thing() {
// Reading a value from authorized in private automatically adds an extra validity condition: the base rollup
// circuit will reject this tx if included in a block past the block horizon, which is as far as the circuit can
// guarantee the value will not change from some historical value (due to CHANGE_AUTHORIZED_DELAY_BLOCKS).
let authorized = storage.authorized.get_current_value_in_private();
assert_eq(authorized, context.msg_sender(), "caller is not authorized");

// We emit logs because we cannot otherwise return these values
emit_unencrypted_log_from_private(&mut context, value);
}
}
14 changes: 4 additions & 10 deletions noir-projects/noir-contracts/contracts/test_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ contract Test {
}

#[aztec(private)]
fn call_get_notes(storage_slot: Field, active_or_nullified: bool) {
fn call_get_notes(storage_slot: Field, active_or_nullified: bool) -> Field {
assert(
storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant"
);
Expand All @@ -107,14 +107,11 @@ contract Test {

let opt_notes: [Option<ValueNote>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = get_notes(&mut context, storage_slot, options);

// We can't get the return value of a private function from the outside world in an end to end test, so we
// expose it via an unecrypted log instead.
let value = opt_notes[0].unwrap().value;
emit_unencrypted_log_from_private(&mut context, value);
opt_notes[0].unwrap().value
}

#[aztec(private)]
fn call_get_notes_many(storage_slot: Field, active_or_nullified: bool) {
fn call_get_notes_many(storage_slot: Field, active_or_nullified: bool) -> [Field; 2] {
assert(
storage_slot != storage.example_constant.get_storage_slot(), "this storage slot is reserved for example_constant"
);
Expand All @@ -126,10 +123,7 @@ contract Test {

let opt_notes: [Option<ValueNote>; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] = get_notes(&mut context, storage_slot, options);

// We can't get the return value of a private function from the outside world in an end to end test, so we
// expose it via an unecrypted log instead.
emit_unencrypted_log_from_private(&mut context, opt_notes[0].unwrap().value);
emit_unencrypted_log_from_private(&mut context, opt_notes[1].unwrap().value);
[opt_notes[0].unwrap().value, opt_notes[1].unwrap().value]
}

unconstrained fn call_view_notes(storage_slot: Field, active_or_nullified: bool) -> pub Field {
Expand Down
53 changes: 23 additions & 30 deletions yarn-project/end-to-end/src/e2e_auth_contract.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AccountWallet, AztecAddress, type ContractFunctionInteraction, Fr, type PXE } from '@aztec/aztec.js';
import { type AccountWallet, AztecAddress, Fr, type PXE } from '@aztec/aztec.js';
import { AuthContract } from '@aztec/noir-contracts.js';

import { jest } from '@jest/globals';
Expand All @@ -19,7 +19,6 @@ describe('e2e_auth_contract', () => {

let contract: AuthContract;

const VALUE = 3;
const DELAY = 5;

beforeAll(async () => {
Expand Down Expand Up @@ -48,40 +47,30 @@ describe('e2e_auth_contract', () => {
}
}

async function assertLoggedAddress(interaction: ContractFunctionInteraction, address: AztecAddress) {
const logs = await pxe.getUnencryptedLogs({ txHash: (await interaction.send().wait()).txHash });
expect(AztecAddress.fromBuffer(logs.logs[0].log.data)).toEqual(address);
}

async function assertLoggedNumber(interaction: ContractFunctionInteraction, value: number) {
const logs = await pxe.getUnencryptedLogs({ txHash: (await interaction.send().wait()).txHash });
expect(Fr.fromBuffer(logs.logs[0].log.data)).toEqual(new Fr(value));
}

it('authorized is unset initially', async () => {
await assertLoggedAddress(contract.methods.get_authorized(), AztecAddress.ZERO);
expect(await contract.methods.get_authorized().simulate()).toEqual(AztecAddress.ZERO);
});

it('admin sets authorized', async () => {
await contract.withWallet(admin).methods.set_authorized(authorized.getAddress()).send().wait();

await assertLoggedAddress(contract.methods.get_scheduled_authorized(), authorized.getAddress());
expect(await contract.methods.get_scheduled_authorized().simulate()).toEqual(authorized.getAddress());
});

it('authorized is not yet set, cannot use permission', async () => {
await assertLoggedAddress(contract.methods.get_authorized(), AztecAddress.ZERO);
expect(await contract.methods.get_authorized().simulate()).toEqual(AztecAddress.ZERO);

await expect(
contract.withWallet(authorized).methods.do_private_authorized_thing(VALUE).send().wait(),
).rejects.toThrow('caller is not authorized');
await expect(contract.withWallet(authorized).methods.do_private_authorized_thing().send().wait()).rejects.toThrow(
'caller is not authorized',
);
});

it('after a while the scheduled change is effective and can be used with max block restriction', async () => {
await mineBlocks(DELAY); // This gets us past the block of change

await assertLoggedAddress(contract.methods.get_authorized(), authorized.getAddress());
expect(await contract.methods.get_authorized().simulate()).toEqual(authorized.getAddress());

const interaction = contract.withWallet(authorized).methods.do_private_authorized_thing(VALUE);
const interaction = contract.withWallet(authorized).methods.do_private_authorized_thing();

const tx = await interaction.prove();

Expand All @@ -94,32 +83,36 @@ describe('e2e_auth_contract', () => {
expect(tx.data.forRollup!.rollupValidationRequests.maxBlockNumber.isSome).toEqual(true);
expect(tx.data.forRollup!.rollupValidationRequests.maxBlockNumber.value).toEqual(new Fr(expectedMaxBlockNumber));

await assertLoggedNumber(interaction, VALUE);
expect((await interaction.send().wait()).status).toEqual('mined');
});

it('a new authorized address is set but not immediately effective, the previous one retains permissions', async () => {
await contract.withWallet(admin).methods.set_authorized(other.getAddress()).send().wait();

await assertLoggedAddress(contract.methods.get_authorized(), authorized.getAddress());
expect(await contract.methods.get_authorized().simulate()).toEqual(authorized.getAddress());

await assertLoggedAddress(contract.methods.get_scheduled_authorized(), other.getAddress());
expect(await contract.methods.get_scheduled_authorized().simulate()).toEqual(other.getAddress());

await expect(contract.withWallet(other).methods.do_private_authorized_thing(VALUE).send().wait()).rejects.toThrow(
await expect(contract.withWallet(other).methods.do_private_authorized_thing().send().wait()).rejects.toThrow(
'caller is not authorized',
);

await assertLoggedNumber(contract.withWallet(authorized).methods.do_private_authorized_thing(VALUE), VALUE);
expect((await contract.withWallet(authorized).methods.do_private_authorized_thing().send().wait()).status).toEqual(
'mined',
);
});

it('after some time the scheduled change is made effective', async () => {
await mineBlocks(DELAY); // This gets us past the block of change

await assertLoggedAddress(contract.methods.get_authorized(), other.getAddress());
expect(await contract.methods.get_authorized().simulate()).toEqual(other.getAddress());

await expect(
contract.withWallet(authorized).methods.do_private_authorized_thing(VALUE).send().wait(),
).rejects.toThrow('caller is not authorized');
await expect(contract.withWallet(authorized).methods.do_private_authorized_thing().send().wait()).rejects.toThrow(
'caller is not authorized',
);

await assertLoggedNumber(contract.withWallet(other).methods.do_private_authorized_thing(VALUE), VALUE);
expect((await contract.withWallet(other).methods.do_private_authorized_thing().send().wait()).status).toEqual(
'mined',
);
});
});
30 changes: 5 additions & 25 deletions yarn-project/end-to-end/src/e2e_note_getter.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type AztecAddress, Comparator, Fr, type Wallet, toBigInt } from '@aztec/aztec.js';
import { type AztecAddress, Comparator, Fr, type Wallet } from '@aztec/aztec.js';
import { DocsExampleContract, TestContract } from '@aztec/noir-contracts.js';

import { setup } from './fixtures/utils.js';
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('e2e_note_getter', () => {

async function assertNoteIsReturned(storageSlot: number, expectedValue: number, activeOrNullified: boolean) {
const viewNotesResult = await contract.methods.call_view_notes(storageSlot, activeOrNullified).simulate();
const getNotesResult = await callGetNotes(storageSlot, activeOrNullified);
const getNotesResult = await contract.methods.call_get_notes(storageSlot, activeOrNullified).simulate();

expect(viewNotesResult).toEqual(getNotesResult);
expect(viewNotesResult).toEqual(BigInt(expectedValue));
Expand All @@ -183,28 +183,6 @@ describe('e2e_note_getter', () => {
);
}

async function callGetNotes(storageSlot: number, activeOrNullified: boolean): Promise<bigint> {
// call_get_notes exposes the return value via an event since we cannot use simulate() with it.
const tx = contract.methods.call_get_notes(storageSlot, activeOrNullified).send();
await tx.wait();

const logs = (await tx.getUnencryptedLogs()).logs;
expect(logs.length).toBe(1);

return toBigInt(logs[0].log.data);
}

async function callGetNotesMany(storageSlot: number, activeOrNullified: boolean): Promise<Array<bigint>> {
// call_get_notes_many exposes the return values via event since we cannot use simulate() with it.
const tx = contract.methods.call_get_notes_many(storageSlot, activeOrNullified).send();
await tx.wait();

const logs = (await tx.getUnencryptedLogs()).logs;
expect(logs.length).toBe(2);

return [toBigInt(logs[0].log.data), toBigInt(logs[1].log.data)];
}

describe('active note only', () => {
const activeOrNullified = false;

Expand Down Expand Up @@ -250,7 +228,9 @@ describe('e2e_note_getter', () => {
const viewNotesManyResult = await contract.methods
.call_view_notes_many(storageSlot, activeOrNullified)
.simulate();
const getNotesManyResult = await callGetNotesMany(storageSlot, activeOrNullified);
const getNotesManyResult = await contract.methods
.call_get_notes_many(storageSlot, activeOrNullified)
.simulate();

// We can't be sure in which order the notes will be returned, so we simply sort them to test equality. Note
// however that both view_notes and get_notes get the exact same result.
Expand Down

0 comments on commit e7d2aff

Please sign in to comment.