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
12 changes: 12 additions & 0 deletions docs/docs/dev_docs/contracts/compiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ To generate them, include a `--typescript` option in the compile command with a
aztec-cli compile --typescript ./path/to/typescript/src ./path/to/my_aztec_contract_project
```

You can also generate these interfaces from prebuilt artifacts using the `generate-typescript` command:

```
aztec-cli generate-typescript ./path/to/my_aztec_contract_project
```

Example code generated from the [PrivateToken](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr) contract:

```ts showLineNumbers
Expand Down Expand Up @@ -82,6 +88,12 @@ To generate them, include a `--interface` option in the compile command with a p
aztec-cli compile --interface ./path/to/another_aztec_contract_project/src ./path/to/my_aztec_contract_project
```

You can also generate these interfaces from prebuilt artifacts using the `generate-noir-interface` command:

```
aztec-cli generate-noir-interface ./path/to/my_aztec_contract_project
```

Example code generated from the [PrivateToken](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/private_token_contract/src/main.nr) contract:

```rust
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { StructType } from '@aztec/foundation/abi';
import { JsonStringify } from '@aztec/foundation/json-rpc';
import { DebugLogger, LogFn } from '@aztec/foundation/log';
import { fileURLToPath } from '@aztec/foundation/url';
import { compileContract } from '@aztec/noir-compiler/cli';
import { compileContract, generateNoirInterface, generateTypescriptInterface } from '@aztec/noir-compiler/cli';
import { CompleteAddress, ContractData, L2BlockL2Logs, TxHash } from '@aztec/types';

import { Command } from 'commander';
Expand Down Expand Up @@ -486,6 +486,8 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
});

compileContract(program, 'compile', log);
generateTypescriptInterface(program, 'generate-typescript', log);
generateNoirInterface(program, 'generate-noir-interface', log);

return program;
}
8 changes: 6 additions & 2 deletions yarn-project/noir-compiler/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { createConsoleLogger } from '@aztec/foundation/log';
import { Command } from 'commander';

import { compileContract } from './cli/contract.js';
import { generateNoirInterface } from './cli/noir-interface.js';
import { generateTypescriptInterface } from './cli/typescript.js';

const program = new Command();
const log = createConsoleLogger('aztec:compiler-cli');

const main = async () => {
compileContract(program.name('aztec-compile'), 'contract', log);

program.name('aztec-compile');
compileContract(program, 'contract', log);
generateTypescriptInterface(program, 'typescript', log);
generateNoirInterface(program, 'interface', log);
await program.parseAsync(process.argv);
};

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/noir-compiler/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { compileContract } from './contract.js';
export { generateNoirInterface } from './noir-interface.js';
export { generateTypescriptInterface } from './typescript.js';
62 changes: 62 additions & 0 deletions yarn-project/noir-compiler/src/cli/noir-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { LogFn } from '@aztec/foundation/log';

import { Command } from 'commander';
import { readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
import { mkdirpSync } from 'fs-extra';
import path, { resolve } from 'path';

import { generateNoirContractInterface } from '../index.js';
import { isContractAbi } from '../utils.js';

/**
* Registers a 'interface' command on the given commander program that generates a Noir interface out of an ABI.
* @param program - Commander program.
* @param log - Optional logging function.
* @returns The program with the command registered.
*/
export function generateNoirInterface(program: Command, name = 'interface', log: LogFn = () => {}): Command {
return program
.command(name)
.argument('<project-path>', 'Path to the noir project')
.option('--artifacts <path>', 'Folder containing the compiled artifacts, relative to the project path', 'target')
.option(
'-o, --outdir <path>',
'Output folder for the generated noir interfaces, relative to the project path',
'interfaces',
)
.description('Generates Noir interfaces from the artifacts in the given project')

.action(
(
projectPath: string,
/* eslint-disable jsdoc/require-jsdoc */
options: {
outdir: string;
artifacts: string;
},
/* eslint-enable jsdoc/require-jsdoc */
) => {
const { outdir, artifacts } = options;
if (typeof projectPath !== 'string') throw new Error(`Missing project path argument`);
const currentDir = process.cwd();

const artifactsDir = resolve(projectPath, artifacts);
for (const artifactsDirItem of readdirSync(artifactsDir)) {
const artifactPath = resolve(artifactsDir, artifactsDirItem);
if (statSync(artifactPath).isFile() && artifactPath.endsWith('.json')) {
const contract = JSON.parse(readFileSync(artifactPath).toString());
if (!isContractAbi(contract)) continue;
const interfacePath = resolve(projectPath, outdir, `${contract.name}_interface.nr`);
log(`Writing ${contract.name} Noir external interface to ${path.relative(currentDir, interfacePath)}`);
try {
const noirInterface = generateNoirContractInterface(contract);
mkdirpSync(path.dirname(interfacePath));
writeFileSync(interfacePath, noirInterface);
} catch (err) {
log(`Error generating interface for ${artifactPath}: ${err}`);
}
}
}
},
);
}
63 changes: 63 additions & 0 deletions yarn-project/noir-compiler/src/cli/typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { LogFn } from '@aztec/foundation/log';

import { Command } from 'commander';
import { readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
import { mkdirpSync } from 'fs-extra';
import path, { resolve } from 'path';

import { generateTypescriptContractInterface } from '../index.js';
import { isContractAbi } from '../utils.js';

/**
* Registers a 'typescript' command on the given commander program that generates typescript interface out of an ABI.
* @param program - Commander program.
* @param log - Optional logging function.
* @returns The program with the command registered.
*/
export function generateTypescriptInterface(program: Command, name = 'typescript', log: LogFn = () => {}): Command {
return program
.command(name)
.argument('<project-path>', 'Path to the noir project')
.option('--artifacts <path>', 'Folder containing the compiled artifacts, relative to the project path', 'target')
.option(
'-o, --outdir <path>',
'Output folder for the generated typescript wrappers, relative to the project path',
'types',
)
.description('Generates typescript interfaces from the artifacts in the given project')

.action(
(
projectPath: string,
/* eslint-disable jsdoc/require-jsdoc */
options: {
outdir: string;
artifacts: string;
},
/* eslint-enable jsdoc/require-jsdoc */
) => {
const { outdir, artifacts } = options;
if (typeof projectPath !== 'string') throw new Error(`Missing project path argument`);
const currentDir = process.cwd();

const artifactsDir = resolve(projectPath, artifacts);
for (const artifactsDirItem of readdirSync(artifactsDir)) {
const artifactPath = resolve(artifactsDir, artifactsDirItem);
if (statSync(artifactPath).isFile() && artifactPath.endsWith('.json')) {
const contract = JSON.parse(readFileSync(artifactPath).toString());
if (!isContractAbi(contract)) continue;
const tsPath = resolve(projectPath, outdir, `${contract.name}.ts`);
log(`Writing ${contract.name} typescript interface to ${path.relative(currentDir, tsPath)}`);
const relativeArtifactPath = path.relative(path.dirname(tsPath), artifactPath);
try {
const tsWrapper = generateTypescriptContractInterface(contract, relativeArtifactPath);
mkdirpSync(path.dirname(tsPath));
writeFileSync(tsPath, tsWrapper);
} catch (err) {
log(`Error generating interface for ${artifactPath}: ${err}`);
}
}
}
},
);
}
21 changes: 21 additions & 0 deletions yarn-project/noir-compiler/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ContractAbi } from '@aztec/foundation/abi';

/**
* Checks if the given input looks like a valid ContractAbi. The check is not exhaustive,
* and it's just meant to differentiate between nargo raw build artifacts and the ones
* produced by this compiler.
* @param input - Input object.
* @returns True if it looks like a ContractAbi.
*/
export function isContractAbi(input: any): input is ContractAbi {
if (typeof input !== 'object') return false;
const maybeContractAbi = input as ContractAbi;
if (typeof maybeContractAbi.name !== 'string') return false;
if (!Array.isArray(maybeContractAbi.functions)) return false;
for (const fn of maybeContractAbi.functions) {
if (typeof fn.name !== 'string') return false;
if (typeof fn.functionType !== 'string') return false;
if (typeof fn.isInternal !== 'boolean') return false;
}
return true;
}