Skip to content

Commit

Permalink
Connect oracles (#56)
Browse files Browse the repository at this point in the history
* Connect getAccountOracle and getHeaderOracle to oracle dict

* Introduce ResultModifier

* Introduce incHexStr function

* Rest for invalid proof

* Refactor test e2e tests to use alterCall


---------

Co-authored-by: Kirejczyk Marek <[email protected]>
  • Loading branch information
akonior and marekkirejczyk authored Jan 23, 2024
1 parent 2fc121e commit f01590f
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 39 deletions.
6 changes: 1 addition & 5 deletions packages/noir-ethereum-api/src/arrays.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert } from './assert.js';
import { incHexByte } from './utils/string.js';

type PaddingDirection = 'left' | 'right';

Expand All @@ -18,8 +19,3 @@ export const alterArray = function (array: readonly string[]): string[] {
assert(array.length > 0, 'Array should not be empty');
return [incHexByte(array[0]), ...array.slice(1)];
};

function incHexByte(hexByte: string): string {
const newByte = ((parseInt(hexByte) + 1) % 256).toString(16);
return '0x' + newByte;
}
8 changes: 6 additions & 2 deletions packages/noir-ethereum-api/src/ethereum/mockClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { assert } from '../assert.js';
import { readObject } from '../utils/file.js';
import { mock } from '../utils/mock.js';
import isEqual from 'lodash.isequal';
import { identity } from '../utils/function.js';

export async function createMockClient(filePath: string): Promise<PublicClient> {
export async function createMockClient(
filePath: string,
resultModifier: (call: Call) => Call = identity
): Promise<PublicClient> {
const savedCalls = await readObject<Call[]>(filePath);

return mock<PublicClient>(isEthereumApiMethod, (method: string, args: object): object => {
const call: Call | undefined = savedCalls.find((it) => it.method === method && isEqual(it.arguments, args));
assert(!!call, `call not found for: ${method}(${args})`);
return call.result;
return resultModifier(call).result;
});
}
8 changes: 7 additions & 1 deletion packages/noir-ethereum-api/src/noir/oracles/oracles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type ForeignCallOutput } from '@noir-lang/noir_js';
import { createDefaultClient } from '../../ethereum/client.js';
import { getAccountOracle } from './accountOracles.js';
import { type PublicClient } from 'viem';
import { getHeaderOracle } from './headerOracle.js';

export type NoirArguments = string[][];

Expand All @@ -22,4 +23,9 @@ export const createOracles =
return await fn(client, args);
};

export const defaultOracles = createOracles(createDefaultClient())({ getAccountOracle });
export const defaultOraclesMap = {
get_account: getAccountOracle,
get_header: getHeaderOracle
};

export const defaultOracles = createOracles(createDefaultClient())(defaultOraclesMap);
1 change: 1 addition & 0 deletions packages/noir-ethereum-api/src/utils/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const identity = <T>(it: T): T => it;
8 changes: 8 additions & 0 deletions packages/noir-ethereum-api/src/utils/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function updateNestedField<T, V>(obj: T, pathArray: string[], updater: (value: V) => V): void {
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
pathArray.reduce((acc: any, key: string, i: number) => {
if (acc[key] === undefined) acc[key] = {};
if (i === pathArray.length - 1) acc[key] = updater(acc[key]);
return acc[key];
}, obj);
}
8 changes: 8 additions & 0 deletions packages/noir-ethereum-api/src/utils/string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function incHexStr(hexStr: string): string {
return '0x' + (BigInt(hexStr) + 1n).toString(16);
}

export function incHexByte(hexByte: string): string {
const newByte = ((parseInt(hexByte) + 1) % 256).toString(16);
return '0x' + newByte;
}
64 changes: 33 additions & 31 deletions packages/noir-ethereum-api/test/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { describe, expect, it } from 'vitest';
import { generateAndVerifyStorageProof, type MainInputs } from '../src/main.js';
import { encodeAddress } from '../src/noir/encode.js';
import { createOracles, type Oracles } from '../src/noir/oracles/oracles.js';
import accountWithProofJSON from './fixtures/accountWithProof.json';
import { AccountWithProof, expectCircuitFail, type FieldsOfType, serializeAccountWithProof } from './helpers.js';
import { blockHeaders } from './fixtures/blockHeader.json';
import { encodeBlockHeaderPartial } from '../src/noir/oracles/headerOracle.js';
import { type BlockHeader } from '../src/ethereum/blockHeader.js';
import { PublicClient } from 'viem';
import { beforeAll, describe, expect, it } from 'vitest';
import { alterArray } from '../src/arrays.js';
import { incHexStr } from '../src/utils/string.js';
import { createMockClient } from '../src/ethereum/mockClient.js';
import { Call } from '../src/ethereum/recordingClient.js';
import { generateAndVerifyStorageProof, type MainInputs } from '../src/main.js';
import { encodeAddress } from '../src/noir/encode.js';
import { createOracles, defaultOraclesMap, type Oracles } from '../src/noir/oracles/oracles.js';
import { ADDRESS } from './ethereum/recordingClient.test.js';
import { expectCircuitFail } from './helpers.js';
import { updateNestedField } from '../src/utils/object.js';

const defaultTestCircuitInputParams: MainInputs = {
block_no: 14194126,
Expand All @@ -20,41 +20,43 @@ const defaultTestCircuitInputParams: MainInputs = {
'0xda', '0xc4', '0x8e', '0x2b', '0x4']
};

function alterCall(callName: string, path: string[]) {
return (call: Call): Call => {
if (call.method === callName) {
updateNestedField(call, path, incHexStr);
}
return call;
};
}
describe(
'e2e',
async () => {
async function oracles(accountWithProof: AccountWithProof = accountWithProofJSON): Promise<Oracles> {
return createOracles(await createMockClient('./test/fixtures/mockClientData.json'))({
get_account: async () => serializeAccountWithProof(accountWithProof),
get_header: async () => encodeBlockHeaderPartial(blockHeaders[1].header as BlockHeader)
});
}
() => {
let client: PublicClient;
let oracles: Oracles;

beforeAll(async () => {
client = await createMockClient('./test/fixtures/mockClientData.json');
oracles = createOracles(client)(defaultOraclesMap);
});

it('proof successes', async () => {
expect(await generateAndVerifyStorageProof(defaultTestCircuitInputParams, await oracles())).toEqual(true);
expect(await generateAndVerifyStorageProof(defaultTestCircuitInputParams, oracles)).toEqual(true);
});

const arrayKeys: Array<FieldsOfType<AccountWithProof, readonly string[]>> = ['key', 'value', 'proof'];
arrayKeys.forEach((arrayField) => {
it(`proof fails: invalid field: ${arrayField}`, async () => {
await expectCircuitFail(
generateAndVerifyStorageProof(
defaultTestCircuitInputParams,
await oracles({
...accountWithProofJSON,
[arrayField]: alterArray(accountWithProofJSON[arrayField])
})
)
);
});
it('proof fails: invalid proof', async () => {
const resultModifier = alterCall('getProof', ['result', 'accountProof', '0']);
const oracles = createOracles(await createMockClient('./test/fixtures/mockClientData.json', resultModifier))(
defaultOraclesMap
);
await expectCircuitFail(generateAndVerifyStorageProof(defaultTestCircuitInputParams, oracles));
});

it('proof fails: invalid state root', async () => {
const inputParams = {
...defaultTestCircuitInputParams,
state_root: alterArray(defaultTestCircuitInputParams.state_root)
};
await expectCircuitFail(generateAndVerifyStorageProof(inputParams, await oracles()));
await expectCircuitFail(generateAndVerifyStorageProof(inputParams, oracles));
});
},
{
Expand Down
17 changes: 17 additions & 0 deletions packages/noir-ethereum-api/test/utils/object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { updateNestedField } from '../../src/utils/object.js';
import { describe, it, expect } from 'vitest';

describe('updateNestedField', () => {
it('existing key', () => {
const object = { a: [{ bar: { c: 3 } }] };
updateNestedField(object, ['a', '0', 'bar', 'c'], (x: number) => x + 1);
expect(object.a[0].bar.c).to.eq(4);
});

it('non-existing key', () => {
const object = { a: [{ bar: { c: 3 } }] };
updateNestedField(object, ['x', '0', 'y', 'z'], () => 5);
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
expect((object as any).x[0]?.y?.z).to.eq(5);
});
});
33 changes: 33 additions & 0 deletions packages/noir-ethereum-api/test/utils/string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from 'vitest';
import { incHexByte, incHexStr } from '../../src/utils/string.js';

describe('incHexStr', () => {
it('zero', () => {
expect(incHexStr('0x0')).toEqual('0x1');
});

it('small', () => {
expect(incHexStr('0x10')).toEqual('0x11');
});

it('big', () => {
expect(incHexStr('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1')).toEqual(
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2'
);
});
});

describe('incHexByte', () => {
it('zero', () => {
expect(incHexByte('0x0')).toEqual('0x1');
});

it('mid size', () => {
expect(incHexByte('0xaa')).toEqual('0xab');
expect(incHexByte('0xAA')).toEqual('0xab');
});

it('oveflow', () => {
expect(incHexByte('0xFF')).toEqual('0x0');
});
});

0 comments on commit f01590f

Please sign in to comment.