Skip to content

Commit adb099b

Browse files
committed
refactor(standard-contracts): mirror protocol-contracts generator structure
- Decompose renderTsData into generateNames/generateSalts/generateAddresses/generateClassIdPreimages helpers, matching protocol-contracts pattern 1:1. - Restore privateFunctions in ContractData and emit StandardContractPrivateFunctions in the data file; makeStandardContract now reads from it instead of hardcoding []. Required for contracts that have private functions (e.g. MultiCallEntrypoint upstack).
1 parent ca5a3f8 commit adb099b

3 files changed

Lines changed: 130 additions & 64 deletions

File tree

yarn-project/standard-contracts/src/make_standard_contract.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
StandardContractClassIdPreimage,
1010
StandardContractInitializationHash,
1111
type StandardContractName,
12+
StandardContractPrivateFunctions,
1213
StandardContractSalt,
1314
} from './standard_contract_data.js';
1415

@@ -30,7 +31,7 @@ export function makeStandardContract(name: StandardContractName, artifact: Contr
3031
privateFunctionsRoot,
3132
publicBytecodeCommitment,
3233
packedBytecode: artifact.functions.find(f => f.name === 'public_dispatch')?.bytecode ?? Buffer.alloc(0),
33-
privateFunctions: [],
34+
privateFunctions: StandardContractPrivateFunctions[name],
3435
};
3536

3637
const instance = {

yarn-project/standard-contracts/src/scripts/generate_data.ts

Lines changed: 120 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// expensive hashing at runtime and keeps the Noir-side address aligned with the TS-side.
66
import { Fr } from '@aztec/foundation/curves/bn254';
77
import { createConsoleLogger } from '@aztec/foundation/log';
8-
import { loadContractArtifact } from '@aztec/stdlib/abi';
8+
import { FunctionSelector, loadContractArtifact } from '@aztec/stdlib/abi';
99
import { AztecAddress } from '@aztec/stdlib/aztec-address';
1010
import {
1111
computeContractAddressFromInstance,
@@ -30,114 +30,166 @@ const salt = new Fr(1);
3030
const deployer = AztecAddress.zero();
3131

3232
// Maps each TS name to its source artifact name in `noir-contracts/target/` and the Noir
33-
// constant name to emit. Add a row here when introducing a new standard contract.
34-
const standardContracts: { name: string; src: string; nrConst: string }[] = [
33+
// constant name to emit. `nrConst: null` skips the Noir-side stamp for contracts with no
34+
// Noir-side address consumer (e.g. account-side entrypoints). Add a row here when introducing
35+
// a new standard contract.
36+
const standardContracts: { name: string; src: string; nrConst: string | null }[] = [
3537
{ name: 'AuthRegistry', src: 'auth_registry_contract-AuthRegistry', nrConst: 'AUTH_REGISTRY_ADDRESS' },
3638
];
3739

38-
type ContractData = {
39-
address: AztecAddress;
40-
classId: Fr;
41-
artifactHash: Fr;
42-
privateFunctionsRoot: Fr;
43-
publicBytecodeCommitment: Fr;
44-
initializationHash: Fr;
45-
};
46-
4740
async function clearDestDir() {
4841
try {
4942
await fs.access(destArtifactsDir);
43+
// If the directory exists, remove it recursively.
5044
await fs.rm(destArtifactsDir, { recursive: true, force: true, maxRetries: 3 });
5145
} catch (err: any) {
52-
if (err.code !== 'ENOENT') {
46+
if (err.code === 'ENOENT') {
47+
// If the directory does not exist, do nothing.
48+
} else {
5349
log(`Error removing dest directory: ${err}`);
5450
}
5551
}
5652
await fs.mkdir(destArtifactsDir, { recursive: true });
5753
}
5854

59-
async function copyArtifact(srcName: string, destName: string): Promise<NoirCompiledContract> {
55+
async function copyArtifact(srcName: string, destName: string) {
6056
const src = path.join(srcPath, `${srcName}.json`);
6157
const artifact = JSON.parse(await fs.readFile(src, 'utf8')) as NoirCompiledContract;
62-
await fs.copyFile(src, path.join(destArtifactsDir, `${destName}.json`));
58+
const dest = path.join(destArtifactsDir, `${destName}.json`);
59+
await fs.copyFile(src, dest);
6360
return artifact;
6461
}
6562

66-
async function generateDeclarationFile(destName: string) {
67-
const content = `
68-
import type { NoirCompiledContract } from '@aztec/stdlib/noir';
69-
const circuit: NoirCompiledContract;
70-
export = circuit;
71-
`;
72-
await fs.writeFile(path.join(destArtifactsDir, `${destName}.d.json.ts`), content);
73-
}
63+
type ContractData = {
64+
address: AztecAddress;
65+
classId: Fr;
66+
artifactHash: Fr;
67+
privateFunctionsRoot: Fr;
68+
publicBytecodeCommitment: Fr;
69+
initializationHash: Fr;
70+
privateFunctions: { selector: FunctionSelector; vkHash: Fr }[];
71+
};
7472

73+
// Precompute all the expensive contract data that can be obtained from the artifact, to avoid redundant computations in clients.
74+
// Standard contracts come from a trusted source (the build pipeline), so no class verifications are needed.
7575
async function computeContractData(artifact: NoirCompiledContract): Promise<ContractData> {
7676
const loaded = loadContractArtifact(artifact);
7777
const contractClass = await getContractClassFromArtifact(loaded);
7878
const constructorArtifact = loaded.functions.find(f => f.name === 'constructor');
7979
const initializationHash = await computeInitializationHash(constructorArtifact, []);
80-
const address = await computeContractAddressFromInstance({
80+
const instance = {
8181
version: 1 as const,
8282
currentContractClassId: contractClass.id,
8383
originalContractClassId: contractClass.id,
8484
initializationHash,
8585
publicKeys: PublicKeys.default(),
8686
salt,
8787
deployer,
88-
});
88+
};
89+
const address = await computeContractAddressFromInstance(instance);
8990
return {
9091
address,
9192
classId: contractClass.id,
9293
artifactHash: contractClass.artifactHash,
9394
privateFunctionsRoot: contractClass.privateFunctionsRoot,
9495
publicBytecodeCommitment: contractClass.publicBytecodeCommitment,
9596
initializationHash,
97+
privateFunctions: contractClass.privateFunctions,
9698
};
9799
}
98100

99-
function renderTsData(entries: { name: string; data: ContractData }[]): string {
100-
const join = (render: (e: { name: string; data: ContractData }) => string) => entries.map(render).join(',\n');
101-
return `// GENERATED FILE - DO NOT EDIT. RUN \`yarn generate\` or \`yarn generate:data\`
102-
import { Fr } from '@aztec/foundation/curves/bn254';
103-
import { AztecAddress } from '@aztec/stdlib/aztec-address';
101+
async function generateDeclarationFile(destName: string) {
102+
const content = `
103+
import type { NoirCompiledContract } from '@aztec/stdlib/noir';
104+
const circuit: NoirCompiledContract;
105+
export = circuit;
106+
`;
107+
await fs.writeFile(path.join(destArtifactsDir, `${destName}.d.json.ts`), content);
108+
}
104109

105-
export const standardContractNames = [${entries.map(e => `'${e.name}'`).join(', ')}] as const;
110+
function generateNames(names: string[]) {
111+
return `
112+
export const standardContractNames = [
113+
${names.map(name => `'${name}'`).join(',\n')}
114+
] as const;
106115
107-
export type StandardContractName = (typeof standardContractNames)[number];
116+
export type StandardContractName = typeof standardContractNames[number];
117+
`;
118+
}
108119

109-
export const StandardContractSalt: Record<StandardContractName, Fr> = {
110-
${join(e => `${e.name}: new Fr(${salt.toNumber()})`)},
111-
};
120+
function generateSalts(names: string[]) {
121+
return `
122+
export const StandardContractSalt: Record<StandardContractName, Fr> = {
123+
${names.map(name => `${name}: new Fr(${salt.toNumber()})`).join(',\n')}
124+
};
125+
`;
126+
}
112127

113-
export const StandardContractAddress: Record<StandardContractName, AztecAddress> = {
114-
${join(e => `${e.name}: AztecAddress.fromString('${e.data.address.toString()}')`)},
115-
};
128+
function generateAddresses(names: string[], contractData: ContractData[]) {
129+
return `
130+
export const StandardContractAddress: Record<StandardContractName, AztecAddress> = {
131+
${contractData.map((d, i) => `${names[i]}: AztecAddress.fromString('${d.address.toString()}')`).join(',\n')}
132+
};
133+
`;
134+
}
116135

117-
export const StandardContractClassId: Record<StandardContractName, Fr> = {
118-
${join(e => `${e.name}: Fr.fromString('${e.data.classId.toString()}')`)},
119-
};
136+
function generateClassIdPreimages(names: string[], contractData: ContractData[]) {
137+
return `
138+
export const StandardContractClassId: Record<StandardContractName, Fr> = {
139+
${contractData.map((d, i) => `${names[i]}: Fr.fromString('${d.classId.toString()}')`).join(',\n')}
140+
};
141+
142+
export const StandardContractClassIdPreimage: Record<StandardContractName, { artifactHash: Fr; privateFunctionsRoot: Fr; publicBytecodeCommitment: Fr }> = {
143+
${contractData
144+
.map(
145+
(d, i) => `${names[i]}: {
146+
artifactHash: Fr.fromString('${d.artifactHash.toString()}'),
147+
privateFunctionsRoot: Fr.fromString('${d.privateFunctionsRoot.toString()}'),
148+
publicBytecodeCommitment: Fr.fromString('${d.publicBytecodeCommitment.toString()}'),
149+
}`,
150+
)
151+
.join(',\n')}
152+
};
153+
154+
export const StandardContractInitializationHash: Record<StandardContractName, Fr> = {
155+
${contractData.map((d, i) => `${names[i]}: Fr.fromString('${d.initializationHash.toString()}')`).join(',\n')}
156+
};
157+
158+
export const StandardContractPrivateFunctions: Record<StandardContractName, { selector: FunctionSelector; vkHash: Fr }[]> = {
159+
${contractData
160+
.map(
161+
(d, i) =>
162+
`${names[i]}: [${d.privateFunctions
163+
.map(
164+
fn =>
165+
`{ selector: FunctionSelector.fromField(Fr.fromString('${fn.selector.toField().toString()}')), vkHash: Fr.fromString('${fn.vkHash.toString()}') }`,
166+
)
167+
.join(', ')}]`,
168+
)
169+
.join(',\n')}
170+
};
171+
`;
172+
}
120173

121-
export const StandardContractClassIdPreimage: Record<
122-
StandardContractName,
123-
{ artifactHash: Fr; privateFunctionsRoot: Fr; publicBytecodeCommitment: Fr }
124-
> = {
125-
${join(
126-
e => `${e.name}: {
127-
artifactHash: Fr.fromString('${e.data.artifactHash.toString()}'),
128-
privateFunctionsRoot: Fr.fromString('${e.data.privateFunctionsRoot.toString()}'),
129-
publicBytecodeCommitment: Fr.fromString('${e.data.publicBytecodeCommitment.toString()}'),
130-
}`,
131-
)},
132-
};
174+
async function generateOutputFile(names: string[], contractData: ContractData[]) {
175+
const content = `
176+
// GENERATED FILE - DO NOT EDIT. RUN \`yarn generate\` or \`yarn generate:data\`
177+
import { Fr } from '@aztec/foundation/curves/bn254';
178+
import { FunctionSelector } from '@aztec/stdlib/abi';
179+
import { AztecAddress } from '@aztec/stdlib/aztec-address';
133180
134-
export const StandardContractInitializationHash: Record<StandardContractName, Fr> = {
135-
${join(e => `${e.name}: Fr.fromString('${e.data.initializationHash.toString()}')`)},
136-
};
137-
`;
181+
${generateNames(names)}
182+
183+
${generateSalts(names)}
184+
185+
${generateAddresses(names, contractData)}
186+
187+
${generateClassIdPreimages(names, contractData)}
188+
`;
189+
await fs.writeFile(outputFilePath, content);
138190
}
139191

140-
function renderNoirAddresses(rows: { nrConst: string; address: AztecAddress }[]): string {
192+
function generateNoirAddresses(rows: { nrConst: string; address: AztecAddress }[]): string {
141193
// Pre-wrapped to survive `nargo fmt`'s line-width pass without diff churn.
142194
const globals = rows
143195
.map(
@@ -156,19 +208,24 @@ ${globals}
156208
async function main() {
157209
await clearDestDir();
158210

159-
const entries: { name: string; nrConst: string; data: ContractData }[] = [];
160-
for (const { name, src, nrConst } of standardContracts) {
211+
const names = standardContracts.map(c => c.name);
212+
const contractDataList: ContractData[] = [];
213+
for (const { name, src } of standardContracts) {
161214
const artifact = await copyArtifact(src, name);
162215
await generateDeclarationFile(name);
163-
entries.push({ name, nrConst, data: await computeContractData(artifact) });
216+
contractDataList.push(await computeContractData(artifact));
164217
}
165218

166-
await fs.writeFile(outputFilePath, renderTsData(entries));
219+
await generateOutputFile(names, contractDataList);
167220

168221
await fs.mkdir(path.dirname(noirAddressesPath), { recursive: true });
169222
await fs.writeFile(
170223
noirAddressesPath,
171-
renderNoirAddresses(entries.map(e => ({ nrConst: e.nrConst, address: e.data.address }))),
224+
generateNoirAddresses(
225+
standardContracts
226+
.map((c, i) => ({ nrConst: c.nrConst, address: contractDataList[i].address }))
227+
.filter((row): row is { nrConst: string; address: AztecAddress } => row.nrConst !== null),
228+
),
172229
);
173230
}
174231

yarn-project/standard-contracts/src/standard_contract_data.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// GENERATED FILE - DO NOT EDIT. RUN `yarn generate` or `yarn generate:data`
22
import { Fr } from '@aztec/foundation/curves/bn254';
3+
import { FunctionSelector } from '@aztec/stdlib/abi';
34
import { AztecAddress } from '@aztec/stdlib/aztec-address';
45

56
export const standardContractNames = ['AuthRegistry'] as const;
@@ -32,3 +33,10 @@ export const StandardContractClassIdPreimage: Record<
3233
export const StandardContractInitializationHash: Record<StandardContractName, Fr> = {
3334
AuthRegistry: Fr.fromString('0x0000000000000000000000000000000000000000000000000000000000000000'),
3435
};
36+
37+
export const StandardContractPrivateFunctions: Record<
38+
StandardContractName,
39+
{ selector: FunctionSelector; vkHash: Fr }[]
40+
> = {
41+
AuthRegistry: [],
42+
};

0 commit comments

Comments
 (0)