Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add noir_codegen package #3392

Merged
merged 7 commits into from
Nov 2, 2023
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"tooling/noir_js_types",
"tooling/noirc_abi_wasm",
"tooling/noir_js",
"tooling/noir_codegen",
"tooling/noir_js_backend_barretenberg",
"acvm-repo/acvm_js",
"release-tests",
Expand Down
9 changes: 7 additions & 2 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"path": "compiler/wasm/package.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": "tooling/noir_codegen/package.json",
"jsonpath": "$.version"
},
{
"type": "json",
"path": "tooling/noir_js/package.json",
Expand All @@ -45,8 +50,8 @@
"jsonpath": "$.version"
}
]
},
"acvm-repo" : {
},
"acvm-repo": {
"release-type": "simple",
"package-name": "acvm",
"component": "acvm",
Expand Down
2 changes: 2 additions & 0 deletions tooling/noir_codegen/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
3 changes: 3 additions & 0 deletions tooling/noir_codegen/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["../../.eslintrc.js"],
};
4 changes: 4 additions & 0 deletions tooling/noir_codegen/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
crs
lib

!test/*/target
11 changes: 11 additions & 0 deletions tooling/noir_codegen/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"require": "ts-node/register",
"loader": "ts-node/esm",
"extensions": [
"ts",
"cjs"
],
"spec": [
"test/**/*.test.ts*"
]
}
4 changes: 4 additions & 0 deletions tooling/noir_codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

## Acknowledgements

`noir-codegen` repurposes the CLI code from https://github.com/dethcrypto/TypeChain, used under the MIT license.
53 changes: 53 additions & 0 deletions tooling/noir_codegen/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@noir-lang/noir_codegen",
"collaborators": [
"The Noir Team <[email protected]>"
],
"version": "0.18.0",
"packageManager": "[email protected]",
"license": "(MIT OR Apache-2.0)",
"type": "module",
"dependencies": {
"@noir-lang/types": "workspace:*",
"glob": "^10.3.10",
"lodash": "^4.17.21",
"ts-command-line-args": "^2.5.1"
},
"files": [
"lib",
"package.json"
],
"source": "src/index.ts",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"bin": {
"noir-codegen": "lib/main.js"
},
"scripts": {
"dev": "tsc-multi --watch",
"build": "tsc",
"test": "ts-node --esm src/main.ts ./test/assert_lt/target/** --out-dir ./test/codegen && yarn test:node && rm -rf ./test/codegen",
"test:node": "mocha --timeout 25000 --exit --config ./.mocharc.json",
"prettier": "prettier 'src/**/*.ts'",
"prettier:fix": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
"lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0",
"nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json",
"publish": "echo 📡 publishing `$npm_package_name` && yarn npm publish",
"clean": "rm -rf ./lib"
},
"devDependencies": {
"@noir-lang/noir_js": "workspace:*",
"@types/chai": "^4",
"@types/lodash": "^4",
"@types/mocha": "^10.0.1",
"@types/node": "^20.6.2",
"@types/prettier": "^3",
"chai": "^4.3.8",
"eslint": "^8.50.0",
"eslint-plugin-prettier": "^5.0.0",
"mocha": "^10.2.0",
"prettier": "3.0.3",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
28 changes: 28 additions & 0 deletions tooling/noir_codegen/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { CompiledCircuit } from '@noir-lang/types';

const codegenImports = `import { InputMap, InputValue } from "@noir-lang/noirc_abi"
import { Noir } from "@noir-lang/noir_js"`;

const codegenFunction = (
name: string,
compiled_program: CompiledCircuit,
) => `export async function ${name}(args: InputMap): Promise<InputValue> {
const program = new Noir(${JSON.stringify(compiled_program)});
const { returnValue } = await program.execute(args);
return returnValue;
}`;

export const codegen = (programs: [string, CompiledCircuit][]): string => {
const results = [codegenImports];
for (const [name, program] of programs) {
results.push(codegenFunction(name, stripUnwantedFields(program)));
}

return results.join('\n\n');
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
function stripUnwantedFields(value: any): CompiledCircuit {
const { abi, bytecode } = value;
return { abi, bytecode };
}
47 changes: 47 additions & 0 deletions tooling/noir_codegen/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#! /usr/bin/env node

import { CompiledCircuit } from '@noir-lang/types';
import fs from 'fs';
import path from 'path';
import { parseArgs } from './parseArgs.js';
import { glob } from './utils/glob.js';
import { codegen } from './index.js';

function main() {
const cliConfig = parseArgs();
const cwd = process.cwd();

const files = getFilesToProcess(cwd, cliConfig.files);
if (files.length === 0) {
throw new Error('No files passed.' + '\n' + `\`${cliConfig.files}\` didn't match any input files in ${cwd}`);
}

const programs = files.map((file_path): [string, CompiledCircuit] => {
const program_name = path.parse(file_path).name;
const file_contents = fs.readFileSync(file_path).toString();
const { abi, bytecode } = JSON.parse(file_contents);

return [program_name, { abi, bytecode }];
});

const result = codegen(programs);

const outputDir = path.resolve(cliConfig.outDir ?? './codegen');
const outputFile = path.join(outputDir, 'index.ts');
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
fs.writeFileSync(outputFile, result);
}

function getFilesToProcess(cwd: string, filesOrPattern: string[]) {
let res = glob(cwd, filesOrPattern);

if (res.length === 0) {
// If there are no files found, but first parameter is surrounded with single quotes, we try again without quotes
const match = filesOrPattern[0].match(/'([\s\S]*)'/)?.[1];
if (match) res = glob(cwd, [match]);
}

return res;
}

main();
64 changes: 64 additions & 0 deletions tooling/noir_codegen/src/parseArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { parse as commandLineArgs } from 'ts-command-line-args';

const DEFAULT_GLOB_PATTERN = './target/**/*.json';

export interface ParsedArgs {
files: string[];
outDir?: string | undefined;
inputDir?: string | undefined;
}

export function parseArgs(): ParsedArgs {
const rawOptions = commandLineArgs<CommandLineArgs>(
{
glob: {
type: String,
defaultOption: true,
multiple: true,
defaultValue: [DEFAULT_GLOB_PATTERN],
description:
'Pattern that will be used to find program artifacts. Remember about adding quotes: noir-codegen "**/*.json".',
},
'out-dir': { type: String, optional: true, description: 'Output directory for generated files.' },
'input-dir': {
type: String,
optional: true,
description:
'Directory containing program artifact files. Inferred as lowest common path of all files if not specified.',
},
help: { type: Boolean, defaultValue: false, alias: 'h', description: 'Prints this message.' },
},
{
helpArg: 'help',
headerContentSections: [
{
content: `\
noir-codegen generates TypeScript wrappers for Noir programs to simplify replicating your Noir logic in JS.`,
},
],
footerContentSections: [
{
header: 'Example Usage',
content: `\
noir-codegen --out-dir app/noir_programs './target/*.json'


You can read more about noir-codegen at {underline https://github.com/noir-lang/noir}.`,
},
],
},
);

return {
files: rawOptions.glob,
outDir: rawOptions['out-dir'],
inputDir: rawOptions['input-dir'],
};
}

interface CommandLineArgs {
glob: string[];
'out-dir'?: string;
'input-dir'?: string;
help: boolean;
}
9 changes: 9 additions & 0 deletions tooling/noir_codegen/src/utils/glob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { sync as globSync } from 'glob';
import _ from 'lodash';
const { flatten, uniq } = _;

export function glob(cwd: string, patternsOrFiles: string[]): string[] {
const matches = patternsOrFiles.map((p) => globSync(p, { ignore: 'node_modules/**', absolute: true, cwd }));

return uniq(flatten(matches));
}
5 changes: 5 additions & 0 deletions tooling/noir_codegen/test/assert_lt/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "assert_lt"
type = "bin"
authors = [""]
[dependencies]
4 changes: 4 additions & 0 deletions tooling/noir_codegen/test/assert_lt/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main(x : u64, y : pub u64) -> pub u64 {
assert(x < y);
x + y
}
1 change: 1 addition & 0 deletions tooling/noir_codegen/test/assert_lt/target/assert_lt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"hash":13834844072603749544,"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"private"},{"name":"y","type":{"kind":"integer","sign":"unsigned","width":64},"visibility":"public"}],"param_witnesses":{"x":[1],"y":[2]},"return_type":{"kind":"integer","sign":"unsigned","width":64},"return_witnesses":[12]},"bytecode":"H4sIAAAAAAAA/+1WUW6DMAx1QksZoGr72jUcAiX8VbvJ0Oj9j7ChJpKbtXw0NpvUWkImUXixn53w3gDgHc6mfh7t/ZGMtR9TU96HeYuHtp36ZjLWfGIzjK7DthsPzjjTue6rcdZOrnX9MA49Dqa1kzl1gz3h2bL7sTDCMhmJbylmTDOT8WEhjXfjH/DcB8u8zwVygWifmL/9lTnWzSWKsxHA3QJf00vlveWvERJIUU4x0eb86aEJppljVox9oO+Py8QTV1Jnw6a85t7vSL8pwvN89j7gd88o8q79Gr2wRt3AeSFz4XvRSyokl5MAtSfgGO2ZCewdsDibLRVrDzIXTMxfqiLIGXPeMdY1gb/Fg8+tznJY50eSGmfB2DNrqciCD+tCRc4X5FNFJmIWnkhu3BL+t4qc8y75aySqIkvGOP9CRWKaGQ0ydUrsgUUVWXlfw4OpyAouVWQN66pITDPDqSJfQaZxuVVkxZhzzVgLTv5uHbDwXhN+vwGywklHPBQAAA=="}
11 changes: 11 additions & 0 deletions tooling/noir_codegen/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect } from 'chai';
import { assert_lt } from './codegen/index.js';

it('codegens a callable function', async () => {
const result = await assert_lt({
x: '2',
y: '3',
});

expect(result).to.be.eq('0x05');
});
20 changes: 20 additions & 0 deletions tooling/noir_codegen/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "esnext",
"declaration": true,
"emitDeclarationOnly": false,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./lib",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"noImplicitAny": false,
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading
Loading