Skip to content

Commit

Permalink
feat: noir_wasm compilation of noir programs (#3272)
Browse files Browse the repository at this point in the history
Was starting #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.
  • Loading branch information
dan-aztec authored Nov 22, 2023
1 parent 4743486 commit f9981d5
Show file tree
Hide file tree
Showing 15 changed files with 372 additions and 157 deletions.
4 changes: 2 additions & 2 deletions yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions yarn-project/noir-compiler/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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);
Expand Down
145 changes: 145 additions & 0 deletions yarn-project/noir-compiler/src/cli/compileNoir.ts
Original file line number Diff line number Diff line change
@@ -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('<project-path>', 'Path to the bin or Aztec.nr project to compile')
.option('-o, --outdir <path>', 'Output folder for the binary artifacts, relative to the project path', 'target')
.option('-ts, --typescript <path>', 'Optional output folder for generating typescript wrappers', undefined)
.option('-i, --interface <path>', 'Optional output folder for generating an Aztec.nr contract interface', undefined)
.option('-c --compiler <string>', '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);
}
}
88 changes: 0 additions & 88 deletions yarn-project/noir-compiler/src/cli/contract.ts

This file was deleted.

2 changes: 1 addition & 1 deletion yarn-project/noir-compiler/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -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';
6 changes: 3 additions & 3 deletions yarn-project/noir-compiler/src/compile/nargo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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<NoirCompilationArtifacts[]> {
public compile(): Promise<NoirContractCompilationArtifacts[]> {
const stdio = this.opts.quiet ? 'ignore' : 'inherit';
const nargoBin = this.opts.nargoBin ?? 'nargo';
const version = execSync(`${nargoBin} --version`, { cwd: this.projectPath, stdio: 'pipe' }).toString();
Expand All @@ -51,7 +51,7 @@ export class NargoContractCompiler {
}
}

private collectArtifacts(): NoirCompilationArtifacts[] {
private collectArtifacts(): NoirContractCompilationArtifacts[] {
const contractArtifacts = new Map<string, NoirCompiledContract>();
const debugArtifacts = new Map<string, NoirDebugMetadata>();

Expand Down
Loading

0 comments on commit f9981d5

Please sign in to comment.