Skip to content

Commit e1ef998

Browse files
authored
fix: add zod parsing for generated contract artifacts (#9905)
This PR enables us to enforce deterministic contract artifact JSON stringifying. This is because now when we generate our contract artifact, we pass it through our zod parser, which enforces ordering in the object, and enforces correct ordering of props in objects. This is related to #6021, which we need for deterministic artifact hashes.
1 parent f7bbd83 commit e1ef998

File tree

3 files changed

+25
-23
lines changed

3 files changed

+25
-23
lines changed

yarn-project/circuits.js/src/contract/artifact_hash.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type ContractArtifact } from '@aztec/foundation/abi';
22

3+
import { getTestContractArtifact } from '../tests/fixtures.js';
34
import { computeArtifactHash } from './artifact_hash.js';
45

56
describe('ArtifactHash', () => {
@@ -19,4 +20,14 @@ describe('ArtifactHash', () => {
1920
`"0x0c6fd9b48570721c5d36f978d084d77cacbfd2814f1344985f40e62bea6e61be"`,
2021
);
2122
});
23+
24+
it('calculates the test contract artifact hash multiple times to ensure deterministic hashing', () => {
25+
const testArtifact = getTestContractArtifact();
26+
27+
for (let i = 0; i < 1000; i++) {
28+
expect(computeArtifactHash(testArtifact).toString()).toMatchInlineSnapshot(
29+
`"0x11ba97d2d4de6335cc86d271d3c4a6237840cf630eaa442cf75d1666ff475f61"`,
30+
);
31+
}
32+
});
2233
});

yarn-project/foundation/src/abi/note_selector.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class NoteSelector extends Selector {
2727
}
2828

2929
static fromString(buf: string) {
30-
const withoutPrefix = buf.replace(/^0x/i, '');
30+
const withoutPrefix = buf.replace(/^0x/i, '').slice(-8);
3131
const buffer = Buffer.from(withoutPrefix, 'hex');
3232
return NoteSelector.fromBuffer(buffer);
3333
}

yarn-project/types/src/abi/contract_artifact.ts

+13-22
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ import {
44
type AbiType,
55
type BasicValue,
66
type ContractArtifact,
7+
ContractArtifactSchema,
78
type ContractNote,
89
type FieldLayout,
910
type FunctionArtifact,
1011
FunctionType,
1112
type IntegerValue,
12-
NoteSelector,
1313
type StructValue,
1414
type TypedStructFieldValue,
1515
} from '@aztec/foundation/abi';
16-
import { Fr } from '@aztec/foundation/fields';
1716

1817
import {
1918
AZTEC_INITIALIZER_ATTRIBUTE,
@@ -53,18 +52,7 @@ export function contractArtifactToBuffer(artifact: ContractArtifact): Buffer {
5352
* @returns Deserialized artifact.
5453
*/
5554
export function contractArtifactFromBuffer(buffer: Buffer): ContractArtifact {
56-
return JSON.parse(buffer.toString('utf-8'), (key, value) => {
57-
if (key === 'bytecode' && typeof value === 'string') {
58-
return Buffer.from(value, 'base64');
59-
}
60-
if (typeof value === 'object' && value !== null && value.type === 'NoteSelector') {
61-
return new NoteSelector(Number(value.value));
62-
}
63-
if (typeof value === 'object' && value !== null && value.type === 'Fr') {
64-
return new Fr(BigInt(value.value));
65-
}
66-
return value;
67-
});
55+
return ContractArtifactSchema.parse(JSON.parse(buffer.toString('utf-8')));
6856
}
6957

7058
/**
@@ -133,7 +121,10 @@ type NoirCompiledContractFunction = NoirCompiledContract['functions'][number];
133121
* @param contract - Parent contract.
134122
* @returns Function artifact.
135123
*/
136-
function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: NoirCompiledContract): FunctionArtifact {
124+
function generateFunctionArtifact(
125+
fn: NoirCompiledContractFunction,
126+
contract: NoirCompiledContract,
127+
): Omit<FunctionArtifact, 'bytecode'> & { bytecode: string } {
137128
if (fn.custom_attributes === undefined) {
138129
throw new Error(
139130
`No custom attributes found for contract function ${fn.name}. Try rebuilding the contract with the latest nargo version.`,
@@ -178,7 +169,7 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: No
178169
isInitializer: fn.custom_attributes.includes(AZTEC_INITIALIZER_ATTRIBUTE),
179170
parameters,
180171
returnTypes,
181-
bytecode: Buffer.from(fn.bytecode, 'base64'),
172+
bytecode: fn.bytecode,
182173
debugSymbols: fn.debug_symbols,
183174
errorTypes: fn.abi.error_types,
184175
...(fn.assert_messages ? { assertMessages: fn.assert_messages } : undefined),
@@ -238,11 +229,11 @@ function getStorageLayout(input: NoirCompiledContract) {
238229
return {};
239230
}
240231

241-
return storageFields.reduce((acc: Record<string, FieldLayout>, field) => {
232+
return storageFields.reduce((acc: Record<string, Omit<FieldLayout, 'slot'> & { slot: string }>, field) => {
242233
const name = field.name;
243234
const slot = field.value.fields[0].value as IntegerValue;
244235
acc[name] = {
245-
slot: Fr.fromString(slot.value),
236+
slot: slot.value,
246237
};
247238
return acc;
248239
}, {});
@@ -262,7 +253,7 @@ function getNoteTypes(input: NoirCompiledContract) {
262253
return {};
263254
}
264255

265-
return notes.reduce((acc: Record<string, ContractNote>, note) => {
256+
return notes.reduce((acc: Record<string, Omit<ContractNote, 'id'> & { id: string }>, note) => {
266257
const noteFields = note.fields;
267258

268259
// We find note type id by looking for respective kinds as each of them is unique
@@ -274,7 +265,7 @@ function getNoteTypes(input: NoirCompiledContract) {
274265
throw new Error(`Could not find note type id, name or fields for note ${note}`);
275266
}
276267

277-
const noteTypeId = NoteSelector.fromField(Fr.fromString(rawNoteTypeId.value));
268+
const noteTypeId = rawNoteTypeId.value as string;
278269
const name = rawName.value as string;
279270

280271
// Note type id is encoded as a hex string
@@ -301,15 +292,15 @@ function getNoteTypes(input: NoirCompiledContract) {
301292
*/
302293
function generateContractArtifact(contract: NoirCompiledContract, aztecNrVersion?: string): ContractArtifact {
303294
try {
304-
return {
295+
return ContractArtifactSchema.parse({
305296
name: contract.name,
306297
functions: contract.functions.map(f => generateFunctionArtifact(f, contract)),
307298
outputs: contract.outputs,
308299
storageLayout: getStorageLayout(contract),
309300
notes: getNoteTypes(contract),
310301
fileMap: contract.file_map,
311302
...(aztecNrVersion ? { aztecNrVersion } : {}),
312-
};
303+
});
313304
} catch (err) {
314305
throw new Error(`Could not generate contract artifact for ${contract.name}: ${err}`);
315306
}

0 commit comments

Comments
 (0)