From 37afc3876d90381ce8e221ac6c15e9c70cfba91b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 17 Jan 2024 11:36:30 -0300 Subject: [PATCH] feat!: Unify ABIs between nargo and yarn-project (#3989) Changes the output of `nargo` and `noir-wasm` to generate a single artifact that includes debug information. Compiled contracts now have a `file_map` at the root of the artifact, and a `debug_symbols` within each function. For consistency, compiled programs also have `file_map` and `debug_symbols`. Debug symbols are compressed and base64-encoded, as they were by the former `noir-compiler` postprocessing. The `debug` json artifact is no longer emitted. Removes all code related to generating `yarn-project`-specific ABIs from noir compiler. Instead, `types` now exposes a `loadContractArtifact` that renames fields as needed (and use camel instead of snake case), and is required when loading a noir contract artifact. Autogenerated types run this automatically. Since we are no longer post-processing artifacts, the `noir-contracts` project shape is changed. JSON files live in the `target` folder where nargo outputs them (this is not configurable), and are loaded by the autogenerated typescript interfaces from there. As part of the refactor, moves functions for getting functions debug info out of `acir-simulator` and into `types`. **Breaking change**: This removes the need to run `codegen` for using a compiled contract. However, when creating a new contract object from aztec.js, a dev needs to call `loadContractArtifact`. **Future changes**: Type information for a compilation artifact is now spread all over the place, and duplicated in more than one place. There are types defined within rust code in Noir as `[wasm_bindgen(typescript_custom_section)]`, others defined within typescript code in the `noir_wasm` package, others in `foundation`, and others in `types`. We should unify it in a single place. Fixes https://github.com/AztecProtocol/aztec-packages/issues/3812 Supersedes #3906 Noir subrepo has been pushed to https://github.com/noir-lang/noir/pull/4035 to run the Noir CI --- boxes/blank-react/tsconfig.json | 2 +- boxes/blank/tsconfig.json | 2 +- boxes/token/tsconfig.json | 2 +- docs/docs/dev_docs/contracts/compiling.md | 16 +- docs/docs/dev_docs/contracts/deploying.md | 2 +- .../aztecnr-getting-started.md | 2 +- .../tutorials/uniswap/typescript_glue_code.md | 2 +- .../writing_dapp/contract_deployment.md | 11 +- .../writing_dapp/contract_interaction.md | 2 +- .../writing_private_voting_contract.md | 10 +- .../tutorials/writing_token_contract.md | 2 +- noir/Cargo.lock | 8 +- noir/Cargo.toml | 1 + noir/acvm-repo/acir/Cargo.toml | 2 +- noir/compiler/noirc_errors/Cargo.toml | 5 +- noir/compiler/noirc_errors/src/debug_info.rs | 48 ++++- noir/compiler/wasm/Cargo.toml | 1 + noir/compiler/wasm/package.json | 5 + noir/compiler/wasm/src/compile.rs | 68 +++---- noir/compiler/wasm/src/index.cts | 3 +- noir/compiler/wasm/src/index.mts | 3 +- noir/compiler/wasm/src/noir/debug.ts | 6 + noir/compiler/wasm/src/types/noir_artifact.ts | 53 ++--- .../browser/compile_with_deps.test.ts | 41 ++-- .../compiler/node/compile_with_deps.test.ts | 22 +- .../compiler/shared/compile_with_deps.test.ts | 80 ++++++++ noir/tooling/nargo/src/artifacts/contract.rs | 29 ++- noir/tooling/nargo/src/artifacts/program.rs | 30 +++ noir/tooling/nargo_cli/src/cli/compile_cmd.rs | 81 +------- noir/tooling/nargo_cli/src/cli/fs/program.rs | 24 +-- noir/yarn.lock | 16 ++ yarn-project/accounts/package.json | 2 +- yarn-project/accounts/package.local.json | 2 +- .../accounts/scripts/copy-contracts.sh | 11 + yarn-project/accounts/src/ecdsa/artifact.ts | 4 +- yarn-project/accounts/src/schnorr/artifact.ts | 4 +- .../accounts/src/single_key/artifact.ts | 6 +- .../acir-simulator/src/client/db_oracle.ts | 12 +- .../src/client/private_execution.test.ts | 11 +- .../src/client/private_execution.ts | 3 +- .../src/client/simulator.test.ts | 5 +- .../acir-simulator/src/client/simulator.ts | 10 +- .../src/client/unconstrained_execution.ts | 3 +- yarn-project/acir-simulator/src/test/utils.ts | 31 --- yarn-project/aztec.js/package.json | 3 +- yarn-project/aztec.js/package.local.json | 2 +- yarn-project/aztec.js/src/api/abi.ts | 2 + .../aztec.js/src/contract/contract.test.ts | 4 + .../contract_deployer.test.ts | 2 + yarn-project/aztec.js/src/index.ts | 4 +- .../circuit-types/src/contract_dao.test.ts | 2 + .../circuit-types/src/contract_dao.ts | 9 +- yarn-project/circuit-types/src/mocks.ts | 1 + yarn-project/cli/src/cmds/get_accounts.ts | 24 ++- yarn-project/cli/src/index.ts | 3 +- yarn-project/cli/src/test/mocks.ts | 3 + .../end-to-end/src/guides/up_quick_start.sh | 7 +- .../end-to-end/src/sample-dapp/contracts.mjs | 6 +- .../end-to-end/src/sample-dapp/deploy.mjs | 5 +- yarn-project/end-to-end/tsconfig.web.json | 2 +- yarn-project/foundation/src/abi/abi.ts | 81 +++++--- yarn-project/foundation/src/abi/selector.ts | 15 +- .../src/json-rpc/client/json_rpc_client.ts | 2 +- yarn-project/noir-compiler/package.json | 1 + .../add_noir_compiler_commander_actions.ts | 5 +- yarn-project/noir-compiler/src/cli/codegen.ts | 47 ++--- .../src/contract-interface-gen/abi.ts | 120 ----------- .../programTypescript.ts | 189 ------------------ .../{contractTypescript.ts => typescript.ts} | 4 +- yarn-project/noir-compiler/src/index.ts | 6 +- yarn-project/noir-compiler/tsconfig.json | 3 + yarn-project/noir-contracts/bootstrap.sh | 3 - yarn-project/noir-contracts/package.json | 5 +- .../noir-contracts/scripts/generate-types.sh | 6 +- .../scripts/transform_json_abi.sh | 22 -- yarn-project/noir-contracts/tsconfig.json | 8 +- .../noir-protocol-circuits/package.json | 5 +- .../noir-protocol-circuits/src/index.ts | 2 +- .../src/scripts/generate_ts_from_abi.ts | 2 +- .../noir-protocol-circuits/tsconfig.json | 3 + .../pxe/src/simulator_oracle/index.ts | 3 +- yarn-project/types/.eslintrc.cjs | 2 +- yarn-project/types/package.json | 4 +- .../types/src/abi/contract_artifact.ts | 130 ++++++++++++ yarn-project/types/src/abi/index.ts | 1 + yarn-project/types/src/abi/mocked_keys.ts | 2 + .../src/noir/index.ts} | 64 ++---- yarn-project/yarn.lock | 4 + 88 files changed, 706 insertions(+), 795 deletions(-) create mode 100644 noir/compiler/wasm/src/noir/debug.ts create mode 100644 noir/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts create mode 100755 yarn-project/accounts/scripts/copy-contracts.sh delete mode 100644 yarn-project/noir-compiler/src/contract-interface-gen/abi.ts delete mode 100644 yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts rename yarn-project/noir-compiler/src/contract-interface-gen/{contractTypescript.ts => typescript.ts} (97%) delete mode 100755 yarn-project/noir-contracts/bootstrap.sh delete mode 100755 yarn-project/noir-contracts/scripts/transform_json_abi.sh create mode 100644 yarn-project/types/src/abi/contract_artifact.ts create mode 100644 yarn-project/types/src/abi/index.ts create mode 100644 yarn-project/types/src/abi/mocked_keys.ts rename yarn-project/{noir-compiler/src/noir_artifact.ts => types/src/noir/index.ts} (77%) diff --git a/boxes/blank-react/tsconfig.json b/boxes/blank-react/tsconfig.json index 25932cb04b5..acc585a3b0e 100644 --- a/boxes/blank-react/tsconfig.json +++ b/boxes/blank-react/tsconfig.json @@ -21,5 +21,5 @@ "skipLibCheck": true, "jsx": "react-jsx" }, - "include": ["src", "src/artifacts/Blank.json"] + "include": ["src", "src/contracts/target/*.json"] } diff --git a/boxes/blank/tsconfig.json b/boxes/blank/tsconfig.json index 25932cb04b5..acc585a3b0e 100644 --- a/boxes/blank/tsconfig.json +++ b/boxes/blank/tsconfig.json @@ -21,5 +21,5 @@ "skipLibCheck": true, "jsx": "react-jsx" }, - "include": ["src", "src/artifacts/Blank.json"] + "include": ["src", "src/contracts/target/*.json"] } diff --git a/boxes/token/tsconfig.json b/boxes/token/tsconfig.json index 0c77d2196f9..acc585a3b0e 100644 --- a/boxes/token/tsconfig.json +++ b/boxes/token/tsconfig.json @@ -21,5 +21,5 @@ "skipLibCheck": true, "jsx": "react-jsx" }, - "include": ["src", "src/artifacts/Token.json"] + "include": ["src", "src/contracts/target/*.json"] } diff --git a/docs/docs/dev_docs/contracts/compiling.md b/docs/docs/dev_docs/contracts/compiling.md index a44bd6e32cd..8d19f7811a8 100644 --- a/docs/docs/dev_docs/contracts/compiling.md +++ b/docs/docs/dev_docs/contracts/compiling.md @@ -18,23 +18,11 @@ aztec-nargo compile This will output a JSON [artifact](./artifacts.md) for each contract in the project to a `target` folder containing the Noir ABI artifacts. -Before you can use the ABI it currently needs to be validated as being Aztec compatible, and transformed to an Aztec compatible ABI using `aztec-cli codegen`, passing a Noir ABI file or folder, and output location, e.g: - -```bash -aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts -``` - -It can be useful to perform this compilation, validation and transformation in one go, so you may wish to chain the commands and perhaps add them to a package.json script. The below assumes your contract is in a folder called `contract` at your project root: - -```bash -(cd contract && aztec-nargo compile && aztec-cli codegen target -o ../src/artifacts) -``` - ### Typescript Interfaces -You can use the codegenerator to autogenerate type-safe typescript classes for each of your contracts. These classes define type-safe methods for deploying and interacting with your contract based on their artifact. +You can use the code generator to autogenerate type-safe typescript classes for each of your contracts. These classes define type-safe methods for deploying and interacting with your contract based on their artifact. -To generate them, include a `--ts` option in the codegen command with a path to the target folder for the typescript files: +To generate them, include a `--ts` option in the `codegen` command with a path to the target folder for the typescript files: ```bash aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts --ts diff --git a/docs/docs/dev_docs/contracts/deploying.md b/docs/docs/dev_docs/contracts/deploying.md index 7e1f9a6d012..c2db9fc764a 100644 --- a/docs/docs/dev_docs/contracts/deploying.md +++ b/docs/docs/dev_docs/contracts/deploying.md @@ -33,7 +33,7 @@ Compile the contract: aztec-nargo compile ``` -Generate the ABI and typescript class: +Generate the typescript class: ```bash aztec-cli codegen ./aztec-nargo/output/target/path -o src/artifacts --ts diff --git a/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md b/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md index fe1b59cd6c8..954d60ff2b1 100644 --- a/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md +++ b/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md @@ -156,7 +156,7 @@ aztec-nargo compile This will compile the smart contract and create a `target` folder with a `.json` artifact inside. -After compiling, you need to generate the ABI and typescript class. In the same directory, run this: +After compiling, you can generate a typescript class. In the same directory, run this: ```bash aztec-cli codegen target -o src/artifacts --ts diff --git a/docs/docs/dev_docs/tutorials/uniswap/typescript_glue_code.md b/docs/docs/dev_docs/tutorials/uniswap/typescript_glue_code.md index e4004c9b02d..3bc026658e5 100644 --- a/docs/docs/dev_docs/tutorials/uniswap/typescript_glue_code.md +++ b/docs/docs/dev_docs/tutorials/uniswap/typescript_glue_code.md @@ -33,7 +33,7 @@ and the each of the Aztec.nr contracts by going into each folder and running: aztec-nargo compile ``` -And then generate the ABIs and typescript interface: +And then generate the typescript interface: ```bash aztec-cli codegen ./target/ -o ../../../src/test/fixtures uniswap --ts diff --git a/docs/docs/dev_docs/tutorials/writing_dapp/contract_deployment.md b/docs/docs/dev_docs/tutorials/writing_dapp/contract_deployment.md index d15d12279be..7459287af51 100644 --- a/docs/docs/dev_docs/tutorials/writing_dapp/contract_deployment.md +++ b/docs/docs/dev_docs/tutorials/writing_dapp/contract_deployment.md @@ -42,14 +42,6 @@ Now run the following from your contract folder (containing Nargo.toml): aztec-nargo compile ``` -Once you have compiled your contracts, you need to generate the ABIs: - -```sh -aztec-cli codegen ./target/path -o src/artifacts --ts -``` - -This should have created an artifact `contracts/token/src/artifacts/Token.json` with the interface and bytecode for your contract. - ## Deploy your contracts Let's now write a script for deploying your contracts to the Sandbox. We'll create a Private eXecution Environment (PXE) client, and then use the `ContractDeployer` class to deploy our contracts, and store the deployment address to a local JSON file. @@ -61,7 +53,8 @@ Create a new file `src/deploy.mjs`: import { writeFileSync } from 'fs'; import { Contract, ContractDeployer, createPXEClient } from '@aztec/aztec.js'; import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; -import TokenContractArtifact from "../contracts/token/target/Token.json" assert { type: "json" }; +import TokenContractJson from "../contracts/token/target/token_contract-Token.json" assert { type: "json" }; + #include_code dapp-deploy yarn-project/end-to-end/src/sample-dapp/deploy.mjs raw diff --git a/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md b/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md index 0e520cbf687..73c0eef546c 100644 --- a/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md +++ b/docs/docs/dev_docs/tutorials/writing_dapp/contract_interaction.md @@ -18,7 +18,7 @@ To do this, let's first initialize a new `Contract` instance using `aztec.js` th // src/contracts.mjs import { Contract } from "@aztec/aztec.js"; import { readFileSync } from "fs"; -import TokenContractArtifact from "../contracts/token/src/artifacts/Token.json" assert { type: "json" }; +import TokenContractJson from "../contracts/token/target/token_contract-Token.json" assert { type: "json" }; ``` And then add the following code for initializing the `Contract` instances: diff --git a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md index 4683798346e..b5e6f084721 100644 --- a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md @@ -193,18 +193,16 @@ The easiest way to compile the contract is with `aztec-nargo`. Run the following aztec-nargo compile ``` -This will create a new directory called `target` and a JSON artifact inside it. To create the Aztec contract ABI and typescript interface, run: +This will create a new directory called `target` and a JSON artifact inside it. To optionally create a typescript interface, run: ```bash aztec-cli codegen target -o src/artifacts --ts ``` -This will create the ABI called `Voting.json` in `src/artifacts`. If you are getting some errors here you might want to check out the [debugging page](../debugging/main.md). - Once it is compiled you can [deploy](../contracts/deploying.md) it to the sandbox. Ensure your [sandbox is running](../cli/sandbox-reference.md) and run this in the same dir as before: ```bash -aztec-cli deploy ./src/artifacts/Voting.json --args $ADMIN_ADDRESS +aztec-cli deploy ./target/Voting.json --args $ADMIN_ADDRESS ``` The constructor takes an address as an argument to set the admin, so you can use an address that is deployed with the sandbox - check the sandbox terminal or run `aztec-cli get-accounts`. @@ -214,7 +212,7 @@ You should see a success message with the contract address. Now we can start cal Cast a vote like this: ```bash -aztec-cli send cast_vote --contract-artifact ./src/artifacts/Voting.json --contract-address $CONTRACT_ADDRESS --args 1 --private-key $PRIVATE_KEY +aztec-cli send cast_vote --contract-artifact ./target/Voting.json --contract-address $CONTRACT_ADDRESS --args 1 --private-key $PRIVATE_KEY ``` You can get the contract address from the sandbox terminal or the message printed when you deployed the contract. You can also get a private key from the sandbox terminal, or generate one with `aztec-cli generate-private-key`. @@ -226,7 +224,7 @@ You can now try running this command again to ensure our nullifier works. Get the number of votes like this: ```bash -aztec-cli call get_vote --contract-artifact ./src/artifacts/Voting.json --contract-address $CONTRACT_ADDRESS --args 1 +aztec-cli call get_vote --contract-artifact ./target/Voting.json --contract-address $CONTRACT_ADDRESS --args 1 ``` This should return `1n`. diff --git a/docs/docs/dev_docs/tutorials/writing_token_contract.md b/docs/docs/dev_docs/tutorials/writing_token_contract.md index e3380713e8e..f4fda400456 100644 --- a/docs/docs/dev_docs/tutorials/writing_token_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_token_contract.md @@ -467,7 +467,7 @@ Run the following command in the directory where your `Nargo.toml` file is locat aztec-nargo compile ``` -Once your contract is compiled, generate the Aztec contract ABI with typescript interface with the following command: +Once your contract is compiled, optionally generate a typescript interface with the following command: ```bash aztec-cli codegen target -o src/artifacts --ts diff --git a/noir/Cargo.lock b/noir/Cargo.lock index 1088bb63004..79f1934059f 100644 --- a/noir/Cargo.lock +++ b/noir/Cargo.lock @@ -1663,9 +1663,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -2870,6 +2870,7 @@ dependencies = [ "nargo", "noirc_driver", "noirc_errors", + "noirc_evaluator", "noirc_frontend", "rust-embed", "serde", @@ -2937,11 +2938,14 @@ name = "noirc_errors" version = "0.22.0" dependencies = [ "acvm", + "base64 0.21.2", "chumsky", "codespan", "codespan-reporting", + "flate2", "fm", "serde", + "serde_json", "serde_with", "tracing", ] diff --git a/noir/Cargo.toml b/noir/Cargo.toml index 05139934c62..f0fc7249efc 100644 --- a/noir/Cargo.toml +++ b/noir/Cargo.toml @@ -123,6 +123,7 @@ num-traits = "0.2" similar-asserts = "1.5.0" tempfile = "3.6.0" jsonrpc = { version = "0.16.0", features = ["minreq_http"] } +flate2 = "1.0.24" tracing = "0.1.40" tracing-web = "0.1.3" diff --git a/noir/acvm-repo/acir/Cargo.toml b/noir/acvm-repo/acir/Cargo.toml index a0877120a58..b44c64dd838 100644 --- a/noir/acvm-repo/acir/Cargo.toml +++ b/noir/acvm-repo/acir/Cargo.toml @@ -17,7 +17,7 @@ acir_field.workspace = true brillig.workspace = true serde.workspace = true thiserror.workspace = true -flate2 = "1.0.24" +flate2.workspace = true bincode.workspace = true base64.workspace = true diff --git a/noir/compiler/noirc_errors/Cargo.toml b/noir/compiler/noirc_errors/Cargo.toml index 02e97b2c670..935137ba2fc 100644 --- a/noir/compiler/noirc_errors/Cargo.toml +++ b/noir/compiler/noirc_errors/Cargo.toml @@ -15,4 +15,7 @@ fm.workspace = true chumsky.workspace = true serde.workspace = true serde_with = "3.2.0" -tracing.workspace = true \ No newline at end of file +tracing.workspace = true +flate2.workspace = true +serde_json.workspace = true +base64.workspace = true \ No newline at end of file diff --git a/noir/compiler/noirc_errors/src/debug_info.rs b/noir/compiler/noirc_errors/src/debug_info.rs index ee40ced19bf..ffca8fbf2e1 100644 --- a/noir/compiler/noirc_errors/src/debug_info.rs +++ b/noir/compiler/noirc_errors/src/debug_info.rs @@ -1,14 +1,24 @@ use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; +use base64::Engine; +use flate2::read::DeflateDecoder; +use flate2::write::DeflateEncoder; +use flate2::Compression; +use serde::Deserializer; +use serde::Serializer; use serde_with::serde_as; use serde_with::DisplayFromStr; use std::collections::BTreeMap; use std::collections::HashMap; +use std::io::Read; +use std::io::Write; use std::mem; use crate::Location; -use serde::{Deserialize, Serialize}; +use serde::{ + de::Error as DeserializationError, ser::Error as SerializationError, Deserialize, Serialize, +}; #[serde_as] #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -86,4 +96,40 @@ impl DebugInfo { counted_opcodes } + + pub fn serialize_compressed_base64_json( + debug_info: &DebugInfo, + s: S, + ) -> Result + where + S: Serializer, + { + let json_str = serde_json::to_string(debug_info).map_err(S::Error::custom)?; + + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(json_str.as_bytes()).map_err(S::Error::custom)?; + let compressed_data = encoder.finish().map_err(S::Error::custom)?; + + let encoded_b64 = base64::prelude::BASE64_STANDARD.encode(compressed_data); + s.serialize_str(&encoded_b64) + } + + pub fn deserialize_compressed_base64_json<'de, D>( + deserializer: D, + ) -> Result + where + D: Deserializer<'de>, + { + let encoded_b64: String = Deserialize::deserialize(deserializer)?; + + let compressed_data = + base64::prelude::BASE64_STANDARD.decode(encoded_b64).map_err(D::Error::custom)?; + + let mut decoder = DeflateDecoder::new(&compressed_data[..]); + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data).map_err(D::Error::custom)?; + + let json_str = String::from_utf8(decompressed_data).map_err(D::Error::custom)?; + serde_json::from_str(&json_str).map_err(D::Error::custom) + } } diff --git a/noir/compiler/wasm/Cargo.toml b/noir/compiler/wasm/Cargo.toml index 7af26269106..a20efeeed8a 100644 --- a/noir/compiler/wasm/Cargo.toml +++ b/noir/compiler/wasm/Cargo.toml @@ -18,6 +18,7 @@ nargo.workspace = true noirc_driver.workspace = true noirc_frontend.workspace = true noirc_errors.workspace = true +noirc_evaluator.workspace = true wasm-bindgen.workspace = true serde.workspace = true js-sys.workspace = true diff --git a/noir/compiler/wasm/package.json b/noir/compiler/wasm/package.json index a8abf26a7e2..412e9c82f9a 100644 --- a/noir/compiler/wasm/package.json +++ b/noir/compiler/wasm/package.json @@ -34,6 +34,7 @@ "test": "yarn test:build_fixtures && yarn test:node && yarn test:browser", "test:build_fixtures": "./scripts/build-fixtures.sh", "test:browser": "web-test-runner", + "test:browser:docker": "docker run --rm -v $(cd ../.. && pwd):/usr/src/noir -w /usr/src/noir/compiler/wasm mcr.microsoft.com/playwright:v1.40.0-jammy yarn test:browser", "test:node": "NODE_NO_WARNINGS=1 mocha --config ./.mocharc.json", "clean": "rm -rf ./build ./target ./dist public/fixtures/simple/target public/fixtures/with-deps/target", "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", @@ -49,6 +50,7 @@ "@types/mocha": "^10.0.6", "@types/mocha-each": "^2", "@types/node": "^20.10.5", + "@types/pako": "^2", "@types/path-browserify": "^1", "@types/readable-stream": "^4", "@types/sinon": "^17", @@ -76,5 +78,8 @@ "url": "^0.11.3", "webpack": "^5.49.0", "webpack-cli": "^4.7.2" + }, + "dependencies": { + "pako": "^2.1.0" } } diff --git a/noir/compiler/wasm/src/compile.rs b/noir/compiler/wasm/src/compile.rs index 151dde2eea6..351f9ae8a86 100644 --- a/noir/compiler/wasm/src/compile.rs +++ b/noir/compiler/wasm/src/compile.rs @@ -3,7 +3,6 @@ use gloo_utils::format::JsValueSerdeExt; use js_sys::{JsString, Object}; use nargo::artifacts::{ contract::{ContractArtifact, ContractFunctionArtifact}, - debug::DebugArtifact, program::ProgramArtifact, }; use noirc_driver::{ @@ -11,6 +10,7 @@ use noirc_driver::{ prepare_dependency, CompileOptions, CompiledContract, CompiledProgram, NOIR_ARTIFACT_VERSION_STRING, }; +use noirc_evaluator::errors::SsaReport; use noirc_frontend::{ graph::{CrateId, CrateName}, hir::Context, @@ -28,35 +28,30 @@ export type DependencyGraph = { library_dependencies: Readonly>; } -export type CompiledContract = { +export type ContractArtifact = { noir_version: string; name: string; functions: Array; events: Array; + file_map: Record; }; -export type CompiledProgram = { +export type ProgramArtifact = { noir_version: string; + hash: number; abi: any; bytecode: string; + debug_symbols: any; + file_map: Record; } -export type DebugArtifact = { - debug_symbols: Array; - file_map: Record; - warnings: Array; -}; +type WarningsCompileResult = { warnings: Array; }; -export type CompileResult = ( - | { - contract: CompiledContract; - debug: DebugArtifact; - } - | { - program: CompiledProgram; - debug: DebugArtifact; - } -); +export type ContractCompileResult = { contract: CompiledContract; } & WarningsCompileResult; + +export type ProgramCompileResult = { program: CompiledProgram; } & WarningsCompileResult; + +export type CompileResult = ContractCompileResult | ProgramCompileResult; "#; #[wasm_bindgen] @@ -76,38 +71,36 @@ extern "C" { impl JsCompileResult { const CONTRACT_PROP: &'static str = "contract"; const PROGRAM_PROP: &'static str = "program"; - const DEBUG_PROP: &'static str = "debug"; + const WARNINGS_PROP: &'static str = "warnings"; pub fn new(resp: CompileResult) -> JsCompileResult { let obj = JsCompileResult::constructor(); match resp { - CompileResult::Contract { contract, debug } => { + CompileResult::Contract { contract, warnings } => { js_sys::Reflect::set( &obj, &JsString::from(JsCompileResult::CONTRACT_PROP), &::from_serde(&contract).unwrap(), ) .unwrap(); - js_sys::Reflect::set( &obj, - &JsString::from(JsCompileResult::DEBUG_PROP), - &::from_serde(&debug).unwrap(), + &JsString::from(JsCompileResult::WARNINGS_PROP), + &::from_serde(&warnings).unwrap(), ) .unwrap(); } - CompileResult::Program { program, debug } => { + CompileResult::Program { program, warnings } => { js_sys::Reflect::set( &obj, &JsString::from(JsCompileResult::PROGRAM_PROP), &::from_serde(&program).unwrap(), ) .unwrap(); - js_sys::Reflect::set( &obj, - &JsString::from(JsCompileResult::DEBUG_PROP), - &::from_serde(&debug).unwrap(), + &JsString::from(JsCompileResult::WARNINGS_PROP), + &::from_serde(&warnings).unwrap(), ) .unwrap(); } @@ -148,8 +141,8 @@ impl PathToFileSourceMap { } pub enum CompileResult { - Contract { contract: ContractArtifact, debug: DebugArtifact }, - Program { program: ProgramArtifact, debug: DebugArtifact }, + Contract { contract: ContractArtifact, warnings: Vec }, + Program { program: ProgramArtifact, warnings: Vec }, } #[wasm_bindgen] @@ -273,21 +266,11 @@ fn add_noir_lib(context: &mut Context, library_name: &CrateName) -> CrateId { } pub(crate) fn generate_program_artifact(program: CompiledProgram) -> CompileResult { - let debug_artifact = DebugArtifact { - debug_symbols: vec![program.debug.clone()], - file_map: program.file_map.clone(), - warnings: program.warnings.clone(), - }; - - CompileResult::Program { program: program.into(), debug: debug_artifact } + let warnings = program.warnings.clone(); + CompileResult::Program { program: program.into(), warnings } } pub(crate) fn generate_contract_artifact(contract: CompiledContract) -> CompileResult { - let debug_artifact = DebugArtifact { - debug_symbols: contract.functions.iter().map(|function| function.debug.clone()).collect(), - file_map: contract.file_map, - warnings: contract.warnings, - }; let functions = contract.functions.into_iter().map(ContractFunctionArtifact::from).collect(); let contract_artifact = ContractArtifact { @@ -295,9 +278,10 @@ pub(crate) fn generate_contract_artifact(contract: CompiledContract) -> CompileR name: contract.name, functions, events: contract.events, + file_map: contract.file_map, }; - CompileResult::Contract { contract: contract_artifact, debug: debug_artifact } + CompileResult::Contract { contract: contract_artifact, warnings: contract.warnings } } #[cfg(test)] diff --git a/noir/compiler/wasm/src/index.cts b/noir/compiler/wasm/src/index.cts index 31d169e8268..14687e615df 100644 --- a/noir/compiler/wasm/src/index.cts +++ b/noir/compiler/wasm/src/index.cts @@ -3,6 +3,7 @@ import { createNodejsFileManager } from './noir/file-manager/nodejs-file-manager import { NoirWasmCompiler } from './noir/noir-wasm-compiler'; import { LogData, LogFn } from './utils'; import { CompilationResult } from './types/noir_artifact'; +import { inflateDebugSymbols } from './noir/debug'; async function compile( fileManager: FileManager, @@ -46,4 +47,4 @@ async function compile( const createFileManager = createNodejsFileManager; -export { compile, createFileManager, CompilationResult }; +export { compile, createFileManager, inflateDebugSymbols, CompilationResult }; diff --git a/noir/compiler/wasm/src/index.mts b/noir/compiler/wasm/src/index.mts index 6340e2dc37a..8774a7857ef 100644 --- a/noir/compiler/wasm/src/index.mts +++ b/noir/compiler/wasm/src/index.mts @@ -3,6 +3,7 @@ import { createNodejsFileManager } from './noir/file-manager/nodejs-file-manager import { NoirWasmCompiler } from './noir/noir-wasm-compiler'; import { LogData, LogFn } from './utils'; import { CompilationResult } from './types/noir_artifact'; +import { inflateDebugSymbols } from './noir/debug'; async function compile( fileManager: FileManager, @@ -48,4 +49,4 @@ async function compile( const createFileManager = createNodejsFileManager; -export { compile, createFileManager, CompilationResult }; +export { compile, createFileManager, inflateDebugSymbols, CompilationResult }; diff --git a/noir/compiler/wasm/src/noir/debug.ts b/noir/compiler/wasm/src/noir/debug.ts new file mode 100644 index 00000000000..7a65f4b68c2 --- /dev/null +++ b/noir/compiler/wasm/src/noir/debug.ts @@ -0,0 +1,6 @@ +import { inflate } from 'pako'; + +/** Decompresses and decodes the debug symbols */ +export function inflateDebugSymbols(debugSymbols: string) { + return JSON.parse(inflate(Buffer.from(debugSymbols, 'base64'), { to: 'string', raw: true })); +} diff --git a/noir/compiler/wasm/src/types/noir_artifact.ts b/noir/compiler/wasm/src/types/noir_artifact.ts index f2147cfbeda..715877e335f 100644 --- a/noir/compiler/wasm/src/types/noir_artifact.ts +++ b/noir/compiler/wasm/src/types/noir_artifact.ts @@ -49,36 +49,40 @@ export interface NoirFunctionEntry { abi: Abi; /** The bytecode of the function in base64. */ bytecode: string; + /** The debug information, compressed and base64 encoded. */ + debug_symbols: string; } /** * The compilation result of an Noir contract. */ -export interface CompiledContract { +export interface ContractArtifact { /** The name of the contract. */ name: string; - /** Compilation backend. */ - backend: string; + /** Version of noir used for the build. */ + noir_version: string; /** The functions of the contract. */ functions: NoirFunctionEntry[]; /** The events of the contract */ events: EventAbi[]; + /** The map of file ID to the source code and path of the file. */ + file_map: DebugFileMap; } /** * The compilation result of an Noir contract. */ -export interface CompiledCircuit { +export interface ProgramArtifact { /** The hash of the circuit. */ hash?: number; - /** Compilation backend. */ - backend: string; - /** - * The ABI of the function. - */ + /** * The ABI of the function. */ abi: Abi; /** The bytecode of the circuit in base64. */ bytecode: string; + /** The debug information, compressed and base64 encoded. */ + debug_symbols: string; + /** The map of file ID to the source code and path of the file. */ + file_map: DebugFileMap; } /** @@ -142,19 +146,8 @@ export type DebugFileMap = Record< } >; -/** - * The debug metadata of an Noir contract. - */ -export interface DebugMetadata { - /** - * The debug information for each function. - */ - debug_symbols: DebugInfo[]; - /** - * The map of file ID to the source code and path of the file. - */ - file_map: DebugFileMap; -} +/** Compilation warning */ +export type Warning = unknown; /** * The compilation artifacts of a given contract. @@ -163,12 +156,10 @@ export interface ContractCompilationArtifacts { /** * The compiled contract. */ - contract: CompiledContract; + contract: ContractArtifact; - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: DebugMetadata; + /** Compilation warnings. */ + warnings: Warning[]; } /** @@ -182,12 +173,10 @@ export interface ProgramCompilationArtifacts { /** * The compiled contract. */ - program: CompiledCircuit; + program: ProgramArtifact; - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: DebugMetadata; + /** Compilation warnings. */ + warnings: Warning[]; } /** diff --git a/noir/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts b/noir/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts index 3580779ff1d..0d1e22e288f 100644 --- a/noir/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts +++ b/noir/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts @@ -2,7 +2,8 @@ import { getPaths } from '../../shared'; import { expect } from '@esm-bundle/chai'; import { compile, createFileManager } from '@noir-lang/noir_wasm'; -import { CompiledContract } from '../../../src/types/noir_artifact'; +import { ContractArtifact } from '../../../src/types/noir_artifact'; +import { shouldCompileIdentically } from '../shared/compile_with_deps.test'; const paths = getPaths('.'); @@ -21,24 +22,22 @@ async function getPrecompiledSource(path: string): Promise { return JSON.parse(compiledData); } -describe('noir-compiler', () => { - it('both nargo and noir_wasm should compile identically', async () => { - const { contractExpectedArtifact } = paths; - const fm = createFileManager('/'); - const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); - for (const path of files) { - console.log(path); - await fm.writeFile(path, (await getFile(path)).body as ReadableStream); - } - const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as CompiledContract; - nargoArtifact.functions.sort((a, b) => a.name.localeCompare(b.name)); - const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract'); - if (!('contract' in noirWasmArtifact)) { - throw new Error('Compilation failed'); - } - const noirWasmContract = noirWasmArtifact.contract; - expect(noirWasmContract).not.to.be.undefined; - noirWasmContract.functions.sort((a, b) => a.name.localeCompare(b.name)); - expect(nargoArtifact).to.deep.eq(noirWasmContract); - }).timeout(60 * 20e3); +describe('noir-compiler/browser', () => { + shouldCompileIdentically( + async () => { + const { contractExpectedArtifact } = paths; + const fm = createFileManager('/'); + const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); + for (const path of files) { + console.log(path); + await fm.writeFile(path, (await getFile(path)).body as ReadableStream); + } + const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as ContractArtifact; + const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract'); + + return { nargoArtifact, noirWasmArtifact }; + }, + expect, + 60 * 20e3, + ); }); diff --git a/noir/compiler/wasm/test/compiler/node/compile_with_deps.test.ts b/noir/compiler/wasm/test/compiler/node/compile_with_deps.test.ts index 546ec03c183..2a402dc9d02 100644 --- a/noir/compiler/wasm/test/compiler/node/compile_with_deps.test.ts +++ b/noir/compiler/wasm/test/compiler/node/compile_with_deps.test.ts @@ -2,25 +2,19 @@ import { join, resolve } from 'path'; import { getPaths } from '../../shared'; import { expect } from 'chai'; -import { readFile } from 'fs/promises'; import { compile, createFileManager } from '@noir-lang/noir_wasm'; -import { CompiledContract } from '../../../src/types/noir_artifact'; +import { readFile } from 'fs/promises'; +import { ContractArtifact } from '../../../src/types/noir_artifact'; +import { shouldCompileIdentically } from '../shared/compile_with_deps.test'; const basePath = resolve(join(__dirname, '../../')); const { contractProjectPath, contractExpectedArtifact } = getPaths(basePath); -describe('noir-compiler', () => { - it('both nargo and noir_wasm should compile identically', async () => { +describe('noir-compiler/node', () => { + shouldCompileIdentically(async () => { const fm = createFileManager(contractProjectPath); - const nargoArtifact = JSON.parse((await readFile(contractExpectedArtifact)).toString()) as CompiledContract; - nargoArtifact.functions.sort((a, b) => a.name.localeCompare(b.name)); + const nargoArtifact = JSON.parse((await readFile(contractExpectedArtifact)).toString()) as ContractArtifact; const noirWasmArtifact = await compile(fm); - if (!('contract' in noirWasmArtifact)) { - throw new Error('Compilation failed'); - } - const noirWasmContract = noirWasmArtifact.contract; - expect(noirWasmContract).not.to.be.undefined; - noirWasmContract.functions.sort((a, b) => a.name.localeCompare(b.name)); - expect(nargoArtifact).to.deep.eq(noirWasmContract); - }); + return { nargoArtifact, noirWasmArtifact }; + }, expect); }); diff --git a/noir/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts b/noir/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts new file mode 100644 index 00000000000..0960cba0665 --- /dev/null +++ b/noir/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts @@ -0,0 +1,80 @@ +import { CompilationResult, inflateDebugSymbols } from '@noir-lang/noir_wasm'; +import { type expect as Expect } from 'chai'; +import { + ContractArtifact, + ContractCompilationArtifacts, + DebugFileMap, + DebugInfo, + NoirFunctionEntry, +} from '../../../src/types/noir_artifact'; + +export function shouldCompileIdentically( + compileFn: () => Promise<{ nargoArtifact: ContractArtifact; noirWasmArtifact: CompilationResult }>, + expect: typeof Expect, + timeout = 5000, +) { + it('both nargo and noir_wasm should compile identically', async () => { + // Compile! + const { nargoArtifact, noirWasmArtifact } = await compileFn(); + + // Prepare nargo artifact + const [nargoDebugInfos, nargoFileMap] = deleteDebugMetadata(nargoArtifact); + normalizeVersion(nargoArtifact); + + // Prepare noir-wasm artifact + const noirWasmContract = (noirWasmArtifact as ContractCompilationArtifacts).contract; + expect(noirWasmContract).not.to.be.undefined; + const [noirWasmDebugInfos, norWasmFileMap] = deleteDebugMetadata(noirWasmContract); + normalizeVersion(noirWasmContract); + + // We first compare both contracts without considering debug info + expect(nargoArtifact).to.deep.eq(noirWasmContract); + + // Compare the file maps, ignoring keys, since those depend in the order in which files are visited, + // which may change depending on the file manager implementation. Also ignores paths, since the base + // path is reported differently between nargo and noir-wasm. + expect(getSources(nargoFileMap)).to.have.members(getSources(norWasmFileMap)); + + // Compare the debug symbol information, ignoring the actual ids used for file identifiers. + // Debug symbol info looks like the following, what we need is to ignore the 'file' identifiers + // {"locations":{"0":[{"span":{"start":141,"end":156},"file":39},{"span":{"start":38,"end":76},"file":38},{"span":{"start":824,"end":862},"file":23}]}} + expect(nargoDebugInfos).to.deep.eq(noirWasmDebugInfos); + }).timeout(timeout); +} + +/** Remove commit identifier from version, which may not match depending on cached nargo and noir-wasm */ +function normalizeVersion(contract: ContractArtifact) { + contract.noir_version = contract.noir_version.replace(/\+.+$/, ''); +} + +/** Extracts the debug symbols from all functions, decodes them, removes their file identifiers, and deletes them from the artifact. */ +function extractDebugInfos(fns: NoirFunctionEntry[]) { + return fns.map((fn) => { + const debugSymbols = inflateDebugSymbols(fn.debug_symbols); + delete (fn as Partial).debug_symbols; + clearFileIdentifiers(debugSymbols); + return debugSymbols; + }); +} + +/** Deletes all debug info from a contract and returns it. */ +function deleteDebugMetadata(contract: ContractArtifact) { + contract.functions.sort((a, b) => a.name.localeCompare(b.name)); + const fileMap = contract.file_map; + delete (contract as Partial).file_map; + return [extractDebugInfos(contract.functions), fileMap]; +} + +/** Clears file identifiers from a set of debug symbols. */ +function clearFileIdentifiers(debugSymbols: DebugInfo) { + for (const loc of Object.values(debugSymbols.locations)) { + for (const span of loc) { + span.file = 0; + } + } +} + +/** Returns list of sources from file map, dropping paths along the way, since they don't match depending on the file manager. */ +function getSources(fileMap: DebugFileMap) { + return Object.values(fileMap).map((file) => file.source); +} diff --git a/noir/tooling/nargo/src/artifacts/contract.rs b/noir/tooling/nargo/src/artifacts/contract.rs index 04699126762..d928b09fcb9 100644 --- a/noir/tooling/nargo/src/artifacts/contract.rs +++ b/noir/tooling/nargo/src/artifacts/contract.rs @@ -1,8 +1,14 @@ use acvm::acir::circuit::Circuit; use noirc_abi::{Abi, ContractEvent}; -use noirc_driver::{ContractFunction, ContractFunctionType}; +use noirc_driver::{CompiledContract, ContractFunction, ContractFunctionType}; use serde::{Deserialize, Serialize}; +use noirc_driver::DebugFile; +use noirc_errors::debug_info::DebugInfo; +use std::collections::BTreeMap; + +use fm::FileId; + #[derive(Serialize, Deserialize)] pub struct ContractArtifact { /// Version of noir used to compile this contract @@ -13,6 +19,20 @@ pub struct ContractArtifact { pub functions: Vec, /// All the events defined inside the contract scope. pub events: Vec, + /// Map of file Id to the source code so locations in debug info can be mapped to source code they point to. + pub file_map: BTreeMap, +} + +impl From for ContractArtifact { + fn from(contract: CompiledContract) -> Self { + ContractArtifact { + noir_version: contract.noir_version, + name: contract.name, + functions: contract.functions.into_iter().map(ContractFunctionArtifact::from).collect(), + events: contract.events, + file_map: contract.file_map, + } + } } /// Each function in the contract will be compiled as a separate noir program. @@ -34,6 +54,12 @@ pub struct ContractFunctionArtifact { deserialize_with = "Circuit::deserialize_circuit_base64" )] pub bytecode: Circuit, + + #[serde( + serialize_with = "DebugInfo::serialize_compressed_base64_json", + deserialize_with = "DebugInfo::deserialize_compressed_base64_json" + )] + pub debug_symbols: DebugInfo, } impl From for ContractFunctionArtifact { @@ -44,6 +70,7 @@ impl From for ContractFunctionArtifact { is_internal: func.is_internal, abi: func.abi, bytecode: func.bytecode, + debug_symbols: func.debug, } } } diff --git a/noir/tooling/nargo/src/artifacts/program.rs b/noir/tooling/nargo/src/artifacts/program.rs index 96e63e6fe50..d8dc42ec214 100644 --- a/noir/tooling/nargo/src/artifacts/program.rs +++ b/noir/tooling/nargo/src/artifacts/program.rs @@ -1,6 +1,11 @@ +use std::collections::BTreeMap; + use acvm::acir::circuit::Circuit; +use fm::FileId; use noirc_abi::Abi; use noirc_driver::CompiledProgram; +use noirc_driver::DebugFile; +use noirc_errors::debug_info::DebugInfo; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] @@ -20,6 +25,15 @@ pub struct ProgramArtifact { deserialize_with = "Circuit::deserialize_circuit_base64" )] pub bytecode: Circuit, + + #[serde( + serialize_with = "DebugInfo::serialize_compressed_base64_json", + deserialize_with = "DebugInfo::deserialize_compressed_base64_json" + )] + pub debug_symbols: DebugInfo, + + /// Map of file Id to the source code so locations in debug info can be mapped to source code they point to. + pub file_map: BTreeMap, } impl From for ProgramArtifact { @@ -29,6 +43,22 @@ impl From for ProgramArtifact { abi: program.abi, noir_version: program.noir_version, bytecode: program.circuit, + debug_symbols: program.debug, + file_map: program.file_map, + } + } +} + +impl From for CompiledProgram { + fn from(program: ProgramArtifact) -> Self { + CompiledProgram { + hash: program.hash, + abi: program.abi, + noir_version: program.noir_version, + circuit: program.bytecode, + debug: program.debug_symbols, + file_map: program.file_map, + warnings: vec![], } } } diff --git a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs index 8bc35080fd7..9e739f5c818 100644 --- a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -1,10 +1,8 @@ use std::path::Path; use acvm::ExpressionWidth; + use fm::FileManager; -use iter_extended::vecmap; -use nargo::artifacts::contract::{ContractArtifact, ContractFunctionArtifact}; -use nargo::artifacts::debug::DebugArtifact; use nargo::artifacts::program::ProgramArtifact; use nargo::errors::CompileError; use nargo::insert_all_files_for_workspace_into_file_manager; @@ -15,6 +13,7 @@ use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelec use noirc_driver::file_manager_with_stdlib; use noirc_driver::NOIR_ARTIFACT_VERSION_STRING; use noirc_driver::{CompilationResult, CompileOptions, CompiledContract, CompiledProgram}; + use noirc_frontend::graph::CrateName; use clap::Args; @@ -23,10 +22,7 @@ use crate::backends::Backend; use crate::errors::CliError; use super::fs::program::only_acir; -use super::fs::program::{ - read_debug_artifact_from_file, read_program_from_file, save_contract_to_file, - save_debug_artifact_to_file, save_program_to_file, -}; +use super::fs::program::{read_program_from_file, save_contract_to_file, save_program_to_file}; use super::NargoConfig; use rayon::prelude::*; @@ -170,28 +166,11 @@ fn compile_program( let (mut context, crate_id) = prepare_package(file_manager, package); let program_artifact_path = workspace.package_build_path(package); - let mut debug_artifact_path = program_artifact_path.clone(); - debug_artifact_path.set_file_name(format!("debug_{}.json", package.name)); - let cached_program = if let (Ok(program_artifact), Ok(mut debug_artifact)) = ( - read_program_from_file(program_artifact_path), - read_debug_artifact_from_file(debug_artifact_path), - ) { - if program_artifact.noir_version == NOIR_ARTIFACT_VERSION_STRING { - Some(CompiledProgram { - hash: program_artifact.hash, - circuit: program_artifact.bytecode, - abi: program_artifact.abi, - noir_version: program_artifact.noir_version, - debug: debug_artifact.debug_symbols.remove(0), - file_map: debug_artifact.file_map, - warnings: debug_artifact.warnings, - }) - } else { - None - } - } else { - None - }; + let cached_program: Option = + read_program_from_file(program_artifact_path) + .ok() + .filter(|p| p.noir_version == NOIR_ARTIFACT_VERSION_STRING) + .map(|p| p.into()); let (program, warnings) = noirc_driver::compile_main(&mut context, crate_id, compile_options, cached_program)?; @@ -236,51 +215,13 @@ pub(super) fn save_program( } else { save_program_to_file(&program_artifact, &package.name, circuit_dir); } - - let debug_artifact = DebugArtifact { - debug_symbols: vec![program.debug], - file_map: program.file_map, - warnings: program.warnings, - }; - let circuit_name: String = (&package.name).into(); - save_debug_artifact_to_file(&debug_artifact, &circuit_name, circuit_dir); } fn save_contract(contract: CompiledContract, package: &Package, circuit_dir: &Path) { - // TODO(#1389): I wonder if it is incorrect for nargo-core to know anything about contracts. - // As can be seen here, It seems like a leaky abstraction where ContractFunctions (essentially CompiledPrograms) - // are compiled via nargo-core and then the ContractArtifact is constructed here. - // This is due to EACH function needing it's own CRS, PKey, and VKey from the backend. - let debug_artifact = DebugArtifact { - debug_symbols: contract.functions.iter().map(|function| function.debug.clone()).collect(), - file_map: contract.file_map, - warnings: contract.warnings, - }; - - let functions = vecmap(contract.functions, |func| ContractFunctionArtifact { - name: func.name, - function_type: func.function_type, - is_internal: func.is_internal, - abi: func.abi, - bytecode: func.bytecode, - }); - - let contract_artifact = ContractArtifact { - noir_version: contract.noir_version, - name: contract.name, - functions, - events: contract.events, - }; - + let contract_name = contract.name.clone(); save_contract_to_file( - &contract_artifact, - &format!("{}-{}", package.name, contract_artifact.name), - circuit_dir, - ); - - save_debug_artifact_to_file( - &debug_artifact, - &format!("{}-{}", package.name, contract_artifact.name), + &contract.into(), + &format!("{}-{}", package.name, contract_name), circuit_dir, ); } diff --git a/noir/tooling/nargo_cli/src/cli/fs/program.rs b/noir/tooling/nargo_cli/src/cli/fs/program.rs index 1d2f012736e..1fb57ae6685 100644 --- a/noir/tooling/nargo_cli/src/cli/fs/program.rs +++ b/noir/tooling/nargo_cli/src/cli/fs/program.rs @@ -1,9 +1,7 @@ use std::path::{Path, PathBuf}; use acvm::acir::circuit::Circuit; -use nargo::artifacts::{ - contract::ContractArtifact, debug::DebugArtifact, program::ProgramArtifact, -}; +use nargo::artifacts::{contract::ContractArtifact, program::ProgramArtifact}; use noirc_frontend::graph::CrateName; use crate::errors::FilesystemError; @@ -40,15 +38,6 @@ pub(crate) fn save_contract_to_file>( save_build_artifact_to_file(compiled_contract, circuit_name, circuit_dir) } -pub(crate) fn save_debug_artifact_to_file>( - debug_artifact: &DebugArtifact, - circuit_name: &str, - circuit_dir: P, -) -> PathBuf { - let artifact_name = format!("debug_{circuit_name}"); - save_build_artifact_to_file(debug_artifact, &artifact_name, circuit_dir) -} - fn save_build_artifact_to_file, T: ?Sized + serde::Serialize>( build_artifact: &T, artifact_name: &str, @@ -74,14 +63,3 @@ pub(crate) fn read_program_from_file>( Ok(program) } - -pub(crate) fn read_debug_artifact_from_file>( - debug_artifact_path: P, -) -> Result { - let input_string = std::fs::read(&debug_artifact_path) - .map_err(|_| FilesystemError::PathNotValid(debug_artifact_path.as_ref().into()))?; - let program = serde_json::from_slice(&input_string) - .map_err(|err| FilesystemError::ProgramSerializationError(err.to_string()))?; - - Ok(program) -} diff --git a/noir/yarn.lock b/noir/yarn.lock index ea141531601..86a3d2242a0 100644 --- a/noir/yarn.lock +++ b/noir/yarn.lock @@ -4478,6 +4478,7 @@ __metadata: "@types/mocha": ^10.0.6 "@types/mocha-each": ^2 "@types/node": ^20.10.5 + "@types/pako": ^2 "@types/path-browserify": ^1 "@types/readable-stream": ^4 "@types/sinon": ^17 @@ -4494,6 +4495,7 @@ __metadata: memfs: ^4.6.0 mocha: ^10.2.0 mocha-each: ^2.0.1 + pako: ^2.1.0 path-browserify: ^1.0.1 process: ^0.11.10 readable-stream: ^4.4.2 @@ -6096,6 +6098,13 @@ __metadata: languageName: node linkType: hard +"@types/pako@npm:^2": + version: 2.0.3 + resolution: "@types/pako@npm:2.0.3" + checksum: 0746dd5d29eccf5b2e6cceb3ccb093851219e78bd2e2e20d25757e247987139e061e5d4ba37cb5295493f06e3c683c74f8876011cd8a3f3748a09244fbc841d9 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.2 resolution: "@types/parse-json@npm:4.0.2" @@ -16897,6 +16906,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:^2.1.0": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 71666548644c9a4d056bcaba849ca6fd7242c6cf1af0646d3346f3079a1c7f4a66ffec6f7369ee0dc88f61926c10d6ab05da3e1fca44b83551839e89edd75a3e + languageName: node + linkType: hard + "param-case@npm:^3.0.4": version: 3.0.4 resolution: "param-case@npm:3.0.4" diff --git a/yarn-project/accounts/package.json b/yarn-project/accounts/package.json index 2a59abcc1b3..e9c63424550 100644 --- a/yarn-project/accounts/package.json +++ b/yarn-project/accounts/package.json @@ -24,7 +24,7 @@ }, "scripts": { "build": "yarn clean && yarn build:copy-contracts && tsc -b", - "build:copy-contracts": "mkdir -p ./src/artifacts && cp ../noir-contracts/src/SchnorrAccount.json ../noir-contracts/src/EcdsaAccount.json ../noir-contracts/src/SchnorrSingleKeyAccount.json ./src/artifacts", + "build:copy-contracts": "./scripts/copy-contracts.sh", "build:dev": "tsc -b --watch", "build:ts": "tsc -b", "clean": "rm -rf ./dest .tsbuildinfo ./src/artifacts", diff --git a/yarn-project/accounts/package.local.json b/yarn-project/accounts/package.local.json index e502a0f74d0..def45a001a2 100644 --- a/yarn-project/accounts/package.local.json +++ b/yarn-project/accounts/package.local.json @@ -1,7 +1,7 @@ { "scripts": { "build": "yarn clean && yarn build:copy-contracts && tsc -b", - "build:copy-contracts": "mkdir -p ./src/artifacts && cp ../noir-contracts/src/SchnorrAccount.json ../noir-contracts/src/EcdsaAccount.json ../noir-contracts/src/SchnorrSingleKeyAccount.json ./src/artifacts", + "build:copy-contracts": "./scripts/copy-contracts.sh", "build:dev": "tsc -b --watch", "build:ts": "tsc -b", "clean": "rm -rf ./dest .tsbuildinfo ./src/artifacts" diff --git a/yarn-project/accounts/scripts/copy-contracts.sh b/yarn-project/accounts/scripts/copy-contracts.sh new file mode 100755 index 00000000000..1fb52a3f282 --- /dev/null +++ b/yarn-project/accounts/scripts/copy-contracts.sh @@ -0,0 +1,11 @@ +#! /bin/bash +set -euo pipefail +mkdir -p ./src/artifacts + +contracts=(schnorr_account_contract-SchnorrAccount ecdsa_account_contract-EcdsaAccount schnorr_single_key_account_contract-SchnorrSingleKeyAccount) + +for contract in "${contracts[@]}"; do + cp "../noir-contracts/target/$contract.json" ./src/artifacts/${contract#*-}.json +done + +yarn run -T prettier -w ./src/artifacts \ No newline at end of file diff --git a/yarn-project/accounts/src/ecdsa/artifact.ts b/yarn-project/accounts/src/ecdsa/artifact.ts index 05c0aae17c9..4dd2fd60dd6 100644 --- a/yarn-project/accounts/src/ecdsa/artifact.ts +++ b/yarn-project/accounts/src/ecdsa/artifact.ts @@ -1,5 +1,5 @@ -import { ContractArtifact } from '@aztec/aztec.js'; +import { NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js'; import EcdsaAccountContractJson from '../artifacts/EcdsaAccount.json' assert { type: 'json' }; -export const EcdsaAccountContractArtifact = EcdsaAccountContractJson as ContractArtifact; +export const EcdsaAccountContractArtifact = loadContractArtifact(EcdsaAccountContractJson as NoirCompiledContract); diff --git a/yarn-project/accounts/src/schnorr/artifact.ts b/yarn-project/accounts/src/schnorr/artifact.ts index 672b633420e..1d9088bf3ca 100644 --- a/yarn-project/accounts/src/schnorr/artifact.ts +++ b/yarn-project/accounts/src/schnorr/artifact.ts @@ -1,5 +1,5 @@ -import { ContractArtifact } from '@aztec/aztec.js'; +import { NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js'; import SchnorrAccountContractJson from '../artifacts/SchnorrAccount.json' assert { type: 'json' }; -export const SchnorrAccountContractArtifact = SchnorrAccountContractJson as ContractArtifact; +export const SchnorrAccountContractArtifact = loadContractArtifact(SchnorrAccountContractJson as NoirCompiledContract); diff --git a/yarn-project/accounts/src/single_key/artifact.ts b/yarn-project/accounts/src/single_key/artifact.ts index 4bb139b1252..954c5f199e7 100644 --- a/yarn-project/accounts/src/single_key/artifact.ts +++ b/yarn-project/accounts/src/single_key/artifact.ts @@ -1,5 +1,7 @@ -import { ContractArtifact } from '@aztec/aztec.js'; +import { NoirCompiledContract, loadContractArtifact } from '@aztec/aztec.js'; import SchnorrSingleKeyAccountContractJson from '../artifacts/SchnorrSingleKeyAccount.json' assert { type: 'json' }; -export const SchnorrSingleKeyAccountContractArtifact = SchnorrSingleKeyAccountContractJson as ContractArtifact; +export const SchnorrSingleKeyAccountContractArtifact = loadContractArtifact( + SchnorrSingleKeyAccountContractJson as NoirCompiledContract, +); diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index 560c5923ecd..eb4f59fae84 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -1,6 +1,6 @@ import { L2Block, MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/circuit-types'; import { BlockHeader, CompleteAddress, GrumpkinPrivateKey, PublicKey } from '@aztec/circuits.js'; -import { FunctionArtifact, FunctionDebugMetadata, FunctionSelector } from '@aztec/foundation/abi'; +import { FunctionArtifactWithDebugMetadata, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -17,16 +17,6 @@ export class ContractNotFoundError extends Error { } } -/** - * A function artifact with optional debug metadata - */ -export interface FunctionArtifactWithDebugMetadata extends FunctionArtifact { - /** - * Debug metadata for the function. - */ - debug?: FunctionDebugMetadata; -} - /** * The database oracle interface. */ diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index cadb2afec9f..becd6a0f73c 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -21,7 +21,13 @@ import { siloCommitment, } from '@aztec/circuits.js/abis'; import { makeContractDeploymentData } from '@aztec/circuits.js/factories'; -import { FunctionArtifact, FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; +import { + FunctionArtifact, + FunctionSelector, + encodeArguments, + getFunctionArtifact, + getFunctionArtifactWithSelector, +} from '@aztec/foundation/abi'; import { asyncMap } from '@aztec/foundation/async-map'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { pedersenHash } from '@aztec/foundation/crypto'; @@ -46,7 +52,7 @@ import { default as levelup } from 'levelup'; import { type MemDown, default as memdown } from 'memdown'; import { getFunctionSelector } from 'viem'; -import { buildL1ToL2Message, getFunctionArtifact, getFunctionArtifactWithSelector } from '../test/utils.js'; +import { buildL1ToL2Message } from '../test/utils.js'; import { computeSlotForMapping } from '../utils.js'; import { DBOracle } from './db_oracle.js'; import { AcirSimulator } from './simulator.js'; @@ -229,7 +235,6 @@ describe('Private Execution test suite', () => { it('should have a constructor with arguments that inserts notes', async () => { const artifact = getFunctionArtifact(StatefulTestContractArtifact, 'constructor'); - const result = await runSimulator({ args: [owner, 140], artifact }); expect(result.newNotes).toHaveLength(1); diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index d2d43ca534d..c16fcd83b34 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -1,5 +1,5 @@ import { FunctionData, PrivateCallStackItem } from '@aztec/circuits.js'; -import { decodeReturnValues } from '@aztec/foundation/abi'; +import { FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -9,7 +9,6 @@ import { extractPrivateCircuitPublicInputs } from '../acvm/deserialize.js'; import { Oracle, acvm, extractCallStack } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { ClientExecutionContext } from './client_execution_context.js'; -import { FunctionArtifactWithDebugMetadata } from './db_oracle.js'; import { ExecutionResult } from './execution_result.js'; import { AcirSimulator } from './simulator.js'; diff --git a/yarn-project/acir-simulator/src/client/simulator.test.ts b/yarn-project/acir-simulator/src/client/simulator.test.ts index 406a1292461..b1e0d95a4bc 100644 --- a/yarn-project/acir-simulator/src/client/simulator.test.ts +++ b/yarn-project/acir-simulator/src/client/simulator.test.ts @@ -1,7 +1,7 @@ import { Note } from '@aztec/circuit-types'; import { CompleteAddress } from '@aztec/circuits.js'; import { computeUniqueCommitment, siloCommitment } from '@aztec/circuits.js/abis'; -import { ABIParameterVisibility } from '@aztec/foundation/abi'; +import { ABIParameterVisibility, FunctionArtifactWithDebugMetadata, getFunctionArtifact } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { pedersenHash } from '@aztec/foundation/crypto'; import { Fr, GrumpkinScalar } from '@aztec/foundation/fields'; @@ -9,8 +9,7 @@ import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; import { MockProxy, mock } from 'jest-mock-extended'; -import { getFunctionArtifact } from '../test/utils.js'; -import { DBOracle, FunctionArtifactWithDebugMetadata } from './db_oracle.js'; +import { DBOracle } from './db_oracle.js'; import { AcirSimulator } from './simulator.js'; describe('Simulator', () => { diff --git a/yarn-project/acir-simulator/src/client/simulator.ts b/yarn-project/acir-simulator/src/client/simulator.ts index 0e11e43509d..92430a19969 100644 --- a/yarn-project/acir-simulator/src/client/simulator.ts +++ b/yarn-project/acir-simulator/src/client/simulator.ts @@ -1,7 +1,13 @@ import { AztecNode, FunctionCall, Note, TxExecutionRequest } from '@aztec/circuit-types'; import { CallContext, FunctionData } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { ArrayType, FunctionSelector, FunctionType, encodeArguments } from '@aztec/foundation/abi'; +import { + ArrayType, + FunctionArtifactWithDebugMetadata, + FunctionSelector, + FunctionType, + encodeArguments, +} from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; @@ -12,7 +18,7 @@ import { WasmBlackBoxFunctionSolver, createBlackBoxSolver } from '@noir-lang/acv import { createSimulationError } from '../common/errors.js'; import { PackedArgsCache } from '../common/packed_args_cache.js'; import { ClientExecutionContext } from './client_execution_context.js'; -import { DBOracle, FunctionArtifactWithDebugMetadata } from './db_oracle.js'; +import { DBOracle } from './db_oracle.js'; import { ExecutionNoteCache } from './execution_note_cache.js'; import { ExecutionResult } from './execution_result.js'; import { executePrivateFunction } from './private_execution.js'; diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts index c59fe74c405..7d7f3d264b7 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts @@ -1,5 +1,5 @@ import { FunctionData } from '@aztec/circuits.js'; -import { DecodedReturn, decodeReturnValues } from '@aztec/foundation/abi'; +import { DecodedReturn, FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -8,7 +8,6 @@ import { extractReturnWitness } from '../acvm/deserialize.js'; import { ACVMField, Oracle, acvm, extractCallStack, fromACVMField, toACVMWitness } from '../acvm/index.js'; import { ExecutionError } from '../common/errors.js'; import { AcirSimulator } from '../index.js'; -import { FunctionArtifactWithDebugMetadata } from './db_oracle.js'; import { ViewDataOracle } from './view_data_oracle.js'; /** diff --git a/yarn-project/acir-simulator/src/test/utils.ts b/yarn-project/acir-simulator/src/test/utils.ts index e2e0310d458..916243400e4 100644 --- a/yarn-project/acir-simulator/src/test/utils.ts +++ b/yarn-project/acir-simulator/src/test/utils.ts @@ -1,11 +1,8 @@ import { L1Actor, L1ToL2Message, L2Actor } from '@aztec/circuit-types'; import { AztecAddress, EthAddress, Fr } from '@aztec/circuits.js'; import { computeSecretMessageHash } from '@aztec/circuits.js/abis'; -import { ContractArtifact, FunctionSelector, getFunctionDebugMetadata } from '@aztec/foundation/abi'; import { sha256 } from '@aztec/foundation/crypto'; -import { FunctionArtifactWithDebugMetadata } from '../index.js'; - /** * Test utility function to craft an L1 to L2 message. * @param selector - The cross chain message selector. @@ -39,31 +36,3 @@ export const buildL1ToL2Message = ( 0, ); }; - -export const getFunctionArtifact = ( - artifact: ContractArtifact, - functionName: string, -): FunctionArtifactWithDebugMetadata => { - const functionArtifact = artifact.functions.find(f => f.name === functionName); - if (!functionArtifact) { - throw new Error(`Unknown function ${functionName}`); - } - - const debug = getFunctionDebugMetadata(artifact, functionName); - return { ...functionArtifact, debug }; -}; - -export const getFunctionArtifactWithSelector = ( - artifact: ContractArtifact, - functionSelector: FunctionSelector, -): FunctionArtifactWithDebugMetadata => { - const functionArtifact = artifact.functions.find(f => - functionSelector.equals(FunctionSelector.fromNameAndParameters(f.name, f.parameters)), - ); - if (!functionArtifact) { - throw new Error(`Unknown function ${functionSelector}`); - } - - const debug = getFunctionDebugMetadata(artifact, functionArtifact.name); - return { ...functionArtifact, debug }; -}; diff --git a/yarn-project/aztec.js/package.json b/yarn-project/aztec.js/package.json index 7b6d167eef2..c3e3c461005 100644 --- a/yarn-project/aztec.js/package.json +++ b/yarn-project/aztec.js/package.json @@ -25,8 +25,7 @@ "tsconfig": "./tsconfig.json" }, "scripts": { - "build": "yarn clean && yarn build:copy-contracts && tsc -b && webpack", - "build:copy-contracts": "mkdir -p ./src/account_contract/artifacts && cp ../noir-contracts/src/SchnorrAccount.json ../noir-contracts/src/EcdsaAccount.json ../noir-contracts/src/SchnorrSingleKeyAccount.json ./src/account_contract/artifacts", + "build": "yarn clean && tsc -b && webpack", "build:web": "webpack", "build:dev": "tsc -b --watch", "build:ts": "tsc -b", diff --git a/yarn-project/aztec.js/package.local.json b/yarn-project/aztec.js/package.local.json index db6bd6204c7..e070cdff602 100644 --- a/yarn-project/aztec.js/package.local.json +++ b/yarn-project/aztec.js/package.local.json @@ -1,6 +1,6 @@ { "scripts": { - "build": "yarn clean && yarn build:copy-contracts && tsc -b && webpack", + "build": "yarn clean && tsc -b && webpack", "build:web": "webpack", "build:dev": "tsc -b --watch", "build:ts": "tsc -b", diff --git a/yarn-project/aztec.js/src/api/abi.ts b/yarn-project/aztec.js/src/api/abi.ts index d76502b881d..7e94b7eb30f 100644 --- a/yarn-project/aztec.js/src/api/abi.ts +++ b/yarn-project/aztec.js/src/api/abi.ts @@ -1 +1,3 @@ export { ContractArtifact, FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi'; +export { loadContractArtifact } from '@aztec/types/abi'; +export { NoirCompiledContract } from '@aztec/types/noir'; diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 524eb778393..14bdbe71f29 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -43,6 +43,7 @@ describe('Contract Class', () => { name: 'bar', functionType: FunctionType.SECRET, isInternal: false, + debugSymbols: '', parameters: [ { name: 'value', @@ -69,6 +70,7 @@ describe('Contract Class', () => { parameters: [], returnTypes: [], bytecode: '0be', + debugSymbols: '', }, { name: 'qux', @@ -91,9 +93,11 @@ describe('Contract Class', () => { }, ], bytecode: '0cd', + debugSymbols: '', }, ], events: [], + fileMap: {}, }; beforeEach(() => { diff --git a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts index 8316f8f0a71..6fabd46e2ac 100644 --- a/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts +++ b/yarn-project/aztec.js/src/contract_deployer/contract_deployer.test.ts @@ -19,9 +19,11 @@ describe.skip('Contract Deployer', () => { parameters: [], returnTypes: [], bytecode: '0af', + debugSymbols: '', }, ], events: [], + fileMap: {}, }; const publicKey: PublicKey = Point.random(); diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index d0f2ba7a8a2..ff2378a23bf 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -64,7 +64,6 @@ export { EthAddress, Fr, Fq, - FunctionSelector, GlobalVariables, GrumpkinScalar, Point, @@ -114,7 +113,7 @@ export { NodeInfo } from '@aztec/types/interfaces'; // TODO: These kinds of things have no place on our public api. // External devs will almost certainly have their own methods of doing these things. // If we want to use them in our own "aztec.js consuming code", import them from foundation as needed. -export { ContractArtifact, FunctionArtifact, encodeArguments } from '@aztec/foundation/abi'; +export { encodeArguments } from '@aztec/foundation/abi'; export { sha256 } from '@aztec/foundation/crypto'; export { DebugLogger, createDebugLogger, onLog } from '@aztec/foundation/log'; export { retry, retryUntil } from '@aztec/foundation/retry'; @@ -137,3 +136,4 @@ export { // Here you *can* do `export *` as the granular api defacto exports things explicitly. // This entire index file will be deprecated at some point after we're satisfied. export * from './api/init.js'; +export * from './api/abi.js'; diff --git a/yarn-project/circuit-types/src/contract_dao.test.ts b/yarn-project/circuit-types/src/contract_dao.test.ts index 5cfa8ea7e92..5e8faa4650a 100644 --- a/yarn-project/circuit-types/src/contract_dao.test.ts +++ b/yarn-project/circuit-types/src/contract_dao.test.ts @@ -38,9 +38,11 @@ describe('ContractDao', () => { ], returnTypes: [], bytecode: '0af', + debugSymbols: '', }, ], events: [], + fileMap: {}, }; const dao = new ContractDao(artifact, CompleteAddress.random(), EthAddress.random()); diff --git a/yarn-project/circuit-types/src/contract_dao.ts b/yarn-project/circuit-types/src/contract_dao.ts index 2817c1f2551..79e07af969f 100644 --- a/yarn-project/circuit-types/src/contract_dao.ts +++ b/yarn-project/circuit-types/src/contract_dao.ts @@ -1,7 +1,7 @@ import { CompleteAddress, ContractFunctionDao } from '@aztec/circuits.js'; import { ContractArtifact, - DebugMetadata, + DebugFileMap, EventAbi, FunctionDebugMetadata, FunctionSelector, @@ -46,8 +46,8 @@ export class ContractDao implements ContractArtifact { return this.contractArtifact.events; } - get debug(): DebugMetadata | undefined { - return this.contractArtifact.debug; + get fileMap(): DebugFileMap { + return this.contractArtifact.fileMap; } getFunctionArtifact(selector: FunctionSelector): ContractFunctionDao | undefined { @@ -59,7 +59,8 @@ export class ContractDao implements ContractArtifact { } getFunctionDebugMetadataByName(functionName: string): FunctionDebugMetadata | undefined { - return getFunctionDebugMetadata(this, functionName); + const fn = this.getFunctionArtifactByName(functionName); + return fn && getFunctionDebugMetadata(this, fn); } toBuffer(): Buffer { diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 44336d6caa3..c4d9827e852 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -48,6 +48,7 @@ export const randomContractArtifact = (): ContractArtifact => ({ name: randomBytes(4).toString('hex'), functions: [], events: [], + fileMap: {}, }); export const randomDeployedContract = (): DeployedContract => ({ diff --git a/yarn-project/cli/src/cmds/get_accounts.ts b/yarn-project/cli/src/cmds/get_accounts.ts index 155e92d5a4e..79f9dbee325 100644 --- a/yarn-project/cli/src/cmds/get_accounts.ts +++ b/yarn-project/cli/src/cmds/get_accounts.ts @@ -5,11 +5,31 @@ import { createCompatibleClient } from '../client.js'; /** * */ -export async function getAccounts(rpcUrl: string, debugLogger: DebugLogger, log: LogFn) { +export async function getAccounts( + rpcUrl: string, + json: boolean, + debugLogger: DebugLogger, + log: LogFn, + logJson: (output: any) => void, +) { const client = await createCompatibleClient(rpcUrl, debugLogger); const accounts = await client.getRegisteredAccounts(); if (!accounts.length) { - log('No accounts found.'); + if (json) { + logJson([]); + } else { + log('No accounts found.'); + } + return; + } + if (json) { + logJson( + accounts.map(a => ({ + address: a.address.toString(), + publicKey: a.publicKey.toString(), + partialAddress: a.partialAddress.toString(), + })), + ); } else { log(`Accounts found: \n`); for (const account of accounts) { diff --git a/yarn-project/cli/src/index.ts b/yarn-project/cli/src/index.ts index 7b108c9f724..074745d6647 100644 --- a/yarn-project/cli/src/index.ts +++ b/yarn-project/cli/src/index.ts @@ -296,9 +296,10 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command { .command('get-accounts') .description('Gets all the Aztec accounts stored in the PXE.') .addOption(pxeOption) + .option('--json', 'Emit output as json') .action(async (options: any) => { const { getAccounts } = await import('./cmds/get_accounts.js'); - await getAccounts(options.rpcUrl, debugLogger, log); + await getAccounts(options.rpcUrl, options.json, debugLogger, log, logJson); }); program diff --git a/yarn-project/cli/src/test/mocks.ts b/yarn-project/cli/src/test/mocks.ts index f02cbdb8f75..7a9e662eda6 100644 --- a/yarn-project/cli/src/test/mocks.ts +++ b/yarn-project/cli/src/test/mocks.ts @@ -18,6 +18,7 @@ export const mockContractArtifact: ContractArtifact = { ], returnTypes: [], bytecode: 'constructorBytecode', + debugSymbols: '', }, { name: 'mockFunction', @@ -59,7 +60,9 @@ export const mockContractArtifact: ContractArtifact = { ], returnTypes: [{ kind: 'boolean' }], bytecode: 'mockBytecode', + debugSymbols: '', }, ], events: [], + fileMap: {}, }; diff --git a/yarn-project/end-to-end/src/guides/up_quick_start.sh b/yarn-project/end-to-end/src/guides/up_quick_start.sh index badbad7a9e8..cd6de58da45 100755 --- a/yarn-project/end-to-end/src/guides/up_quick_start.sh +++ b/yarn-project/end-to-end/src/guides/up_quick_start.sh @@ -3,11 +3,10 @@ set -eux -# The following accounts and pks must match the ones exposed by the sandbox. - # docs:start:declare-accounts -ALICE="0x26fc40ccf8622e4ac4bb1132762cb3917933b1b556155b1964bbbfdd3071ff5c" -BOB="0x2a0f32c34c5b948a7f9766f0c1aad70a86c0ee649f56208e936be4324d49b0b9" +ACCOUNTS=$(aztec-cli get-accounts --json | jq -r '.[].address') +ALICE=$(echo "$ACCOUNTS" | sed -n 1p) +BOB=$(echo "$ACCOUNTS" | sed -n 2p) ALICE_PRIVATE_KEY="0x2153536ff6628eee01cf4024889ff977a18d9fa61d0e414422f7681cf085c281" # docs:end:declare-accounts diff --git a/yarn-project/end-to-end/src/sample-dapp/contracts.mjs b/yarn-project/end-to-end/src/sample-dapp/contracts.mjs index bb02c811fb0..83dfc7d054f 100644 --- a/yarn-project/end-to-end/src/sample-dapp/contracts.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/contracts.mjs @@ -1,11 +1,11 @@ -import { AztecAddress, Contract } from '@aztec/aztec.js'; -import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; +import { AztecAddress, Contract, loadContractArtifact } from '@aztec/aztec.js'; +import TokenContractJson from '@aztec/noir-contracts/target/token_contract-Token' assert { type: 'json' }; import { readFileSync } from 'fs'; // docs:start:get-tokens export async function getToken(client) { const addresses = JSON.parse(readFileSync('addresses.json')); - return Contract.at(AztecAddress.fromString(addresses.token), TokenContractArtifact, client); + return Contract.at(AztecAddress.fromString(addresses.token), loadContractArtifact(TokenContractJson), client); } // docs:end:get-tokens diff --git a/yarn-project/end-to-end/src/sample-dapp/deploy.mjs b/yarn-project/end-to-end/src/sample-dapp/deploy.mjs index 35654abcac2..33e739c3166 100644 --- a/yarn-project/end-to-end/src/sample-dapp/deploy.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/deploy.mjs @@ -1,6 +1,6 @@ import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; -import { Contract, createPXEClient } from '@aztec/aztec.js'; -import { TokenContractArtifact } from '@aztec/noir-contracts/Token'; +import { Contract, createPXEClient, loadContractArtifact } from '@aztec/aztec.js'; +import TokenContractJson from '@aztec/noir-contracts/target/token_contract-Token' assert { type: 'json' }; import { writeFileSync } from 'fs'; import { fileURLToPath } from 'url'; @@ -13,6 +13,7 @@ async function main() { const [ownerWallet] = await getInitialTestAccountsWallets(pxe); const ownerAddress = ownerWallet.getCompleteAddress(); + const TokenContractArtifact = loadContractArtifact(TokenContractJson); const token = await Contract.deploy(ownerWallet, TokenContractArtifact, [ownerAddress, 'TokenName', 'TKN', 18]) .send() .deployed(); diff --git a/yarn-project/end-to-end/tsconfig.web.json b/yarn-project/end-to-end/tsconfig.web.json index 94b4537fe51..890902dced6 100644 --- a/yarn-project/end-to-end/tsconfig.web.json +++ b/yarn-project/end-to-end/tsconfig.web.json @@ -8,5 +8,5 @@ { "path": "../aztec.js" } - ], + ] } diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index 9216c9805e9..406ff37aa99 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -1,5 +1,7 @@ import { inflate } from 'pako'; +import { type FunctionSelector } from './selector.js'; + /** * A named type. */ @@ -164,6 +166,10 @@ export interface FunctionArtifact extends FunctionAbi { * The verification key of the function. */ verificationKey?: string; + /** + * Maps opcodes to source code pointers + */ + debugSymbols: string; } /** @@ -227,20 +233,6 @@ export type DebugFileMap = Record< } >; -/** - * The debug metadata of an ABI. - */ -export interface DebugMetadata { - /** - * The DebugInfo object, deflated as JSON, compressed using gzip and serialized with base64. - */ - debugSymbols: string[]; - /** - * The map of file ID to the source code and path of the file. - */ - fileMap: DebugFileMap; -} - /** * Defines artifact of a contract. */ @@ -265,10 +257,9 @@ export interface ContractArtifact { events: EventAbi[]; /** - * The debug metadata of the contract. - * It's used to include the relevant source code section when a constraint is not met during simulation. + * The map of file ID to the source code and path of the file. */ - debug?: DebugMetadata; + fileMap: DebugFileMap; } /** @@ -285,6 +276,47 @@ export interface FunctionDebugMetadata { files: DebugFileMap; } +/** A function artifact with optional debug metadata */ +export interface FunctionArtifactWithDebugMetadata extends FunctionArtifact { + /** Debug metadata for the function. */ + debug?: FunctionDebugMetadata; +} + +/** + * Gets a function artifact given its name or selector. + */ +export function getFunctionArtifact( + artifact: ContractArtifact, + functionNameOrSelector: string | FunctionSelector, +): FunctionArtifact { + const functionArtifact = artifact.functions.find(f => + typeof functionNameOrSelector === 'string' + ? f.name === functionNameOrSelector + : functionNameOrSelector.equals(f.name, f.parameters), + ); + if (!functionArtifact) { + throw new Error(`Unknown function ${functionNameOrSelector}`); + } + return functionArtifact; +} + +/** @deprecated Use getFunctionArtifact instead */ +export function getFunctionArtifactWithSelector(artifact: ContractArtifact, selector: FunctionSelector) { + return getFunctionArtifact(artifact, selector); +} + +/** + * Gets a function artifact including debug metadata given its name or selector. + */ +export function getFunctionArtifactWithDebugMetadata( + artifact: ContractArtifact, + functionNameOrSelector: string | FunctionSelector, +): FunctionArtifactWithDebugMetadata { + const functionArtifact = getFunctionArtifact(artifact, functionNameOrSelector); + const debugMetadata = getFunctionDebugMetadata(artifact, functionArtifact); + return { ...functionArtifact, debug: debugMetadata }; +} + /** * Gets the debug metadata of a given function from the contract artifact * @param artifact - The contract build artifact @@ -292,19 +324,14 @@ export interface FunctionDebugMetadata { * @returns The debug metadata of the function */ export function getFunctionDebugMetadata( - artifact: ContractArtifact, - functionName: string, + contractArtifact: ContractArtifact, + functionArtifact: FunctionArtifact, ): FunctionDebugMetadata | undefined { - const functionIndex = artifact.functions.findIndex(f => f.name === functionName); - if (artifact.debug && functionIndex !== -1) { + if (functionArtifact.debugSymbols && contractArtifact.fileMap) { const debugSymbols = JSON.parse( - inflate(Buffer.from(artifact.debug.debugSymbols[functionIndex], 'base64'), { to: 'string' }), + inflate(Buffer.from(functionArtifact.debugSymbols, 'base64'), { to: 'string', raw: true }), ); - const files = artifact.debug.fileMap; - return { - debugSymbols, - files, - }; + return { debugSymbols, files: contractArtifact.fileMap }; } return undefined; } diff --git a/yarn-project/foundation/src/abi/selector.ts b/yarn-project/foundation/src/abi/selector.ts index 0c631418b29..ff70d868f03 100644 --- a/yarn-project/foundation/src/abi/selector.ts +++ b/yarn-project/foundation/src/abi/selector.ts @@ -3,7 +3,7 @@ import { BufferReader } from '@aztec/foundation/serialize'; import { keccak } from '../crypto/keccak/index.js'; import { Fr } from '../fields/index.js'; -import { ABIParameter } from './abi.js'; +import { type ABIParameter } from './abi.js'; import { decodeFunctionSignature } from './decoder.js'; /* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ @@ -71,6 +71,19 @@ export interface FunctionSelector { /** A function selector is the first 4 bytes of the hash of a function signature. */ export class FunctionSelector extends Selector { + /** + * Checks if this function selector is equal to another. + * @returns True if the function selectors are equal. + */ + equals(otherName: string, otherParams: ABIParameter[]): boolean; + equals(other: FunctionSelector): boolean; + equals(other: FunctionSelector | string, otherParams?: ABIParameter[]): boolean { + if (typeof other === 'string') { + return this.equals(FunctionSelector.fromNameAndParameters(other, otherParams!)); + } + return this.value === other.value; + } + /** * Deserializes from a buffer or reader, corresponding to a write in cpp. * @param buffer - Buffer or BufferReader to read from. diff --git a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts index a7cb59263ee..37713f3da94 100644 --- a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts +++ b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts @@ -77,7 +77,7 @@ export function makeFetch(retries: number[], noRetry: boolean, log?: DebugLogger return async (host: string, rpcMethod: string, body: any, useApiEndpoints: boolean) => { return await retry( () => defaultFetch(host, rpcMethod, body, useApiEndpoints, noRetry), - 'JsonRpcClient request', + `JsonRpcClient request to ${host}`, makeBackoff(retries), log, true, diff --git a/yarn-project/noir-compiler/package.json b/yarn-project/noir-compiler/package.json index af5b492ecfe..71c96a4db41 100644 --- a/yarn-project/noir-compiler/package.json +++ b/yarn-project/noir-compiler/package.json @@ -47,6 +47,7 @@ "dependencies": { "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", + "@aztec/types": "workspace:^", "@iarna/toml": "^2.2.5", "@noir-lang/noir_wasm": "portal:../../noir/packages/noir_wasm", "base64-js": "^1.5.1", diff --git a/yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts b/yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts index 4c1dd616c20..ab40a9b78e8 100644 --- a/yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts +++ b/yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts @@ -18,15 +18,14 @@ export function addCodegenCommanderAction(program: Command, _: LogFn = () => {}) .command('codegen') .argument('', 'Path to the Noir ABI or project dir.') .option('-o, --outdir ', 'Output folder for the generated code.') - .option('-d, --debug', 'Include debug info.') .option('--ts', 'Generate TypeScript wrapper.') .option('--nr', 'Generate Noir interface.') .description('Validates and generates an Aztec Contract ABI from Noir ABI.') - .action(async (noirAbiPath: string, { debug, outdir, ts, nr }) => { + .action(async (noirAbiPath: string, { outdir, ts, nr }) => { if (ts && nr) { throw new Error('--ts and --nr are mutually exclusive.'); } const { generateCode } = await import('./codegen.js'); - generateCode(outdir || dirname(noirAbiPath), noirAbiPath, debug, ts, nr); + generateCode(outdir || dirname(noirAbiPath), noirAbiPath, { ts, nr }); }); } diff --git a/yarn-project/noir-compiler/src/cli/codegen.ts b/yarn-project/noir-compiler/src/cli/codegen.ts index 98eb5670d42..19c90f0a551 100644 --- a/yarn-project/noir-compiler/src/cli/codegen.ts +++ b/yarn-project/noir-compiler/src/cli/codegen.ts @@ -1,35 +1,38 @@ -import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs'; +import { loadContractArtifact } from '@aztec/types/abi'; + +import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs'; import path from 'path'; -import { generateContractArtifact } from '../contract-interface-gen/abi.js'; -import { generateTypescriptContractInterface } from '../contract-interface-gen/contractTypescript.js'; import { generateNoirContractInterface } from '../contract-interface-gen/noir.js'; +import { generateTypescriptContractInterface } from '../contract-interface-gen/typescript.js'; + +/** Generate code options */ +type GenerateCodeOptions = { /** Typescript */ ts?: boolean; /** Noir */ nr?: boolean }; /** - * + * Generates Noir interface or Typescript interface for a folder or single file from a Noir compilation artifact. */ -export function generateCode(outputPath: string, fileOrDirPath: string, includeDebug = false, ts = false, nr = false) { +export function generateCode(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOptions = {}) { const stats = statSync(fileOrDirPath); if (stats.isDirectory()) { const files = readdirSync(fileOrDirPath).filter(file => file.endsWith('.json') && !file.startsWith('debug_')); for (const file of files) { const fullPath = path.join(fileOrDirPath, file); - generateFromNoirAbi(outputPath, fullPath, includeDebug, ts, nr); + generateFromNoirAbi(outputPath, fullPath, opts); } } else if (stats.isFile()) { - generateFromNoirAbi(outputPath, fileOrDirPath, includeDebug, ts, nr); + generateFromNoirAbi(outputPath, fileOrDirPath, opts); } } /** - * + * Generates Noir interface or Typescript interface for a single file Noir compilation artifact. */ -function generateFromNoirAbi(outputPath: string, noirAbiPath: string, includeDebug: boolean, ts: boolean, nr: boolean) { +function generateFromNoirAbi(outputPath: string, noirAbiPath: string, opts: GenerateCodeOptions = {}) { const contract = JSON.parse(readFileSync(noirAbiPath, 'utf8')); - const noirDebugPath = includeDebug ? getDebugFilePath(noirAbiPath) : undefined; - const debug = noirDebugPath ? JSON.parse(readFileSync(noirDebugPath, 'utf8')) : undefined; - const aztecAbi = generateContractArtifact({ contract, debug }); + const aztecAbi = loadContractArtifact(contract); + const { nr, ts } = opts; mkdirSync(outputPath, { recursive: true }); @@ -39,20 +42,14 @@ function generateFromNoirAbi(outputPath: string, noirAbiPath: string, includeDeb return; } - writeFileSync(`${outputPath}/${aztecAbi.name}.json`, JSON.stringify(aztecAbi, undefined, 2)); - if (ts) { - const tsWrapper = generateTypescriptContractInterface(aztecAbi, `./${aztecAbi.name}.json`); + let relativeArtifactPath = path.relative(outputPath, noirAbiPath); + if (relativeArtifactPath === path.basename(noirAbiPath)) { + // Prepend ./ for local import if the folder is the same + relativeArtifactPath = `./${relativeArtifactPath}`; + } + + const tsWrapper = generateTypescriptContractInterface(aztecAbi, relativeArtifactPath); writeFileSync(`${outputPath}/${aztecAbi.name}.ts`, tsWrapper); } } - -/** - * - */ -function getDebugFilePath(filePath: string) { - const dirname = path.dirname(filePath); - const basename = path.basename(filePath); - const result = path.join(dirname, 'debug_' + basename); - return existsSync(result) ? result : undefined; -} diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts b/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts deleted file mode 100644 index 47473cdefa8..00000000000 --- a/yarn-project/noir-compiler/src/contract-interface-gen/abi.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { FUNCTION_TREE_HEIGHT } from '@aztec/circuits.js/constants'; -import { ContractArtifact, DebugMetadata, FunctionArtifact, FunctionType } from '@aztec/foundation/abi'; - -import { deflate } from 'pako'; - -import { mockVerificationKey } from '../mocked_keys.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. - * @param fn - Noir function entry. - * @returns Function artifact. - */ -function generateFunctionArtifact(fn: NoirFunctionEntry): FunctionArtifact { - const functionType = fn.function_type.toLowerCase() as FunctionType; - const isInternal = fn.is_internal; - - // If the function is not unconstrained, the first item is inputs or CallContext which we should omit - let parameters = fn.abi.parameters; - if (functionType !== FunctionType.UNCONSTRAINED) { - parameters = parameters.slice(1); - } - - // If the function is secret, the return is the public inputs, which should be omitted - const returnTypes = functionType === FunctionType.SECRET ? [] : [fn.abi.return_type.abi_type]; - - return { - name: fn.name, - functionType, - isInternal, - parameters, - returnTypes, - bytecode: fn.bytecode, - verificationKey: mockVerificationKey, - }; -} - -/** - * 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, - 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 }: NoirContractCompilationArtifacts, - aztecNrVersion?: string, -): ContractArtifact { - const constructorArtifact = contract.functions.find(({ name }) => name === 'constructor'); - if (constructorArtifact === undefined) { - throw new Error('Contract must have a constructor function'); - } - if (contract.functions.length > 2 ** FUNCTION_TREE_HEIGHT) { - throw new Error(`Contract can only have a maximum of ${2 ** FUNCTION_TREE_HEIGHT} functions`); - } - const originalFunctions = contract.functions; - // TODO why sort? we should have idempotent compilation so this should not be needed. - const sortedFunctions = [...contract.functions].sort((fnA, fnB) => fnA.name.localeCompare(fnB.name)); - let parsedDebug: DebugMetadata | undefined = undefined; - - if (debug) { - parsedDebug = { - debugSymbols: sortedFunctions.map(fn => { - const originalIndex = originalFunctions.indexOf(fn); - return Buffer.from(deflate(JSON.stringify(debug.debug_symbols[originalIndex]))).toString('base64'); - }), - fileMap: debug.file_map, - }; - } - - return { - name: contract.name, - functions: sortedFunctions.map(generateFunctionArtifact), - events: contract.events, - debug: parsedDebug, - aztecNrVersion, - }; -} diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts b/yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts deleted file mode 100644 index 7961344b522..00000000000 --- a/yarn-project/noir-compiler/src/contract-interface-gen/programTypescript.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { ABIType } from '@aztec/foundation/abi'; -import { NoirFunctionAbi } from '@aztec/noir-compiler'; - -/** - * Keep track off all of the Noir primitive types that were used. - * Most of these will not have a 1-1 definition in TypeScript, - * so we will need to generate type aliases for them. - * - * We want to generate type aliases - * for specific types that are used in the ABI. - * - * For example: - * - If `Field` is used we want to alias that - * with `number`. - * - If `u32` is used we want to alias that with `number` too. - */ -type PrimitiveTypesUsed = { - /** - * The name of the type alias that we will generate. - */ - aliasName: string; - /** - * The TypeScript type that we will alias to. - */ - tsType: string; -}; - -const noirPrimitiveTypesToTsTypes = new Map(); - -/** - * Typescript does not allow us to check for equality of non-primitive types - * easily, so we create a addIfUnique function that will only add an item - * to the map if it is not already there by using JSON.stringify. - * @param item - The item to add to the map. - */ -function addIfUnique(item: PrimitiveTypesUsed) { - const key = JSON.stringify(item); - if (!noirPrimitiveTypesToTsTypes.has(key)) { - noirPrimitiveTypesToTsTypes.set(key, item); - } -} - -/** - * Converts an ABI type to a TypeScript type. - * @param type - The ABI type to convert. - * @returns The typescript code to define the type. - */ -function abiTypeToTs(type: ABIType): string { - switch (type.kind) { - case 'integer': { - let tsIntType = ''; - if (type.sign === 'signed') { - tsIntType = `i${type.width}`; - } else { - tsIntType = `u${type.width}`; - } - addIfUnique({ aliasName: tsIntType, tsType: 'string' }); - return tsIntType; - } - case 'boolean': - return `boolean`; - case 'array': - return `FixedLengthArray<${abiTypeToTs(type.type)}, ${type.length}>`; - case 'struct': - return getLastComponentOfPath(type.path); - case 'field': - addIfUnique({ aliasName: 'Field', tsType: 'string' }); - return 'Field'; - default: - throw new Error(`Unknown ABI type ${type}`); - } -} - -/** - * Returns the last component of a path, e.g. "foo::bar::baz" -\> "baz" - * Note: that if we have a path such as "Baz", we will return "Baz". - * - * Since these paths corresponds to structs, we can assume that we - * cannot have "foo::bar::". - * - * We also make the assumption that since these paths are coming from - * Noir, then we will not have two paths that look like this: - * - foo::bar::Baz - * - cat::dog::Baz - * ie the last component of the path (struct name) is enough to uniquely identify - * the whole path. - * - * TODO: We should double check this assumption when we use type aliases, - * I expect that `foo::bar::Baz as Dog` would effectively give `foo::bar::Dog` - * @param str - The path to get the last component of. - * @returns The last component of the path. - */ -function getLastComponentOfPath(str: string): string { - const parts = str.split('::'); - const lastPart = parts[parts.length - 1]; - return lastPart; -} - -/** - * Generates TypeScript interfaces for the structs used in the ABI. - * @param type - The ABI type to generate the interface for. - * @param output - The set of structs that we have already generated bindings for. - * @returns The TypeScript code to define the struct. - */ -function generateStructInterfaces(type: ABIType, output: Set): string { - let result = ''; - - // Edge case to handle the array of structs case. - if (type.kind === 'array' && type.type.kind === 'struct' && !output.has(getLastComponentOfPath(type.type.path))) { - result += generateStructInterfaces(type.type, output); - } - if (type.kind !== 'struct') { - return result; - } - - // List of structs encountered while viewing this type that we need to generate - // bindings for. - const typesEncountered = new Set(); - - // Codegen the struct and then its fields, so that the structs fields - // are defined before the struct itself. - let codeGeneratedStruct = ''; - let codeGeneratedStructFields = ''; - - const structName = getLastComponentOfPath(type.path); - if (!output.has(structName)) { - codeGeneratedStruct += `export interface ${structName} {\n`; - for (const field of type.fields) { - codeGeneratedStruct += ` ${field.name}: ${abiTypeToTs(field.type)};\n`; - typesEncountered.add(field.type); - } - codeGeneratedStruct += `}\n\n`; - output.add(structName); - - // Generate code for the encountered structs in the field above - for (const type of typesEncountered) { - codeGeneratedStructFields += generateStructInterfaces(type, output); - } - } - - return codeGeneratedStructFields + '\n' + codeGeneratedStruct; -} - -/** - * Generates a TypeScript interface for the ABI. - * @param abiObj - The ABI to generate the interface for. - * @returns The TypeScript code to define the interface. - */ -export function generateTypescriptProgramInterface(abiObj: NoirFunctionAbi): string { - let result = ``; - const outputStructs = new Set(); - - // Define structs for composite types - for (const param of abiObj.parameters) { - result += generateStructInterfaces(param.type, outputStructs); - } - - // Generating Return type, if it exists - // - if (abiObj.return_type != null) { - result += generateStructInterfaces(abiObj.return_type.abi_type, outputStructs); - result += `export type ReturnType = ${abiTypeToTs(abiObj.return_type.abi_type)};\n`; - } - - // Generating Input type - result += '\nexport interface InputType {\n'; - for (const param of abiObj.parameters) { - result += ` ${param.name}: ${abiTypeToTs(param.type)};\n`; - } - result += '}'; - - // Add the primitive Noir types that do not have a 1-1 mapping to TypeScript. - let primitiveTypeAliases = ''; - for (const [, value] of noirPrimitiveTypesToTsTypes) { - primitiveTypeAliases += `\nexport type ${value.aliasName} = ${value.tsType};`; - } - - const fixedLengthArray = - '\nexport type FixedLengthArray = L extends 0 ? never[]: T[] & { length: L }'; - - return ( - `/* Autogenerated file, do not edit! */\n\n/* eslint-disable */\n` + - fixedLengthArray + - '\n' + - primitiveTypeAliases + - '\n' + - result - ); -} diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts similarity index 97% rename from yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts rename to yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts index 7f37f9e4923..c17d0d7abb4 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts @@ -154,7 +154,7 @@ function generateArtifactGetter(name: string) { function generateAbiStatement(name: string, artifactImportPath: string) { const stmts = [ `import ${name}ContractArtifactJson from '${artifactImportPath}' assert { type: 'json' };`, - `export const ${name}ContractArtifact = ${name}ContractArtifactJson as ContractArtifact;`, + `export const ${name}ContractArtifact = loadContractArtifact(${name}ContractArtifactJson as NoirCompiledContract);`, ]; return stmts.join('\n'); } @@ -192,6 +192,8 @@ import { FieldLike, Fr, FunctionSelectorLike, + loadContractArtifact, + NoirCompiledContract, Point, PublicKey, Wallet, diff --git a/yarn-project/noir-compiler/src/index.ts b/yarn-project/noir-compiler/src/index.ts index 836c2e8b2c9..88416ef4282 100644 --- a/yarn-project/noir-compiler/src/index.ts +++ b/yarn-project/noir-compiler/src/index.ts @@ -1,8 +1,4 @@ export * from './versions.js'; -export { generateTypescriptContractInterface } from './contract-interface-gen/contractTypescript.js'; +export { generateTypescriptContractInterface } from './contract-interface-gen/typescript.js'; export { generateNoirContractInterface } from './contract-interface-gen/noir.js'; -export { generateTypescriptProgramInterface } from './contract-interface-gen/programTypescript.js'; -export { generateContractArtifact } from './contract-interface-gen/abi.js'; - -export * from './noir_artifact.js'; diff --git a/yarn-project/noir-compiler/tsconfig.json b/yarn-project/noir-compiler/tsconfig.json index a6b3ad94790..00bc12a18a0 100644 --- a/yarn-project/noir-compiler/tsconfig.json +++ b/yarn-project/noir-compiler/tsconfig.json @@ -11,6 +11,9 @@ }, { "path": "../foundation" + }, + { + "path": "../types" } ], "include": ["src", "src/*.json"] diff --git a/yarn-project/noir-contracts/bootstrap.sh b/yarn-project/noir-contracts/bootstrap.sh deleted file mode 100755 index 43284ba2a79..00000000000 --- a/yarn-project/noir-contracts/bootstrap.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -yarn noir:build:all diff --git a/yarn-project/noir-contracts/package.json b/yarn-project/noir-contracts/package.json index cd81311cf34..6845b1f90ff 100644 --- a/yarn-project/noir-contracts/package.json +++ b/yarn-project/noir-contracts/package.json @@ -3,8 +3,9 @@ "version": "0.1.0", "type": "module", "exports": { - ".": "./dest/index.js", - "./*": "./dest/*.js" + ".": "./dest/src/index.js", + "./target/*": "./dest/target/*.json", + "./*": "./dest/src/*.js" }, "scripts": { "build": "yarn clean && yarn build:contracts && tsc -b", diff --git a/yarn-project/noir-contracts/scripts/generate-types.sh b/yarn-project/noir-contracts/scripts/generate-types.sh index 6901ab97f40..8ec4b7ebc4c 100755 --- a/yarn-project/noir-contracts/scripts/generate-types.sh +++ b/yarn-project/noir-contracts/scripts/generate-types.sh @@ -7,14 +7,13 @@ INDEX="$OUT_DIR/index.ts" rm -rf $OUT_DIR && mkdir -p $OUT_DIR # Generate index.ts header. -echo "// Auto generated module - do not edit!\n" > $INDEX +echo "// Auto generated module - do not edit!" > $INDEX for ABI in $(find target -maxdepth 1 -type f ! -name 'debug_*' -name '*.json'); do CONTRACT=$(jq -r .name $ABI) - DEBUG_INFO="$(dirname $ABI)/debug_$(basename $ABI)" echo "Creating types for $CONTRACT in $ABI..." - node --no-warnings ../noir-compiler/dest/cli.js codegen -o $OUT_DIR -d --ts $ABI + node --no-warnings ../noir-compiler/dest/cli.js codegen -o $OUT_DIR --ts $ABI # Add contract import/export to index.ts. echo "export * from './${CONTRACT}.js';" >> $INDEX @@ -22,3 +21,4 @@ done echo "Formatting..." yarn formatting:fix + diff --git a/yarn-project/noir-contracts/scripts/transform_json_abi.sh b/yarn-project/noir-contracts/scripts/transform_json_abi.sh deleted file mode 100755 index bc27163ee6f..00000000000 --- a/yarn-project/noir-contracts/scripts/transform_json_abi.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# Sadly, yarn-project expects ABI files in a format different to what is output from nargo. -# This provides a tool to quickly do the transform with jq. -# It's not currently used as the transform is done in TypeScript when we generate the contract wrapper. -# TODO: Why don't our contract classes just work with noir abis? -# -# Lowercase function_type value. -# Camel case function_type and is_internal. -# Discard first parameter (input) if function is not unconstrained. -# Hoist parameters out of abi. -# Hoist return_type out of abi, make an array of 1 element, or empty array if function is secret. -# -jq ' - .functions |= map( - (.functionType = (.function_type | ascii_downcase)) | - (.isInternal = .is_internal) | - del(.function_type, .is_internal) | - (.parameters = if .functionType == "unconstrained" then .abi.parameters else .abi.parameters[1:] end) | - (.returnTypes = if .functionType == "secret" then [] else [ .abi.return_type.abi_type ] end) | - del(.abi) - ) -' $1 diff --git a/yarn-project/noir-contracts/tsconfig.json b/yarn-project/noir-contracts/tsconfig.json index a82cec59baa..bf3da7b0006 100644 --- a/yarn-project/noir-contracts/tsconfig.json +++ b/yarn-project/noir-contracts/tsconfig.json @@ -2,7 +2,7 @@ "extends": "..", "compilerOptions": { "outDir": "dest", - "rootDir": "src", + "rootDir": ".", "tsBuildInfoFile": ".tsbuildinfo" }, "references": [ @@ -12,9 +12,7 @@ ], "include": [ "src", - "src/**/*.json" + "target", + "target/*.json", ], - "exclude": [ - "src/contracts" - ] } diff --git a/yarn-project/noir-protocol-circuits/package.json b/yarn-project/noir-protocol-circuits/package.json index 1a1bd96cb30..358e89ccc14 100644 --- a/yarn-project/noir-protocol-circuits/package.json +++ b/yarn-project/noir-protocol-circuits/package.json @@ -7,12 +7,12 @@ "./types": "./dest/types/index.js" }, "scripts": { - "build": "yarn clean && yarn noir:build && yarn noir:types", + "build": "yarn clean && yarn noir:build && yarn noir:types && tsc -b", "clean": "rm -rf ./dest .tsbuildinfo src/types src/target", "formatting": "run -T prettier --check ./src && run -T eslint ./src", "formatting:fix": "NODE_OPTIONS='--max-old-space-size=8096' run -T eslint --fix ./src && run -T prettier -w ./src", "formatting:fix:types": "NODE_OPTIONS='--max-old-space-size=8096' run -T eslint --fix ./src/types && run -T prettier -w ./src/types", - "noir:build": "cd src && ../../../noir/target/release/nargo compile --silence-warnings && rm -rf ./target/debug_*", + "noir:build": "cd src && ../../../noir/target/release/nargo compile --silence-warnings", "noir:types": "node --loader ts-node/esm src/scripts/generate_ts_from_abi.ts && yarn formatting:fix:types", "noir:test": "cd src && ../../../noir/target/release/nargo test", "test": "yarn test:js && yarn noir:test", @@ -30,6 +30,7 @@ "@aztec/circuits.js": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/noir-compiler": "workspace:^", + "@aztec/types": "workspace:^", "@noir-lang/acvm_js": "portal:../../noir/packages/acvm_js", "@noir-lang/backend_barretenberg": "portal:../../noir/packages/backend_barretenberg", "@noir-lang/noir_js": "portal:../../noir/packages/noir_js", diff --git a/yarn-project/noir-protocol-circuits/src/index.ts b/yarn-project/noir-protocol-circuits/src/index.ts index 89954211e99..05b346011be 100644 --- a/yarn-project/noir-protocol-circuits/src/index.ts +++ b/yarn-project/noir-protocol-circuits/src/index.ts @@ -11,7 +11,7 @@ import { RootRollupInputs, RootRollupPublicInputs, } from '@aztec/circuits.js'; -import { NoirCompiledCircuit } from '@aztec/noir-compiler'; +import { NoirCompiledCircuit } from '@aztec/types/noir'; import { WasmBlackBoxFunctionSolver, createBlackBoxSolver, executeCircuitWithBlackBoxSolver } from '@noir-lang/acvm_js'; import { Abi, abiDecode, abiEncode } from '@noir-lang/noirc_abi'; diff --git a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts b/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts index b3c5d16482a..6fc683cc162 100644 --- a/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts +++ b/yarn-project/noir-protocol-circuits/src/scripts/generate_ts_from_abi.ts @@ -1,6 +1,6 @@ import { ABIType } from '@aztec/foundation/abi'; import { createConsoleLogger } from '@aztec/foundation/log'; -import { NoirCompiledCircuit, NoirFunctionAbi } from '@aztec/noir-compiler'; +import { NoirCompiledCircuit, NoirFunctionAbi } from '@aztec/types/noir'; import fs from 'fs/promises'; diff --git a/yarn-project/noir-protocol-circuits/tsconfig.json b/yarn-project/noir-protocol-circuits/tsconfig.json index 4c2a3c5e245..917d509e475 100644 --- a/yarn-project/noir-protocol-circuits/tsconfig.json +++ b/yarn-project/noir-protocol-circuits/tsconfig.json @@ -15,6 +15,9 @@ { "path": "../noir-compiler" }, + { + "path": "../types" + }, { "path": "../circuit-types" }, diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 7af6ca539d7..d33010931b1 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -1,4 +1,4 @@ -import { DBOracle, FunctionArtifactWithDebugMetadata, MessageLoadOracleInputs } from '@aztec/acir-simulator'; +import { DBOracle, MessageLoadOracleInputs } from '@aztec/acir-simulator'; import { KeyStore, L2Block, @@ -17,6 +17,7 @@ import { GrumpkinPrivateKey, PublicKey, } from '@aztec/circuits.js'; +import { FunctionArtifactWithDebugMetadata } from '@aztec/foundation/abi'; import { createDebugLogger } from '@aztec/foundation/log'; import { ContractDataOracle } from '../contract_data_oracle/index.js'; diff --git a/yarn-project/types/.eslintrc.cjs b/yarn-project/types/.eslintrc.cjs index 30064fcb134..e659927475c 100644 --- a/yarn-project/types/.eslintrc.cjs +++ b/yarn-project/types/.eslintrc.cjs @@ -1 +1 @@ -module.exports = require('@aztec/foundation/eslint'); \ No newline at end of file +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/types/package.json b/yarn-project/types/package.json index 2ebd3ab048d..f8e42023289 100644 --- a/yarn-project/types/package.json +++ b/yarn-project/types/package.json @@ -6,8 +6,10 @@ "main": "./dest/index.js", "types": "./dest/index.d.ts", "exports": { + "./abi": "./dest/abi/index.js", "./interfaces": "./dest/interfaces/index.js", - "./membership": "./dest/sibling-path/index.js" + "./membership": "./dest/sibling-path/index.js", + "./noir": "./dest/noir/index.js" }, "scripts": { "build": "yarn clean && tsc -b", diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts new file mode 100644 index 00000000000..1df2e3dbac1 --- /dev/null +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -0,0 +1,130 @@ +import { + ABIParameter, + ABIParameterVisibility, + ABIType, + ContractArtifact, + FunctionArtifact, + FunctionType, +} from '@aztec/foundation/abi'; + +import { NoirCompiledContract } from '../noir/index.js'; +import { mockVerificationKey } from './mocked_keys.js'; + +/** + * Gets nargo build output and returns a valid contract artifact instance. + * @param input - Input object as generated by nargo compile. + * @returns A valid contract artifact instance. + */ +export function loadContractArtifact(input: NoirCompiledContract): ContractArtifact { + if (isContractArtifact(input)) { + return input; + } + const contractArtifact = generateContractArtifact(input); + validateContractArtifact(contractArtifact); + return contractArtifact; +} + +/** + * Checks if the given input looks like a valid ContractArtifact. 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 ContractArtifact. + */ +function isContractArtifact(input: any): input is ContractArtifact { + if (typeof input !== 'object') { + return false; + } + const maybeContractArtifact = input as ContractArtifact; + if (typeof maybeContractArtifact.name !== 'string') { + return false; + } + if (!Array.isArray(maybeContractArtifact.functions)) { + return false; + } + for (const fn of maybeContractArtifact.functions) { + if (typeof fn.name !== 'string') { + return false; + } + if (typeof fn.functionType !== 'string') { + return false; + } + } + return true; +} + +/** Parameter in a function from a noir contract compilation artifact */ +type NoirCompiledContractFunctionParameter = NoirCompiledContractFunction['abi']['parameters'][number]; + +/** + * Generates a function parameter out of one generated by a nargo build. + * @param param - Noir parameter. + * @returns A function parameter. + */ +function generateFunctionParameter(param: NoirCompiledContractFunctionParameter): ABIParameter { + const { visibility } = param; + if ((visibility as string) === 'databus') { + throw new Error(`Unsupported visibility ${param.visibility} for noir contract function parameter ${param.name}.`); + } + return { ...param, visibility: visibility as ABIParameterVisibility }; +} + +/** Function from a noir contract compilation artifact */ +type NoirCompiledContractFunction = NoirCompiledContract['functions'][number]; + +/** + * Generates a function build artifact. Replaces verification key with a mock value. + * @param fn - Noir function entry. + * @returns Function artifact. + */ +function generateFunctionArtifact(fn: NoirCompiledContractFunction): FunctionArtifact { + const functionType = fn.function_type.toLowerCase() as FunctionType; + const isInternal = fn.is_internal; + + // If the function is not unconstrained, the first item is inputs or CallContext which we should omit + let parameters = fn.abi.parameters.map(generateFunctionParameter); + if (functionType !== 'unconstrained') { + parameters = parameters.slice(1); + } + + // If the function is secret, the return is the public inputs, which should be omitted + let returnTypes: ABIType[] = []; + if (functionType !== 'secret' && fn.abi.return_type) { + returnTypes = [fn.abi.return_type.abi_type]; + } + + return { + name: fn.name, + functionType, + isInternal, + parameters, + returnTypes, + bytecode: fn.bytecode, + verificationKey: mockVerificationKey, + debugSymbols: fn.debug_symbols, + }; +} + +/** Validates contract artifact instance, throwing on error. */ +function validateContractArtifact(contract: ContractArtifact) { + const constructorArtifact = contract.functions.find(({ name }) => name === 'constructor'); + if (constructorArtifact === undefined) { + throw new Error('Contract must have a constructor function'); + } + return contract; +} + +/** + * Given a Nargo output generates an Aztec-compatible contract artifact. + * @param compiled - Noir build output. + * @returns Aztec contract build artifact. + */ +function generateContractArtifact(contract: NoirCompiledContract, aztecNrVersion?: string): ContractArtifact { + return { + name: contract.name, + functions: contract.functions.map(generateFunctionArtifact), + events: contract.events, + fileMap: contract.file_map, + aztecNrVersion, + }; +} diff --git a/yarn-project/types/src/abi/index.ts b/yarn-project/types/src/abi/index.ts new file mode 100644 index 00000000000..27174adec05 --- /dev/null +++ b/yarn-project/types/src/abi/index.ts @@ -0,0 +1 @@ +export * from './contract_artifact.js'; diff --git a/yarn-project/types/src/abi/mocked_keys.ts b/yarn-project/types/src/abi/mocked_keys.ts new file mode 100644 index 00000000000..b8f5c677ead --- /dev/null +++ b/yarn-project/types/src/abi/mocked_keys.ts @@ -0,0 +1,2 @@ +export const mockVerificationKey = + '0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f'; diff --git a/yarn-project/noir-compiler/src/noir_artifact.ts b/yarn-project/types/src/noir/index.ts similarity index 77% rename from yarn-project/noir-compiler/src/noir_artifact.ts rename to yarn-project/types/src/noir/index.ts index f8702b3ca0f..65d7c88cb29 100644 --- a/yarn-project/noir-compiler/src/noir_artifact.ts +++ b/yarn-project/types/src/noir/index.ts @@ -10,20 +10,15 @@ import { /** The Aztec.nr function types. */ type NoirFunctionType = 'Open' | 'Secret' | 'Unconstrained'; +/** The witness indices of the parameters. */ +type ParamWitnessIndices = { /** Start */ start: number; /** End */ end: number }; + /** The ABI of an Aztec.nr function. */ export interface NoirFunctionAbi { /** The parameters of the function. */ parameters: ABIParameter[]; /** The witness indices of the parameters. Indexed by parameter name. */ - param_witnesses: { - /** input */ - input: { - /** start */ - start: number; - /** end */ - end: number; - }[]; - }; + param_witnesses: { [key: string]: undefined | ParamWitnessIndices[] }; /** The return type of the function. */ return_type: { /** @@ -54,9 +49,11 @@ export interface NoirFunctionEntry { /** The bytecode of the function in base64. */ bytecode: string; /** The proving key. */ - proving_key: string; + proving_key?: string; /** The verification key. */ - verification_key: string; + verification_key?: string; + /** The debug information, compressed and base64 encoded. */ + debug_symbols: string; } /** @@ -69,6 +66,8 @@ export interface NoirCompiledContract { functions: NoirFunctionEntry[]; /** The events of the contract */ events: EventAbi[]; + /** The map of file ID to the source code and path of the file. */ + file_map: DebugFileMap; } /** @@ -83,35 +82,10 @@ export interface NoirCompiledCircuit { abi: NoirFunctionAbi; /** The bytecode of the circuit in base64. */ 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 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 information, compressed and base64 encoded. */ + debug_symbols: string; + /** The map of file ID to the source code and path of the file. */ + file_map: DebugFileMap; } /** @@ -136,11 +110,6 @@ export interface NoirContractCompilationArtifacts { * The compiled contract. */ contract: NoirCompiledContract; - - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: NoirDebugMetadata; } /** @@ -155,11 +124,6 @@ export interface NoirProgramCompilationArtifacts { * The compiled contract. */ program: NoirCompiledCircuit; - - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: NoirDebugMetadata; } /** diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index c5e752f4d2f..54e7444f69f 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -608,6 +608,7 @@ __metadata: dependencies: "@aztec/circuits.js": "workspace:^" "@aztec/foundation": "workspace:^" + "@aztec/types": "workspace:^" "@iarna/toml": ^2.2.5 "@jest/globals": ^29.5.0 "@noir-lang/noir_wasm": "portal:../../noir/packages/noir_wasm" @@ -663,6 +664,7 @@ __metadata: "@aztec/foundation": "workspace:^" "@aztec/merkle-tree": "workspace:^" "@aztec/noir-compiler": "workspace:^" + "@aztec/types": "workspace:^" "@jest/globals": ^29.5.0 "@noir-lang/acvm_js": "portal:../../noir/packages/acvm_js" "@noir-lang/backend_barretenberg": "portal:../../noir/packages/backend_barretenberg" @@ -2815,6 +2817,8 @@ __metadata: "@noir-lang/noir_wasm@portal:../noir/packages/noir_wasm::locator=%40aztec%2Faztec3-packages%40workspace%3A.": version: 0.0.0-use.local resolution: "@noir-lang/noir_wasm@portal:../noir/packages/noir_wasm::locator=%40aztec%2Faztec3-packages%40workspace%3A." + dependencies: + pako: ^2.1.0 languageName: node linkType: soft