Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions yarn-project/aztec/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
source $(git rev-parse --show-toplevel)/ci3/source_bootstrap

repo_root=$(git rev-parse --show-toplevel)
export NARGO=${NARGO:-$repo_root/noir/noir-repo/target/release/nargo}
export BB=${BB:-$repo_root/barretenberg/cpp/build/bin/bb}

hash=$(../bootstrap.sh hash)

function test_cmds {
echo "$hash:ISOLATE=1:NAME=aztec/src/cli/cmds/compile.test.ts NARGO=$NARGO BB=$BB yarn-project/scripts/run_test.sh aztec/src/cli/cmds/compile.test.ts"
}

case "$cmd" in
"")
;;
*)
default_cmd_handler "$@"
;;
esac
2 changes: 1 addition & 1 deletion yarn-project/aztec/scripts/aztec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ case $cmd in

aztec start "$@"
;;
compile|new|init|flamegraph)
new|init|flamegraph)
$script_dir/${cmd}.sh "$@"
;;
*)
Expand Down
44 changes: 0 additions & 44 deletions yarn-project/aztec/scripts/compile.sh

This file was deleted.

4 changes: 3 additions & 1 deletion yarn-project/aztec/src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { createConsoleLogger, createLogger } from '@aztec/foundation/log';

import { Command } from 'commander';

import { injectCompileCommand } from '../cli/cmds/compile.js';
import { injectMigrateCommand } from '../cli/cmds/migrate_ha_db.js';
import { injectAztecCommands } from '../cli/index.js';
import { getCliVersion } from '../cli/release_version.js';
Expand Down Expand Up @@ -47,7 +48,7 @@ async function main() {

const cliVersion = getCliVersion();
let program = new Command('aztec');
program.description('Aztec command line interface').version(cliVersion);
program.description('Aztec command line interface').version(cliVersion).enablePositionalOptions();
program = injectAztecCommands(program, userLog, debugLogger);
program = injectBuilderCommands(program);
program = injectContractCommands(program, userLog, debugLogger);
Expand All @@ -56,6 +57,7 @@ async function main() {
program = injectAztecNodeCommands(program, userLog, debugLogger);
program = injectMiscCommands(program, userLog);
program = injectValidatorKeysCommands(program, userLog);
program = injectCompileCommand(program, userLog);
program = injectMigrateCommand(program, userLog);

await program.parseAsync(process.argv);
Expand Down
1 change: 0 additions & 1 deletion yarn-project/aztec/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Additional commands:

init [folder] [options] creates a new Aztec Noir project.
new <path> [options] creates a new Aztec Noir project in a new directory.
compile [options] compiles Aztec Noir contracts.
test [options] starts a TXE and runs "nargo test" using it as the oracle resolver.
`,
);
Expand Down
82 changes: 82 additions & 0 deletions yarn-project/aztec/src/cli/cmds/compile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
import { execFileSync } from 'child_process';
import { existsSync, readFileSync, rmSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';

const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../..');
const CLI = join(PACKAGE_ROOT, 'dest/bin/index.js');
const WORKSPACE = join(PACKAGE_ROOT, 'test/mixed-workspace');
const TARGET = join(WORKSPACE, 'target');
const CODEGEN_OUT = join(WORKSPACE, 'codegen-output');

// Compiles a mixed workspace containing both a contract and a plain circuit,
// then runs codegen. Validates that:
// - Contract artifacts have a functions array and are transpiled
// - Program (circuit) artifacts do not have functions and are not transpiled
// - Codegen produces TypeScript only for contracts, not circuits
describe('aztec compile integration', () => {
beforeAll(() => {
cleanupArtifacts();
runCompile();
runCodegen();
}, 120_000);

afterAll(() => {
cleanupArtifacts();
});

it('contract artifact has functions array', () => {
const artifact = JSON.parse(readFileSync(join(TARGET, 'simple_contract-SimpleContract.json'), 'utf-8'));
expect(Array.isArray(artifact.functions)).toBe(true);
expect(artifact.functions.length).toBeGreaterThan(0);
});

it('program artifact does not have functions', () => {
const artifact = JSON.parse(readFileSync(join(TARGET, 'simple_circuit.json'), 'utf-8'));
expect(artifact.functions).toBeUndefined();
});

it('contract artifact was transpiled', () => {
const artifact = JSON.parse(readFileSync(join(TARGET, 'simple_contract-SimpleContract.json'), 'utf-8'));
expect(artifact.transpiled).toBe(true);
});

it('program artifact was not transpiled', () => {
const artifact = JSON.parse(readFileSync(join(TARGET, 'simple_circuit.json'), 'utf-8'));
expect(artifact.transpiled).toBeFalsy();
});

it('codegen produced TypeScript for contract', () => {
expect(existsSync(join(CODEGEN_OUT, 'SimpleContract.ts'))).toBe(true);
});

it('codegen did not produce TypeScript for circuit', () => {
expect(existsSync(join(CODEGEN_OUT, 'SimpleCircuit.ts'))).toBe(false);
});
});

function cleanupArtifacts() {
rmSync(TARGET, { recursive: true, force: true });
rmSync(CODEGEN_OUT, { recursive: true, force: true });
rmSync(join(WORKSPACE, 'codegenCache.json'), { force: true });
}

function runCompile() {
try {
execFileSync('node', [CLI, 'compile'], { cwd: WORKSPACE, stdio: 'pipe' });
} catch (e: any) {
throw new Error(`compile failed:\n${e.stderr?.toString() ?? e.message}`);
}
}

function runCodegen() {
try {
execFileSync('node', [CLI, 'codegen', 'target', '-o', 'codegen-output', '-f'], {
cwd: WORKSPACE,
stdio: 'pipe',
});
} catch (e: any) {
throw new Error(`codegen failed:\n${e.stderr?.toString() ?? e.message}`);
}
}
107 changes: 107 additions & 0 deletions yarn-project/aztec/src/cli/cmds/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import type { LogFn } from '@aztec/foundation/log';

import { execFileSync, spawn } from 'child_process';
import type { Command } from 'commander';
import { readFile, readdir, writeFile } from 'fs/promises';
import { join } from 'path';

/** Spawns a command with inherited stdio and rejects on non-zero exit. */
function run(cmd: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, { stdio: 'inherit' });
child.on('error', reject);
child.on('close', code => {
if (code !== 0) {
reject(new Error(`${cmd} exited with code ${code}`));
} else {
resolve();
}
});
});
}

/** Returns paths to contract artifacts in the target directory.
* Contract artifacts are identified by having a `functions` array in the JSON.
*/
async function collectContractArtifacts(): Promise<string[]> {
let files: string[];
try {
files = await readdir('target');
} catch (err: any) {
if (err?.code === 'ENOENT') {
return [];
}
throw new Error(`Failed to read target directory: ${err.message}`);
}

const artifacts: string[] = [];
for (const file of files) {
if (!file.endsWith('.json')) {
continue;
}
const filePath = join('target', file);
const content = JSON.parse(await readFile(filePath, 'utf-8'));
if (Array.isArray(content.functions)) {
artifacts.push(filePath);
}
}
return artifacts;
}

/** Strips the `__aztec_nr_internals__` prefix from function names in contract artifacts. */
async function stripInternalPrefixes(artifactPaths: string[]): Promise<void> {
for (const path of artifactPaths) {
const artifact = JSON.parse(await readFile(path, 'utf-8'));
for (const fn of artifact.functions) {
if (typeof fn.name === 'string') {
fn.name = fn.name.replace(/^__aztec_nr_internals__/, '');
}
}
await writeFile(path, JSON.stringify(artifact, null, 2) + '\n');
}
}

/** Compiles Aztec Noir contracts and postprocesses artifacts. */
async function compileAztecContract(nargoArgs: string[], log: LogFn): Promise<void> {
const nargo = process.env.NARGO ?? 'nargo';
const bb = process.env.BB ?? 'bb';

await run(nargo, ['compile', ...nargoArgs]);

const artifacts = await collectContractArtifacts();

if (artifacts.length > 0) {
log('Postprocessing contracts...');
const bbArgs = artifacts.flatMap(a => ['-i', a]);
await run(bb, ['aztec_process', ...bbArgs]);

// TODO: This should be part of bb aztec_process!
await stripInternalPrefixes(artifacts);
}

log('Compilation complete!');
}

export function injectCompileCommand(program: Command, log: LogFn): Command {
program
.command('compile')
.argument('[nargo-args...]')
.passThroughOptions()
.allowUnknownOption()
.description(
'Compile Aztec Noir contracts using nargo and postprocess them to generate transpiled artifacts and verification keys. All options are forwarded to nargo compile.',
)
.addHelpText('after', () => {
// Show nargo's own compile options so users see all available flags in one place.
const nargo = process.env.NARGO ?? 'nargo';
try {
const output = execFileSync(nargo, ['compile', '--help'], { encoding: 'utf-8' });
return `\nUnderlying nargo compile options:\n\n${output}`;
} catch {
return '\n(Run "nargo compile --help" to see available nargo options)';
}
})
.action((nargoArgs: string[]) => compileAztecContract(nargoArgs, log));

return program;
}
3 changes: 3 additions & 0 deletions yarn-project/aztec/test/mixed-workspace/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target/
codegen-output/
codegenCache.json
2 changes: 2 additions & 0 deletions yarn-project/aztec/test/mixed-workspace/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[workspace]
members = ["simple_contract", "simple_circuit"]
26 changes: 26 additions & 0 deletions yarn-project/aztec/test/mixed-workspace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Mixed Workspace Test

Regression test for `aztec compile` and `aztec codegen` in Nargo workspaces that
contain both Aztec contracts and plain Noir circuits.

## Problem

Both `aztec compile` and `aztec codegen` assumed every `.json` in `target/` is a
contract artifact. When a workspace also contains `type = "bin"` packages, the
resulting program artifacts lack `functions`/`name` fields, causing:

- `bb aztec_process` to fail trying to transpile a program artifact
- The jq postprocessing step to fail on missing `.functions`
- `codegen` to crash calling `loadContractArtifact()` on a program artifact

## What the test checks

`yarn-project/aztec/src/cli/cmds/compile.test.ts` runs compile and codegen on
this workspace and verifies:

1. Compilation succeeds without errors
2. Both artifacts exist in `target/`
3. The contract artifact was postprocessed (has `transpiled` field)
4. The program artifact was not modified (no `transpiled` field)
5. Codegen generates a TypeScript wrapper only for the contract
6. No TypeScript wrapper is generated for the program artifact
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "simple_circuit"
authors = [""]
compiler_version = ">=0.25.0"
type = "bin"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main(x: Field) {
assert(x != 0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "simple_contract"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { path = "../../../../../noir-projects/aztec-nr/aztec" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use aztec::macros::aztec;

#[aztec]
pub contract SimpleContract {
use aztec::macros::functions::external;

#[external("private")]
fn private_function() -> Field {
0
}
}
3 changes: 3 additions & 0 deletions yarn-project/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ function test_cmds {
# Uses mocha for browser tests, so we have to treat it differently.
echo "$hash:ISOLATE=1 cd yarn-project/kv-store && yarn test"

# Aztec CLI tests
aztec/bootstrap.sh test_cmds

if [[ "${TARGET_BRANCH:-}" =~ ^v[0-9]+$ ]]; then
echo "$hash yarn-project/scripts/run_test.sh aztec/src/testnet_compatibility.test.ts"
echo "$hash yarn-project/scripts/run_test.sh aztec/src/mainnet_compatibility.test.ts"
Expand Down
6 changes: 6 additions & 0 deletions yarn-project/builder/src/contract-interface-gen/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ async function generateFromNoirAbi(outputPath: string, noirAbiPath: string, opts

const file = await readFile(noirAbiPath, 'utf8');
const contract = JSON.parse(file);

if (!Array.isArray(contract.functions)) {
console.log(`${fileName} is not a contract artifact. Skipping.`);
return;
}

const aztecAbi = loadContractArtifact(contract);

await mkdir(outputPath, { recursive: true });
Expand Down
Loading