From f9981d5a9b719f0a3220cf069a2bd6ac8c483437 Mon Sep 17 00:00:00 2001 From: Dan Lee <142251406+dan-aztec@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:40:01 -0800 Subject: [PATCH] feat: noir_wasm compilation of noir programs (#3272) Was starting https://github.com/AztecProtocol/aztec-packages/issues/3273 and ran into an issue - the noir_wasm CLI is configured for compiling azec.nr smart contracts only, so needs update to support `bin` package type. --- yarn-project/cli/src/index.ts | 4 +- yarn-project/noir-compiler/src/cli.ts | 4 +- .../noir-compiler/src/cli/compileNoir.ts | 145 ++++++++++++++++++ .../noir-compiler/src/cli/contract.ts | 88 ----------- yarn-project/noir-compiler/src/cli/index.ts | 2 +- .../noir-compiler/src/compile/nargo.ts | 6 +- .../src/compile/noir/noir-wasm-compiler.ts | 68 ++++++-- .../noir-compiler/src/compile/noir/package.ts | 9 ++ .../src/contract-interface-gen/abi.ts | 49 +++++- .../{typescript.ts => contractTypescript.ts} | 0 .../programTypescript.ts} | 36 +---- yarn-project/noir-compiler/src/index.test.ts | 21 ++- yarn-project/noir-compiler/src/index.ts | 14 +- .../noir-compiler/src/noir_artifact.ts | 81 +++++++++- .../noir-contracts/scripts/compile.sh | 2 +- 15 files changed, 372 insertions(+), 157 deletions(-) create mode 100644 yarn-project/noir-compiler/src/cli/compileNoir.ts delete mode 100644 yarn-project/noir-compiler/src/cli/contract.ts rename yarn-project/noir-compiler/src/contract-interface-gen/{typescript.ts => contractTypescript.ts} (100%) rename yarn-project/{noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts => noir-compiler/src/contract-interface-gen/programTypescript.ts} (86%) diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index b01a7b3063a..e99d6f640db 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -20,7 +20,7 @@ import { JsonStringify } from '@aztec/foundation/json-rpc'; import { DebugLogger, LogFn } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; import { fileURLToPath } from '@aztec/foundation/url'; -import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli'; +import { compileNoir, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli'; import { CompleteAddress, ContractData, ExtendedNote, LogFilter } from '@aztec/types'; import { createSecp256k1PeerId } from '@libp2p/peer-id-factory'; @@ -740,7 +740,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { await update(projectPath, contract, options.rpcUrl, options.sandboxVersion, log, debugLogger); }); - compileContract(program, 'compile', log); + compileNoir(program, 'compile', log); generateTypescriptInterface(program, 'generate-typescript', log); generateNoirInterface(program, 'generate-noir-interface', log); diff --git a/yarn-project/noir-compiler/src/cli.ts b/yarn-project/noir-compiler/src/cli.ts index 013791602b1..837975c8075 100644 --- a/yarn-project/noir-compiler/src/cli.ts +++ b/yarn-project/noir-compiler/src/cli.ts @@ -3,7 +3,7 @@ import { createConsoleLogger } from '@aztec/foundation/log'; import { Command } from 'commander'; -import { compileContract } from './cli/contract.js'; +import { compileNoir } from './cli/compileNoir.js'; import { generateNoirInterface } from './cli/noir-interface.js'; import { generateTypescriptInterface } from './cli/typescript.js'; @@ -12,7 +12,7 @@ const log = createConsoleLogger('aztec:compiler-cli'); const main = async () => { program.name('aztec-compile'); - compileContract(program, 'contract', log); + compileNoir(program, 'compile', log); generateTypescriptInterface(program, 'typescript', log); generateNoirInterface(program, 'interface', log); await program.parseAsync(process.argv); diff --git a/yarn-project/noir-compiler/src/cli/compileNoir.ts b/yarn-project/noir-compiler/src/cli/compileNoir.ts new file mode 100644 index 00000000000..7e00fed39fa --- /dev/null +++ b/yarn-project/noir-compiler/src/cli/compileNoir.ts @@ -0,0 +1,145 @@ +import { ContractArtifact } from '@aztec/foundation/abi'; +import { LogFn } from '@aztec/foundation/log'; + +import { Command } from 'commander'; +import { mkdirSync, writeFileSync } from 'fs'; +import { mkdirpSync } from 'fs-extra'; +import path, { resolve } from 'path'; + +import { + ProgramArtifact, + compileUsingNargo, + compileUsingNoirWasm, + generateNoirContractInterface, + generateTypescriptContractInterface, + generateTypescriptProgramInterface, +} from '../index.js'; + +/** + * CLI options for configuring behavior + */ +interface Options { + // eslint-disable-next-line jsdoc/require-jsdoc + outdir: string; + // eslint-disable-next-line jsdoc/require-jsdoc + typescript: string | undefined; + // eslint-disable-next-line jsdoc/require-jsdoc + interface: string | undefined; + // eslint-disable-next-line jsdoc/require-jsdoc + compiler: string | undefined; +} +/** + * Registers a 'contract' command on the given commander program that compiles an Aztec.nr contract project. + * @param program - Commander program. + * @param log - Optional logging function. + * @returns The program with the command registered. + */ +export function compileNoir(program: Command, name = 'compile', log: LogFn = () => {}): Command { + return program + .command(name) + .argument('', 'Path to the bin or Aztec.nr project to compile') + .option('-o, --outdir ', 'Output folder for the binary artifacts, relative to the project path', 'target') + .option('-ts, --typescript ', 'Optional output folder for generating typescript wrappers', undefined) + .option('-i, --interface ', 'Optional output folder for generating an Aztec.nr contract interface', undefined) + .option('-c --compiler ', 'Which compiler to use. Either nargo or wasm. Defaults to nargo', 'wasm') + .description('Compiles the Noir Source in the target project') + + .action(async (projectPath: string, options: Options) => { + const { compiler } = options; + if (typeof projectPath !== 'string') { + throw new Error(`Missing project path argument`); + } + if (compiler !== 'nargo' && compiler !== 'wasm') { + throw new Error(`Invalid compiler: ${compiler}`); + } + + const compile = compiler === 'wasm' ? compileUsingNoirWasm : compileUsingNargo; + log(`Compiling ${projectPath} with ${compiler} backend...`); + const results = await compile(projectPath, { log }); + for (const result of results) { + generateOutput(projectPath, result, options, log); + } + }); +} + +/** + * + * @param contract - output from compiler, to serialize locally. branch based on Contract vs Program + */ +function generateOutput( + projectPath: string, + _result: ContractArtifact | ProgramArtifact, + options: Options, + log: LogFn, +) { + const contract = _result as ContractArtifact; + if (contract.name) { + return generateContractOutput(projectPath, contract, options, log); + } else { + const program = _result as ProgramArtifact; + if (program.abi) { + return generateProgramOutput(projectPath, program, options, log); + } + } +} +/** + * + * @param program - output from compiler, to serialize locally + */ +function generateProgramOutput(projectPath: string, program: ProgramArtifact, options: Options, log: LogFn) { + const currentDir = process.cwd(); + const { outdir, typescript, interface: noirInterface } = options; + const artifactPath = resolve(projectPath, outdir, `${program.name ? program.name : 'main'}.json`); + log(`Writing ${program.name} artifact to ${path.relative(currentDir, artifactPath)}`); + mkdirSync(path.dirname(artifactPath), { recursive: true }); + writeFileSync(artifactPath, JSON.stringify(program, null, 2)); + + if (noirInterface) { + log(`noirInterface generation not implemented for programs`); + // not implemented + } + + if (typescript) { + // just need type definitions, since a lib has just one entry point + const tsPath = resolve(projectPath, typescript, `../types/${program.name}_types.ts`); + log(`Writing ${program.name} typescript types to ${path.relative(currentDir, tsPath)}`); + const tsWrapper = generateTypescriptProgramInterface(program.abi); + mkdirpSync(path.dirname(tsPath)); + writeFileSync(tsPath, tsWrapper); + } +} + +/** + * + * @param contract - output from compiler, to serialize locally + */ +function generateContractOutput(projectPath: string, contract: ContractArtifact, options: Options, log: LogFn) { + const currentDir = process.cwd(); + const { outdir, typescript, interface: noirInterface } = options; + const artifactPath = resolve(projectPath, outdir, `${contract.name}.json`); + log(`Writing ${contract.name} artifact to ${path.relative(currentDir, artifactPath)}`); + mkdirSync(path.dirname(artifactPath), { recursive: true }); + writeFileSync(artifactPath, JSON.stringify(contract, null, 2)); + + if (noirInterface) { + const noirInterfacePath = resolve(projectPath, noirInterface, `${contract.name}_interface.nr`); + log(`Writing ${contract.name} Aztec.nr external interface to ${path.relative(currentDir, noirInterfacePath)}`); + const noirWrapper = generateNoirContractInterface(contract); + mkdirpSync(path.dirname(noirInterfacePath)); + writeFileSync(noirInterfacePath, noirWrapper); + } + + if (typescript) { + const tsPath = resolve(projectPath, typescript, `${contract.name}.ts`); + log(`Writing ${contract.name} typescript interface to ${path.relative(currentDir, tsPath)}`); + let relativeArtifactPath = path.relative(path.dirname(tsPath), artifactPath); + if (relativeArtifactPath === `${contract.name}.json`) { + // relative path edge case, prepending ./ for local import - the above logic just does + // `${contract.name}.json`, which is not a valid import for a file in the same directory + relativeArtifactPath = `./${contract.name}.json`; + } + const tsWrapper = generateTypescriptContractInterface(contract, relativeArtifactPath); + mkdirpSync(path.dirname(tsPath)); + writeFileSync(tsPath, tsWrapper); + } +} diff --git a/yarn-project/noir-compiler/src/cli/contract.ts b/yarn-project/noir-compiler/src/cli/contract.ts deleted file mode 100644 index a4cf5bafaf0..00000000000 --- a/yarn-project/noir-compiler/src/cli/contract.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { LogFn } from '@aztec/foundation/log'; - -import { Command } from 'commander'; -import { mkdirSync, writeFileSync } from 'fs'; -import { mkdirpSync } from 'fs-extra'; -import path, { resolve } from 'path'; - -import { - compileUsingNargo, - compileUsingNoirWasm, - generateNoirContractInterface, - generateTypescriptContractInterface, -} from '../index.js'; - -/** - * Registers a 'contract' command on the given commander program that compiles an Aztec.nr contract project. - * @param program - Commander program. - * @param log - Optional logging function. - * @returns The program with the command registered. - */ -export function compileContract(program: Command, name = 'contract', log: LogFn = () => {}): Command { - return program - .command(name) - .argument('', 'Path to the Aztec.nr project to compile') - .option('-o, --outdir ', 'Output folder for the binary artifacts, relative to the project path', 'target') - .option('-ts, --typescript ', 'Optional output folder for generating typescript wrappers', undefined) - .option('-i, --interface ', 'Optional output folder for generating an Aztec.nr contract interface', undefined) - .option('-c --compiler ', 'Which compiler to use. Either nargo or wasm. Defaults to nargo', 'wasm') - .description('Compiles the contracts in the target project') - - .action( - async ( - projectPath: string, - /* eslint-disable jsdoc/require-jsdoc */ - options: { - outdir: string; - typescript: string | undefined; - interface: string | undefined; - compiler: string | undefined; - }, - /* eslint-enable jsdoc/require-jsdoc */ - ) => { - const { outdir, typescript, interface: noirInterface, compiler } = options; - if (typeof projectPath !== 'string') { - throw new Error(`Missing project path argument`); - } - if (compiler !== 'nargo' && compiler !== 'wasm') { - throw new Error(`Invalid compiler: ${compiler}`); - } - const currentDir = process.cwd(); - - const compile = compiler === 'wasm' ? compileUsingNoirWasm : compileUsingNargo; - log(`Compiling contracts...`); - const result = await compile(projectPath, { log }); - - for (const contract of result) { - const artifactPath = resolve(projectPath, outdir, `${contract.name}.json`); - log(`Writing ${contract.name} artifact to ${path.relative(currentDir, artifactPath)}`); - mkdirSync(path.dirname(artifactPath), { recursive: true }); - writeFileSync(artifactPath, JSON.stringify(contract, null, 2)); - - if (noirInterface) { - const noirInterfacePath = resolve(projectPath, noirInterface, `${contract.name}_interface.nr`); - log( - `Writing ${contract.name} Aztec.nr external interface to ${path.relative(currentDir, noirInterfacePath)}`, - ); - const noirWrapper = generateNoirContractInterface(contract); - mkdirpSync(path.dirname(noirInterfacePath)); - writeFileSync(noirInterfacePath, noirWrapper); - } - - if (typescript) { - const tsPath = resolve(projectPath, typescript, `${contract.name}.ts`); - log(`Writing ${contract.name} typescript interface to ${path.relative(currentDir, tsPath)}`); - let relativeArtifactPath = path.relative(path.dirname(tsPath), artifactPath); - if (relativeArtifactPath === `${contract.name}.json`) { - // relative path edge case, prepending ./ for local import - the above logic just does - // `${contract.name}.json`, which is not a valid import for a file in the same directory - relativeArtifactPath = `./${contract.name}.json`; - } - const tsWrapper = generateTypescriptContractInterface(contract, relativeArtifactPath); - mkdirpSync(path.dirname(tsPath)); - writeFileSync(tsPath, tsWrapper); - } - } - }, - ); -} diff --git a/yarn-project/noir-compiler/src/cli/index.ts b/yarn-project/noir-compiler/src/cli/index.ts index 8d312f9ec5c..0687e01706f 100644 --- a/yarn-project/noir-compiler/src/cli/index.ts +++ b/yarn-project/noir-compiler/src/cli/index.ts @@ -1,3 +1,3 @@ -export { compileContract } from './contract.js'; +export { compileNoir } from './compileNoir.js'; export { generateNoirInterface } from './noir-interface.js'; export { generateTypescriptInterface } from './typescript.js'; diff --git a/yarn-project/noir-compiler/src/compile/nargo.ts b/yarn-project/noir-compiler/src/compile/nargo.ts index 85443ebf631..134925d0cdf 100644 --- a/yarn-project/noir-compiler/src/compile/nargo.ts +++ b/yarn-project/noir-compiler/src/compile/nargo.ts @@ -6,7 +6,7 @@ import { emptyDirSync } from 'fs-extra'; import path from 'path'; import { NoirCommit, NoirTag } from '../index.js'; -import { NoirCompilationArtifacts, NoirCompiledContract, NoirDebugMetadata } from '../noir_artifact.js'; +import { NoirCompiledContract, NoirContractCompilationArtifacts, NoirDebugMetadata } from '../noir_artifact.js'; /** Compilation options */ export type CompileOpts = { @@ -31,7 +31,7 @@ export class NargoContractCompiler { * Compiles the contracts in projectPath and returns the Aztec.nr artifact. * @returns Aztec.nr artifact of the compiled contracts. */ - public compile(): Promise { + public compile(): Promise { const stdio = this.opts.quiet ? 'ignore' : 'inherit'; const nargoBin = this.opts.nargoBin ?? 'nargo'; const version = execSync(`${nargoBin} --version`, { cwd: this.projectPath, stdio: 'pipe' }).toString(); @@ -51,7 +51,7 @@ export class NargoContractCompiler { } } - private collectArtifacts(): NoirCompilationArtifacts[] { + private collectArtifacts(): NoirContractCompilationArtifacts[] { const contractArtifacts = new Map(); const debugArtifacts = new Map(); diff --git a/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts b/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts index 03447e0f189..30beca72558 100644 --- a/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts +++ b/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts @@ -3,7 +3,7 @@ import { LogFn, createDebugLogger } from '@aztec/foundation/log'; import { CompileError, compile } from '@noir-lang/noir_wasm'; import { isAbsolute } from 'node:path'; -import { NoirCompilationArtifacts } from '../../noir_artifact.js'; +import { NoirCompilationResult, NoirProgramCompilationArtifacts } from '../../noir_artifact.js'; import { NoirDependencyManager } from './dependencies/dependency-manager.js'; import { GithubDependencyResolver as GithubCodeArchiveDependencyResolver } from './dependencies/github-dependency-resolver.js'; import { LocalDependencyResolver } from './dependencies/local-dependency-resolver.js'; @@ -54,9 +54,6 @@ export class NoirWasmContractCompiler { } const noirPackage = NoirPackage.open(projectPath, fileManager); - if (noirPackage.getType() !== 'contract') { - throw new Error('This is not a contract project'); - } const dependencyManager = new NoirDependencyManager( [ @@ -80,15 +77,65 @@ export class NoirWasmContractCompiler { } /** - * Compiles the project. + * Compile EntryPoint */ - public async compile(): Promise { - const isContract = this.#package.getType() === 'contract'; - // limit to contracts-only because the rest of the pipeline only supports processing contracts - if (!isContract) { - throw new Error('Noir project is not a contract'); + public async compile(): Promise { + if (this.#package.getType() === 'contract') { + this.#debugLog(`Compiling Contract at ${this.#package.getEntryPointPath()}`); + return await this.compileContract(); + } else if (this.#package.getType() === 'bin') { + this.#debugLog(`Compiling Program at ${this.#package.getEntryPointPath()}`); + return await this.compileProgram(); + } else { + this.#log( + `Compile skipped - only supports compiling "contract" and "bin" package types (${this.#package.getType()})`, + ); + return []; } + } + + /** + * Compiles the Program. + */ + public async compileProgram(): Promise { + await this.#dependencyManager.resolveDependencies(); + this.#debugLog(`Dependencies: ${this.#dependencyManager.getPackageNames().join(', ')}`); + + initializeResolver(this.#resolveFile); + try { + const isContract: boolean = false; + const result = compile(this.#package.getEntryPointPath(), isContract, { + /* eslint-disable camelcase */ + root_dependencies: this.#dependencyManager.getEntrypointDependencies(), + library_dependencies: this.#dependencyManager.getLibraryDependencies(), + /* eslint-enable camelcase */ + }); + + if (!('program' in result)) { + throw new Error('No program found in compilation result'); + } + + return [{ name: this.#package.getNoirPackageConfig().package.name, ...result }]; + } catch (err) { + if (err instanceof Error && err.name === 'CompileError') { + this.#processCompileError(err as CompileError); + } + + throw err; + } + } + + /** + * Compiles the Contract. + */ + public async compileContract(): Promise { + if (!(this.#package.getType() === 'contract' || this.#package.getType() === 'bin')) { + this.#log( + `Compile skipped - only supports compiling "contract" and "bin" package types (${this.#package.getType()})`, + ); + return []; + } this.#debugLog(`Compiling contract at ${this.#package.getEntryPointPath()}`); await this.#dependencyManager.resolveDependencies(); this.#debugLog(`Dependencies: ${this.#dependencyManager.getPackageNames().join(', ')}`); @@ -96,6 +143,7 @@ export class NoirWasmContractCompiler { initializeResolver(this.#resolveFile); try { + const isContract: boolean = true; const result = compile(this.#package.getEntryPointPath(), isContract, { /* eslint-disable camelcase */ root_dependencies: this.#dependencyManager.getEntrypointDependencies(), diff --git a/yarn-project/noir-compiler/src/compile/noir/package.ts b/yarn-project/noir-compiler/src/compile/noir/package.ts index c79fc662dd7..4c5f42f1077 100644 --- a/yarn-project/noir-compiler/src/compile/noir/package.ts +++ b/yarn-project/noir-compiler/src/compile/noir/package.ts @@ -29,6 +29,13 @@ export class NoirPackage { return this.#packagePath; } + /** + * Gets this package's Nargo.toml (NoirPackage)Config. + */ + public getNoirPackageConfig() { + return this.#config; + } + /** * The path to the source directory. */ @@ -44,6 +51,8 @@ export class NoirPackage { switch (this.getType()) { case 'lib': + // we shouldn't need to compile `lib` type, since the .nr source is read directly + // when the lib is used as a dependency elsewhere. entrypoint = 'lib.nr'; break; case 'contract': diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts b/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts index 7ba4cf555ba..61b47ea24d6 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts @@ -3,7 +3,15 @@ import { ContractArtifact, DebugMetadata, FunctionArtifact, FunctionType } from import { deflate } from 'pako'; import { mockVerificationKey } from '../mocked_keys.js'; -import { NoirCompilationArtifacts, NoirFunctionEntry } from '../noir_artifact.js'; +import { + NoirCompilationResult, + NoirContractCompilationArtifacts, + NoirFunctionEntry, + NoirProgramCompilationArtifacts, + ProgramArtifact, + isNoirContractCompilationArtifacts, + isNoirProgramCompilationArtifacts, +} from '../noir_artifact.js'; /** * Generates a function build artifact. Replaces verification key with a mock value. @@ -34,13 +42,50 @@ function generateFunctionArtifact(fn: NoirFunctionEntry): FunctionArtifact { }; } +/** + * Entrypoint for generating the .json artifact for compiled contract or program + * @param compileResult - Noir build output. + * @returns Aztec contract build artifact. + */ +export function generateArtifact(compileResult: NoirCompilationResult) { + if (isNoirContractCompilationArtifacts(compileResult)) { + return generateContractArtifact(compileResult); + } else if (isNoirProgramCompilationArtifacts(compileResult)) { + return generateProgramArtifact(compileResult); + } else { + throw Error('Unsupported artifact type'); + } +} + +/** + * Given a Nargo output generates an Aztec-compatible contract artifact. + * @param compiled - Noir build output. + * @returns Aztec contract build artifact. + */ +export function generateProgramArtifact( + { program }: NoirProgramCompilationArtifacts, + // eslint-disable-next-line camelcase + noir_version?: string, +): ProgramArtifact { + return { + // eslint-disable-next-line camelcase + noir_version, + hash: program.hash, + backend: program.backend, + abi: program.abi, + + // TODO: should we parse and write the debug? it doesn't seem to be in the nargo output + // debug: someParsedDebug, + }; +} + /** * Given a Nargo output generates an Aztec-compatible contract artifact. * @param compiled - Noir build output. * @returns Aztec contract build artifact. */ export function generateContractArtifact( - { contract, debug }: NoirCompilationArtifacts, + { contract, debug }: NoirContractCompilationArtifacts, aztecNrVersion?: string, ): ContractArtifact { const originalFunctions = contract.functions; diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts b/yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts similarity index 100% rename from yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts rename to yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts diff --git a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts b/yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts similarity index 86% rename from yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts rename to yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts index f189d592688..257bc287d12 100644 --- a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts @@ -1,10 +1,5 @@ import { ABIType } from '@aztec/foundation/abi'; -import { createConsoleLogger } from '@aztec/foundation/log'; -import { NoirCompiledCircuit, NoirFunctionAbi } from '@aztec/noir-compiler'; - -import fs from 'fs/promises'; - -const log = createConsoleLogger('aztec:noir-contracts'); +import { NoirFunctionAbi } from '@aztec/noir-compiler'; /** * Keep track off all of the Noir primitive types that were used. @@ -151,7 +146,7 @@ function generateStructInterfaces(type: ABIType, output: Set): string { * @param abiObj - The ABI to generate the interface for. * @returns The TypeScript code to define the interface. */ -function generateTsInterface(abiObj: NoirFunctionAbi): string { +export function generateTypescriptProgramInterface(abiObj: NoirFunctionAbi): string { let result = ``; const outputStructs = new Set(); @@ -192,30 +187,3 @@ function generateTsInterface(abiObj: NoirFunctionAbi): string { result ); } - -const circuits = [ - 'private_kernel_init', - 'private_kernel_inner', - 'private_kernel_ordering', - 'public_kernel_private_previous', - 'public_kernel_public_previous', - 'rollup_base', - 'rollup_merge', - 'rollup_root', -]; - -const main = async () => { - for (const circuit of circuits) { - const rawData = await fs.readFile(`./src/target/${circuit}.json`, 'utf-8'); - const abiObj: NoirCompiledCircuit = JSON.parse(rawData); - const generatedInterface = generateTsInterface(abiObj.abi); - await fs.writeFile(`./src/types/${circuit}_types.ts`, generatedInterface); - } -}; - -try { - await main(); -} catch (err: unknown) { - log(`Error generating types ${err}`); - process.exit(1); -} diff --git a/yarn-project/noir-compiler/src/index.test.ts b/yarn-project/noir-compiler/src/index.test.ts index 39c84e61210..4922c1af04c 100644 --- a/yarn-project/noir-compiler/src/index.test.ts +++ b/yarn-project/noir-compiler/src/index.test.ts @@ -6,6 +6,7 @@ import { execSync } from 'child_process'; import path from 'path'; import { + ProgramArtifact, compileUsingNargo, compileUsingNoirWasm, generateNoirContractInterface, @@ -33,25 +34,33 @@ describe('noir-compiler', () => { const nargoAvailable = isNargoAvailable(); const conditionalDescribe = nargoAvailable ? describe : describe.skip; const conditionalIt = nargoAvailable ? it : it.skip; - const withoutDebug = ({ debug: _debug, ...rest }: ContractArtifact): Omit => rest; + const withoutDebug = ({ + debug: _debug, + ...rest + }: ContractArtifact | ProgramArtifact): Omit => rest; + + function compilerTest( + compileFn: (path: string, opts: { log: LogFn }) => Promise<(ProgramArtifact | ContractArtifact)[]>, + ) { + let compiled: (ProgramArtifact | ContractArtifact)[]; + let compiledContract: ContractArtifact[]; - function compilerTest(compileFn: (path: string, opts: { log: LogFn }) => Promise) { - let compiled: ContractArtifact[]; beforeAll(async () => { compiled = await compileFn(projectPath, { log }); + compiledContract = compiled.map(_compiled => _compiled as ContractArtifact); }); it('compiles the test contract', () => { - expect(compiled.map(withoutDebug)).toMatchSnapshot(); + expect(compiledContract.map(withoutDebug)).toMatchSnapshot(); }); it('generates typescript interface', () => { - const result = generateTypescriptContractInterface(compiled[0], `../target/test.json`); + const result = generateTypescriptContractInterface(compiledContract[0], `../target/test.json`); expect(result).toMatchSnapshot(); }); it('generates Aztec.nr external interface', () => { - const result = generateNoirContractInterface(compiled[0]); + const result = generateNoirContractInterface(compiledContract[0]); expect(result).toMatchSnapshot(); }); } diff --git a/yarn-project/noir-compiler/src/index.ts b/yarn-project/noir-compiler/src/index.ts index a8ecf376595..768d328c8ae 100644 --- a/yarn-project/noir-compiler/src/index.ts +++ b/yarn-project/noir-compiler/src/index.ts @@ -6,12 +6,14 @@ import { join, resolve } from 'path'; import { CompileOpts, NargoContractCompiler } from './compile/nargo.js'; import { FileManager } from './compile/noir/file-manager/file-manager.js'; import { NoirWasmCompileOptions, NoirWasmContractCompiler } from './compile/noir/noir-wasm-compiler.js'; -import { generateContractArtifact } from './contract-interface-gen/abi.js'; +import { generateArtifact, generateContractArtifact } from './contract-interface-gen/abi.js'; +import { ProgramArtifact } from './noir_artifact.js'; export * from './versions.js'; +export { generateTypescriptContractInterface } from './contract-interface-gen/contractTypescript.js'; export { generateNoirContractInterface } from './contract-interface-gen/noir.js'; -export { generateTypescriptContractInterface } from './contract-interface-gen/typescript.js'; +export { generateTypescriptProgramInterface } from './contract-interface-gen/programTypescript.js'; export { generateContractArtifact }; export * from './noir_artifact.js'; @@ -37,12 +39,12 @@ export async function compileUsingNargo(projectPath: string, opts: CompileOpts = export async function compileUsingNoirWasm( projectPath: string, opts: NoirWasmCompileOptions, -): Promise { +): Promise<(ContractArtifact | ProgramArtifact)[]> { const cacheRoot = process.env.XDG_CACHE_HOME ?? join(process.env.HOME ?? '', '.cache'); const fileManager = new FileManager(fs, join(cacheRoot, 'aztec-noir-compiler')); const compiler = NoirWasmContractCompiler.new(fileManager, resolve(projectPath), opts); const artifacts = await compiler.compile(); - const resolvedAztecNrVersion = compiler.getResolvedAztecNrVersion(); - - return artifacts.map(artifact => generateContractArtifact(artifact, resolvedAztecNrVersion)); + return artifacts.map(artifact => { + return generateArtifact(artifact); + }); } diff --git a/yarn-project/noir-compiler/src/noir_artifact.ts b/yarn-project/noir-compiler/src/noir_artifact.ts index f03742a274c..6d7d1a090ef 100644 --- a/yarn-project/noir-compiler/src/noir_artifact.ts +++ b/yarn-project/noir-compiler/src/noir_artifact.ts @@ -54,7 +54,7 @@ export interface NoirCompiledContract { */ export interface NoirCompiledCircuit { /** The hash of the circuit. */ - hash: number; + hash?: number; /** Compilation backend. */ backend: string; /** @@ -65,6 +65,40 @@ export interface NoirCompiledCircuit { bytecode: string; } +/** + * Defines artifact of a contract. + */ +export interface ProgramArtifact { + /** + * version of noir used to compile + */ + noir_version?: string; + /** + * the name of the project, read from Nargo.toml + */ + name?: string; + /** + * The hash of the contract. + */ + hash?: number; + + /** + * The compilation backend of the artifact. + */ + backend: string; + + /** + * The abi of the program. + */ + abi: any; // TODO: type + + /** + * The debug metadata of the contract. + * It's used to include the relevant source code section when a constraint is not met during simulation. + */ + debug?: NoirDebugMetadata; +} + /** * The debug metadata of an Aztec.nr contract. */ @@ -82,13 +116,56 @@ export interface NoirDebugMetadata { /** * The compilation artifacts of a given contract. */ -export interface NoirCompilationArtifacts { +export interface NoirContractCompilationArtifacts { /** * The compiled contract. */ contract: NoirCompiledContract; + + /** + * The artifact that contains the debug metadata about the contract. + */ + debug?: NoirDebugMetadata; +} + +/** + * The compilation artifacts of a given program. + */ +export interface NoirProgramCompilationArtifacts { + /** + * not part of the compilation output, injected later + */ + name: string; + /** + * The compiled contract. + */ + program: NoirCompiledCircuit; + /** * The artifact that contains the debug metadata about the contract. */ debug?: NoirDebugMetadata; } + +/** + * output of Noir Wasm compilation, can be for a contract or lib/binary + */ +export type NoirCompilationResult = NoirContractCompilationArtifacts | NoirProgramCompilationArtifacts; + +/** + * Check if it has Contract unique property + */ +export function isNoirContractCompilationArtifacts( + artifact: NoirCompilationResult, +): artifact is NoirContractCompilationArtifacts { + return (artifact as NoirContractCompilationArtifacts).contract !== undefined; +} + +/** + * Check if it has Contract unique property + */ +export function isNoirProgramCompilationArtifacts( + artifact: NoirCompilationResult, +): artifact is NoirProgramCompilationArtifacts { + return (artifact as NoirProgramCompilationArtifacts).program !== undefined; +} diff --git a/yarn-project/noir-contracts/scripts/compile.sh b/yarn-project/noir-contracts/scripts/compile.sh index 10594042c8e..fe2096f4cf4 100755 --- a/yarn-project/noir-contracts/scripts/compile.sh +++ b/yarn-project/noir-contracts/scripts/compile.sh @@ -13,7 +13,7 @@ build() { echo "Compiling $CONTRACT_NAME..." rm -rf ${CONTRACT_FOLDER}/target - node --no-warnings "$COMPILER" contract "$CONTRACT_FOLDER" + node --no-warnings "$COMPILER" compile "$CONTRACT_FOLDER" } export -f build