55// expensive hashing at runtime and keeps the Noir-side address aligned with the TS-side.
66import { Fr } from '@aztec/foundation/curves/bn254' ;
77import { createConsoleLogger } from '@aztec/foundation/log' ;
8- import { loadContractArtifact } from '@aztec/stdlib/abi' ;
8+ import { FunctionSelector , loadContractArtifact } from '@aztec/stdlib/abi' ;
99import { AztecAddress } from '@aztec/stdlib/aztec-address' ;
1010import {
1111 computeContractAddressFromInstance ,
@@ -30,114 +30,166 @@ const salt = new Fr(1);
3030const 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-
4740async 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.
7575async 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}
156208async 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
0 commit comments