From 0fb3262b0575059dc52f5e8827324f2e02a1e806 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 9 Jan 2024 10:19:49 -0300 Subject: [PATCH 1/7] WIP: yarn-project changes --- yarn-project/foundation/src/abi/abi.ts | 10 +- yarn-project/foundation/src/noir/index.ts | 1 + .../foundation/src/noir/noir_artifact.ts | 95 ++++++++++++ yarn-project/noir-compiler/src/cli/codegen.ts | 1 + .../types/src/abi/contract_artifact.ts | 146 ++++++++++++++++++ yarn-project/types/src/abi/index.ts | 1 + yarn-project/types/src/abi/mocked_keys.ts | 2 + 7 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 yarn-project/foundation/src/noir/noir_artifact.ts 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 diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index 9216c9805e92..5eda291ef1cc 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -1,5 +1,6 @@ import { inflate } from 'pako'; + /** * A named type. */ @@ -17,11 +18,8 @@ export interface ABIVariable { /** * Indicates whether a parameter is public or secret/private. */ -export enum ABIParameterVisibility { - PUBLIC = 'public', - SECRET = 'secret', -} - +export type ABIParameterVisibility = 'public' | 'private'; + /** * A function parameter. */ @@ -307,4 +305,4 @@ export function getFunctionDebugMetadata( }; } return undefined; -} +} \ No newline at end of file diff --git a/yarn-project/foundation/src/noir/index.ts b/yarn-project/foundation/src/noir/index.ts index e91eaa5cfecc..dbfc63548d51 100644 --- a/yarn-project/foundation/src/noir/index.ts +++ b/yarn-project/foundation/src/noir/index.ts @@ -1 +1,2 @@ export * from './noir_package_config.js'; +export * from './noir_artifact.js'; \ No newline at end of file diff --git a/yarn-project/foundation/src/noir/noir_artifact.ts b/yarn-project/foundation/src/noir/noir_artifact.ts new file mode 100644 index 000000000000..5b5e75f6019c --- /dev/null +++ b/yarn-project/foundation/src/noir/noir_artifact.ts @@ -0,0 +1,95 @@ +/* eslint-disable camelcase */ + +/* eslint-disable jsdoc/require-jsdoc */ +import { z } from 'zod'; + +const noirBasicAbiTypeSchema = z.discriminatedUnion('kind', [ + z.object({ kind: z.literal('boolean') }), + z.object({ kind: z.literal('field') }), + z.object({ + kind: z.literal('integer'), + sign: z.union([z.literal('signed'), z.literal('unsigned')]), + width: z.number(), + }), + z.object({ kind: z.literal('string'), length: z.number() }), +]); + +type NoirAbiType = + | z.infer + | { kind: 'struct'; fields: { name: string; type: NoirAbiType }[]; path: string } + | { kind: 'array'; length: number; type: NoirAbiType }; + +const noirAbiTypeSchema: z.ZodType = z.lazy(() => + z.discriminatedUnion('kind', [ + ...noirBasicAbiTypeSchema.options, + z.object({ kind: z.literal('array'), length: z.number(), type: noirAbiTypeSchema }), + z.object({ + kind: z.literal('struct'), + path: z.string(), + fields: z.array(noirAbiVariableSchema), + }), + ]), +); + +const noirAbiVariableSchema = z.object({ + name: z.string(), + type: noirAbiTypeSchema, +}); + +const noirAbiParameterVisibilitySchema = z.union([z.literal('databus'), z.literal('private'), z.literal('public')]); + +const noirFunctionVisibilitySchema = z.union([z.literal('Secret'), z.literal('Open'), z.literal('Unconstrained')]); + +const noirContractCompilationArtifactSchema = z.object({ + name: z.string(), + noir_version: z.string(), + functions: z.array( + z.object({ + name: z.string(), + function_type: noirFunctionVisibilitySchema, + is_internal: z.boolean(), + abi: z.object({ + parameters: z.array( + z.object({ + name: z.string(), + type: noirAbiTypeSchema, + visibility: noirAbiParameterVisibilitySchema, + }), + ), + param_witnesses: z.object({ + input: z.array( + z.object({ + start: z.number(), + end: z.number(), + }), + ), + }), + return_type: z + .object({ + abi_type: noirAbiTypeSchema, + visibility: z.string(), + }) + .optional(), + return_witnesses: z.array(z.number()), + }), + bytecode: z.string(), + proving_key: z.string(), + verification_key: z.string(), + }), + ), + events: z.array( + z.object({ + name: z.string(), + path: z.string(), + fields: z.array(noirAbiVariableSchema), + }), + ), +}); + +/** Contract compilation artifact as outputted by Nargo */ +export type NoirContractCompilationArtifact = z.infer; + +/** Parses any object into a noir contract compilation artifact, throwing on validation error. */ +export function parseNoirContractCompilationArtifact(artifact: any): NoirContractCompilationArtifact { + return noirContractCompilationArtifactSchema.parse(artifact); +} diff --git a/yarn-project/noir-compiler/src/cli/codegen.ts b/yarn-project/noir-compiler/src/cli/codegen.ts index 98eb5670d425..9b163e2dd7cf 100644 --- a/yarn-project/noir-compiler/src/cli/codegen.ts +++ b/yarn-project/noir-compiler/src/cli/codegen.ts @@ -26,6 +26,7 @@ export function generateCode(outputPath: string, fileOrDirPath: string, includeD * */ function generateFromNoirAbi(outputPath: string, noirAbiPath: string, includeDebug: boolean, ts: boolean, nr: boolean) { + console.log(`READING noir abi path`, noirAbiPath, noirDebugPath); const contract = JSON.parse(readFileSync(noirAbiPath, 'utf8')); const noirDebugPath = includeDebug ? getDebugFilePath(noirAbiPath) : undefined; const debug = noirDebugPath ? JSON.parse(readFileSync(noirDebugPath, 'utf8')) : undefined; 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 000000000000..fa5124ec353a --- /dev/null +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -0,0 +1,146 @@ +import { FUNCTION_TREE_HEIGHT } from '@aztec/circuits.js'; +import { + ABIParameter, + ABIType, + ContractArtifact, + DebugMetadata, + FunctionArtifact, + FunctionType, +} from '@aztec/foundation/abi'; +import { NoirContractCompilationArtifact, parseNoirContractCompilationArtifact } from '@aztec/foundation/noir'; + +import { mockVerificationKey } from './mocked_keys.js'; + +/** + * Parses 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: any): ContractArtifact { + if (isContractArtifact(input)) { + return input; + } + const noirArtifact = parseNoirContractCompilationArtifact(input); + return generateContractArtifact(noirArtifact); +} + +/** + * 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; +} + +type NoirContractCompilationArtifactFunctionParameter = + NoirContractCompilationArtifactFunction['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: NoirContractCompilationArtifactFunctionParameter): ABIParameter { + const { visibility } = param; + if (visibility === 'databus') { + throw new Error(`Unsupported visibility ${param.visibility} for noir contract function parameter ${param.name}.`); + } + return { ...param, visibility }; +} + +type NoirContractCompilationArtifactFunction = NoirContractCompilationArtifact['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: NoirContractCompilationArtifactFunction): 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 !== 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 !== 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, + }; +} + +/** + * Given a Nargo output generates an Aztec-compatible contract artifact. + * @param compiled - Noir build output. + * @returns Aztec contract build artifact. + */ +function generateContractArtifact( + contract: NoirContractCompilationArtifact, + 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 debug = null; + // 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/types/src/abi/index.ts b/yarn-project/types/src/abi/index.ts new file mode 100644 index 000000000000..27174adec05f --- /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 000000000000..b8f5c677eade --- /dev/null +++ b/yarn-project/types/src/abi/mocked_keys.ts @@ -0,0 +1,2 @@ +export const mockVerificationKey = + '0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f'; From 672bded54580cb54afb004ded04b10a1c9791021 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 9 Jan 2024 16:14:05 -0300 Subject: [PATCH 2/7] Do not store debug artifact in a separate file on nargo cli compile --- noir/Cargo.lock | 7 ++- 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 | 41 +++++++++++++++- noir/tooling/nargo/src/artifacts/contract.rs | 19 ++++++++ noir/tooling/nargo/src/artifacts/program.rs | 21 +++++++++ noir/tooling/nargo_cli/src/cli/compile_cmd.rs | 47 ++++++------------- noir/tooling/nargo_cli/src/cli/fs/program.rs | 22 +-------- 9 files changed, 107 insertions(+), 58 deletions(-) diff --git a/noir/Cargo.lock b/noir/Cargo.lock index 50892d98ff3f..595af7cc9d80 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", @@ -2936,11 +2936,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 5469a63f3dd3..4e7ae874ffb2 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 a0877120a585..b44c64dd8383 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 02e97b2c6709..935137ba2fc3 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 ee40ced19bf5..48838acfa505 100644 --- a/noir/compiler/noirc_errors/src/debug_info.rs +++ b/noir/compiler/noirc_errors/src/debug_info.rs @@ -1,14 +1,22 @@ use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; +use base64::Engine; +use flate2::Compression; +use flate2::read::DeflateDecoder; +use flate2::write::DeflateEncoder; +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 +94,35 @@ 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/tooling/nargo/src/artifacts/contract.rs b/noir/tooling/nargo/src/artifacts/contract.rs index 04699126762d..03ad26d93512 100644 --- a/noir/tooling/nargo/src/artifacts/contract.rs +++ b/noir/tooling/nargo/src/artifacts/contract.rs @@ -2,6 +2,14 @@ use acvm::acir::circuit::Circuit; use noirc_abi::{Abi, ContractEvent}; use noirc_driver::{ContractFunction, ContractFunctionType}; use serde::{Deserialize, Serialize}; +use noirc_evaluator::errors::SsaReport; + +use noirc_driver::DebugFile; +use noirc_errors::debug_info::DebugInfo; +use std::collections::BTreeMap; + +use fm::FileId; + #[derive(Serialize, Deserialize)] pub struct ContractArtifact { @@ -13,6 +21,10 @@ 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, + /// Compilation warnings. + pub warnings: Vec, } /// Each function in the contract will be compiled as a separate noir program. @@ -34,6 +46,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 +62,7 @@ impl From for ContractFunctionArtifact { is_internal: func.is_internal, abi: func.abi, bytecode: func.bytecode, + debug_symbols: func.debug_symbols, } } } diff --git a/noir/tooling/nargo/src/artifacts/program.rs b/noir/tooling/nargo/src/artifacts/program.rs index 96e63e6fe502..b3768e04db3c 100644 --- a/noir/tooling/nargo/src/artifacts/program.rs +++ b/noir/tooling/nargo/src/artifacts/program.rs @@ -1,6 +1,12 @@ +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 noirc_evaluator::errors::SsaReport; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] @@ -20,6 +26,18 @@ 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, + + /// Compilation warnings. + pub warnings: Vec, } impl From for ProgramArtifact { @@ -29,6 +47,9 @@ impl From for ProgramArtifact { abi: program.abi, noir_version: program.noir_version, bytecode: program.circuit, + debug_symbols: program.debug_info, + file_map: program.file_map, + warnings: program.warnings, } } } diff --git a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs index 31105ebe68f2..e6a27534ee6f 100644 --- a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -1,10 +1,12 @@ + + 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,8 +17,11 @@ 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; use crate::backends::Backend; @@ -24,8 +29,8 @@ 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, + read_program_from_file, save_contract_to_file, + save_program_to_file, }; use super::NargoConfig; use rayon::prelude::*; @@ -170,20 +175,15 @@ 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), - ) { + let cached_program = if let Ok(program_artifact) = read_program_from_file(program_artifact_path) { 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, + debug: program_artifact.debug_symbols, + file_map: program_artifact.file_map, + warnings: program_artifact.warnings, }) } else { None @@ -244,14 +244,6 @@ 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) { @@ -259,18 +251,13 @@ fn save_contract(contract: CompiledContract, package: &Package, circuit_dir: &Pa // 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, + debug_symbols: func.debug, }); let contract_artifact = ContractArtifact { @@ -278,6 +265,8 @@ fn save_contract(contract: CompiledContract, package: &Package, circuit_dir: &Pa name: contract.name, functions, events: contract.events, + file_map: contract.file_map, + warnings: contract.warnings, }; save_contract_to_file( @@ -285,12 +274,6 @@ fn save_contract(contract: CompiledContract, package: &Package, circuit_dir: &Pa &format!("{}-{}", package.name, contract_artifact.name), circuit_dir, ); - - save_debug_artifact_to_file( - &debug_artifact, - &format!("{}-{}", package.name, contract_artifact.name), - circuit_dir, - ); } /// Helper function for reporting any errors in a `CompilationResult` diff --git a/noir/tooling/nargo_cli/src/cli/fs/program.rs b/noir/tooling/nargo_cli/src/cli/fs/program.rs index 1d2f012736ee..b9b65113001c 100644 --- a/noir/tooling/nargo_cli/src/cli/fs/program.rs +++ b/noir/tooling/nargo_cli/src/cli/fs/program.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use acvm::acir::circuit::Circuit; use nargo::artifacts::{ - contract::ContractArtifact, debug::DebugArtifact, program::ProgramArtifact, + contract::ContractArtifact, program::ProgramArtifact, }; use noirc_frontend::graph::CrateName; @@ -40,15 +40,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 +65,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) -} From 2ab38faa2e1360c8a097b4602c578b28bb8844ef Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 9 Jan 2024 16:21:18 -0300 Subject: [PATCH 3/7] Remove separate debug artifact from wasm build output --- noir/compiler/wasm/src/compile.rs | 52 +++++++------------------------ 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/noir/compiler/wasm/src/compile.rs b/noir/compiler/wasm/src/compile.rs index 54fdccf13693..adfb0c8d72bb 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::{ @@ -33,28 +32,25 @@ export type CompiledContract = { name: string; functions: Array; events: Array; + file_map: Record; + warnings: Array; }; export type CompiledProgram = { noir_version: string; abi: any; bytecode: string; -} - -export type DebugArtifact = { - debug_symbols: Array; + debug_symbols: any; file_map: Record; warnings: Array; -}; +} export type CompileResult = ( | { contract: CompiledContract; - debug: DebugArtifact; } | { program: CompiledProgram; - debug: DebugArtifact; } ); "#; @@ -76,40 +72,25 @@ extern "C" { impl JsCompileResult { const CONTRACT_PROP: &'static str = "contract"; const PROGRAM_PROP: &'static str = "program"; - const DEBUG_PROP: &'static str = "debug"; pub fn new(resp: CompileResult) -> JsCompileResult { let obj = JsCompileResult::constructor(); match resp { - CompileResult::Contract { contract, debug } => { + CompileResult::Contract { contract } => { 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(), - ) - .unwrap(); } - CompileResult::Program { program, debug } => { + CompileResult::Program { program } => { 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(), - ) - .unwrap(); } }; @@ -148,8 +129,8 @@ impl PathToFileSourceMap { } pub enum CompileResult { - Contract { contract: ContractArtifact, debug: DebugArtifact }, - Program { program: ProgramArtifact, debug: DebugArtifact }, + Contract { contract: ContractArtifact }, + Program { program: ProgramArtifact }, } #[wasm_bindgen] @@ -273,21 +254,10 @@ 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 } + CompileResult::Program { program: program.into() } } 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 +265,11 @@ pub(crate) fn generate_contract_artifact(contract: CompiledContract) -> CompileR name: contract.name, functions, events: contract.events, + file_map: contract.file_map, + warnings: contract.warnings, }; - CompileResult::Contract { contract: contract_artifact, debug: debug_artifact } + CompileResult::Contract { contract: contract_artifact } } #[cfg(test)] From 583cd45cf9737a5cdaa817de9f668eb3091ffa25 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 10 Jan 2024 10:34:05 -0300 Subject: [PATCH 4/7] Fix rebase issues and throw in a few extra trait impls --- noir/tooling/nargo/src/artifacts/contract.rs | 18 +++++++- noir/tooling/nargo/src/artifacts/program.rs | 16 ++++++- noir/tooling/nargo_cli/src/cli/compile_cmd.rs | 43 ++----------------- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/noir/tooling/nargo/src/artifacts/contract.rs b/noir/tooling/nargo/src/artifacts/contract.rs index 03ad26d93512..38815f297d82 100644 --- a/noir/tooling/nargo/src/artifacts/contract.rs +++ b/noir/tooling/nargo/src/artifacts/contract.rs @@ -1,6 +1,6 @@ use acvm::acir::circuit::Circuit; use noirc_abi::{Abi, ContractEvent}; -use noirc_driver::{ContractFunction, ContractFunctionType}; +use noirc_driver::{ContractFunction, ContractFunctionType, CompiledContract}; use serde::{Deserialize, Serialize}; use noirc_evaluator::errors::SsaReport; @@ -27,6 +27,20 @@ pub struct ContractArtifact { pub warnings: Vec, } +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, + warnings: contract.warnings, + } + } +} + + /// Each function in the contract will be compiled as a separate noir program. /// /// A contract function unlike a regular Noir program however can have additional properties. @@ -62,7 +76,7 @@ impl From for ContractFunctionArtifact { is_internal: func.is_internal, abi: func.abi, bytecode: func.bytecode, - debug_symbols: func.debug_symbols, + debug_symbols: func.debug, } } } diff --git a/noir/tooling/nargo/src/artifacts/program.rs b/noir/tooling/nargo/src/artifacts/program.rs index b3768e04db3c..d4f729c4ec15 100644 --- a/noir/tooling/nargo/src/artifacts/program.rs +++ b/noir/tooling/nargo/src/artifacts/program.rs @@ -47,9 +47,23 @@ impl From for ProgramArtifact { abi: program.abi, noir_version: program.noir_version, bytecode: program.circuit, - debug_symbols: program.debug_info, + debug_symbols: program.debug, file_map: program.file_map, warnings: program.warnings, } } } + +impl Into for ProgramArtifact { + fn into(self) -> CompiledProgram { + CompiledProgram { + hash: self.hash, + abi: self.abi, + noir_version: self.noir_version, + circuit: self.bytecode, + debug: self.debug_symbols, + file_map: self.file_map, + warnings: self.warnings, + } + } +} \ No newline at end of file diff --git a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs index e6a27534ee6f..2275d6b56d75 100644 --- a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -5,8 +5,6 @@ use std::path::Path; use acvm::ExpressionWidth; use fm::FileManager; -use iter_extended::vecmap; -use nargo::artifacts::contract::{ContractArtifact, ContractFunctionArtifact}; use nargo::artifacts::program::ProgramArtifact; use nargo::errors::CompileError; use nargo::insert_all_files_for_workspace_into_file_manager; @@ -175,19 +173,7 @@ fn compile_program( let (mut context, crate_id) = prepare_package(file_manager, package); let program_artifact_path = workspace.package_build_path(package); - let cached_program = if let Ok(program_artifact) = read_program_from_file(program_artifact_path) { - Some(CompiledProgram { - hash: program_artifact.hash, - circuit: program_artifact.bytecode, - abi: program_artifact.abi, - noir_version: program_artifact.noir_version, - debug: program_artifact.debug_symbols, - file_map: program_artifact.file_map, - warnings: program_artifact.warnings, - }) - } else { - None - }; + let cached_program: Option = read_program_from_file(program_artifact_path).map(|p| p.into()).ok(); let force_recompile = cached_program.as_ref().map_or(false, |p| p.noir_version != NOIR_ARTIFACT_VERSION_STRING); @@ -247,31 +233,10 @@ pub(super) fn save_program( } 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 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, - debug_symbols: func.debug, - }); - - let contract_artifact = ContractArtifact { - noir_version: contract.noir_version, - name: contract.name, - functions, - events: contract.events, - file_map: contract.file_map, - warnings: contract.warnings, - }; - + let contract_name = contract.name.clone(); save_contract_to_file( - &contract_artifact, - &format!("{}-{}", package.name, contract_artifact.name), + &contract.into(), + &format!("{}-{}", package.name, contract_name), circuit_dir, ); } From 95c035ef72207a2b19d03026d44652fe9b58b8be Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 10 Jan 2024 13:09:56 -0300 Subject: [PATCH 5/7] Run cargo fmt on modified files --- noir/compiler/noirc_errors/src/debug_info.rs | 23 ++++++++++++------- noir/tooling/nargo/src/artifacts/contract.rs | 6 ++--- noir/tooling/nargo/src/artifacts/program.rs | 4 ++-- noir/tooling/nargo_cli/src/cli/compile_cmd.rs | 12 +++------- noir/tooling/nargo_cli/src/cli/fs/program.rs | 4 +--- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/noir/compiler/noirc_errors/src/debug_info.rs b/noir/compiler/noirc_errors/src/debug_info.rs index 48838acfa505..a15119943c1e 100644 --- a/noir/compiler/noirc_errors/src/debug_info.rs +++ b/noir/compiler/noirc_errors/src/debug_info.rs @@ -2,9 +2,9 @@ use acvm::acir::circuit::OpcodeLocation; use acvm::compiler::AcirTransformationMap; use base64::Engine; -use flate2::Compression; use flate2::read::DeflateDecoder; use flate2::write::DeflateEncoder; +use flate2::Compression; use serde::Deserializer; use serde::Serializer; use serde_with::serde_as; @@ -16,7 +16,9 @@ use std::io::Write; use std::mem; use crate::Location; -use serde::{de::Error as DeserializationError, ser::Error as SerializationError, Deserialize, Serialize}; +use serde::{ + de::Error as DeserializationError, ser::Error as SerializationError, Deserialize, Serialize, +}; #[serde_as] #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -95,7 +97,10 @@ impl DebugInfo { counted_opcodes } - pub fn serialize_compressed_base64_json(debug_info: &DebugInfo, s: S) -> Result + pub fn serialize_compressed_base64_json( + debug_info: &DebugInfo, + s: S, + ) -> Result where S: Serializer, { @@ -109,19 +114,21 @@ impl DebugInfo { s.serialize_str(&encoded_b64) } - pub fn deserialize_compressed_base64_json<'de, D>(deserializer: D) -> Result + 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 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/tooling/nargo/src/artifacts/contract.rs b/noir/tooling/nargo/src/artifacts/contract.rs index 38815f297d82..134fb48f50ab 100644 --- a/noir/tooling/nargo/src/artifacts/contract.rs +++ b/noir/tooling/nargo/src/artifacts/contract.rs @@ -1,8 +1,8 @@ use acvm::acir::circuit::Circuit; use noirc_abi::{Abi, ContractEvent}; -use noirc_driver::{ContractFunction, ContractFunctionType, CompiledContract}; -use serde::{Deserialize, Serialize}; +use noirc_driver::{CompiledContract, ContractFunction, ContractFunctionType}; use noirc_evaluator::errors::SsaReport; +use serde::{Deserialize, Serialize}; use noirc_driver::DebugFile; use noirc_errors::debug_info::DebugInfo; @@ -10,7 +10,6 @@ use std::collections::BTreeMap; use fm::FileId; - #[derive(Serialize, Deserialize)] pub struct ContractArtifact { /// Version of noir used to compile this contract @@ -40,7 +39,6 @@ impl From for ContractArtifact { } } - /// Each function in the contract will be compiled as a separate noir program. /// /// A contract function unlike a regular Noir program however can have additional properties. diff --git a/noir/tooling/nargo/src/artifacts/program.rs b/noir/tooling/nargo/src/artifacts/program.rs index d4f729c4ec15..356d6289a3c7 100644 --- a/noir/tooling/nargo/src/artifacts/program.rs +++ b/noir/tooling/nargo/src/artifacts/program.rs @@ -35,7 +35,7 @@ pub struct ProgramArtifact { /// 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, - + /// Compilation warnings. pub warnings: Vec, } @@ -66,4 +66,4 @@ impl Into for ProgramArtifact { warnings: self.warnings, } } -} \ No newline at end of file +} diff --git a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs index 2275d6b56d75..924116861f43 100644 --- a/noir/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -1,5 +1,3 @@ - - use std::path::Path; use acvm::ExpressionWidth; @@ -18,18 +16,13 @@ use noirc_driver::{CompilationResult, CompileOptions, CompiledContract, Compiled use noirc_frontend::graph::CrateName; - - use clap::Args; use crate::backends::Backend; use crate::errors::CliError; use super::fs::program::only_acir; -use super::fs::program::{ - read_program_from_file, save_contract_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::*; @@ -173,7 +166,8 @@ fn compile_program( let (mut context, crate_id) = prepare_package(file_manager, package); let program_artifact_path = workspace.package_build_path(package); - let cached_program: Option = read_program_from_file(program_artifact_path).map(|p| p.into()).ok(); + let cached_program: Option = + read_program_from_file(program_artifact_path).map(|p| p.into()).ok(); let force_recompile = cached_program.as_ref().map_or(false, |p| p.noir_version != NOIR_ARTIFACT_VERSION_STRING); diff --git a/noir/tooling/nargo_cli/src/cli/fs/program.rs b/noir/tooling/nargo_cli/src/cli/fs/program.rs index b9b65113001c..1fb57ae66851 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, program::ProgramArtifact, -}; +use nargo::artifacts::{contract::ContractArtifact, program::ProgramArtifact}; use noirc_frontend::graph::CrateName; use crate::errors::FilesystemError; From 165610ceb319dce3c8f482bf15a7f180a5a98ea4 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 11 Jan 2024 11:16:44 -0300 Subject: [PATCH 6/7] Update types from circuits, types, and foundation --- .../acir-simulator/src/client/db_oracle.ts | 14 +- .../src/client/private_execution.test.ts | 6 +- .../src/client/private_execution.ts | 10 +- .../src/client/simulator.test.ts | 9 +- .../acir-simulator/src/client/simulator.ts | 15 +- .../src/client/unconstrained_execution.ts | 5 +- yarn-project/acir-simulator/src/test/utils.ts | 32 +- .../aztec.js/src/contract/contract.test.ts | 17 +- .../contract/contract_function_interaction.ts | 4 +- .../contract_deployer.test.ts | 9 +- .../contract/contract_tree/contract_tree.ts | 7 +- .../circuits.js/src/structs/function_data.ts | 2 +- yarn-project/cli/src/test/mocks.ts | 19 +- yarn-project/foundation/src/abi/abi.ts | 298 +++++++----------- .../foundation/src/abi/encoder.test.ts | 33 +- .../foundation/src/abi/function_selector.ts | 19 +- .../foundation/src/noir/noir_artifact.ts | 6 +- .../pxe/src/contract_data_oracle/index.ts | 14 +- yarn-project/types/package.json | 1 + .../types/src/abi/contract_artifact.ts | 59 ++-- yarn-project/types/src/contract_dao.test.ts | 11 +- yarn-project/types/src/contract_dao.ts | 25 +- yarn-project/yarn.lock | 3 +- 23 files changed, 256 insertions(+), 362 deletions(-) diff --git a/yarn-project/acir-simulator/src/client/db_oracle.ts b/yarn-project/acir-simulator/src/client/db_oracle.ts index b389d423891b..09aeb6e1f700 100644 --- a/yarn-project/acir-simulator/src/client/db_oracle.ts +++ b/yarn-project/acir-simulator/src/client/db_oracle.ts @@ -1,5 +1,5 @@ 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. */ @@ -181,4 +171,4 @@ export interface DBOracle extends CommitmentsDB { * @returns The block number. */ getBlockNumber(): Promise; -} +} \ No newline at end of file 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 2b9d4d66972b..20c7b17f2351 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,7 @@ 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 } 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 +46,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'; @@ -606,7 +606,7 @@ describe('Private Execution test suite', () => { beforeEach(() => { oracle.getFunctionArtifact.mockImplementation((_, selector) => - Promise.resolve(getFunctionArtifactWithSelector(PendingCommitmentsContractArtifact, selector)), + Promise.resolve(getFunctionArtifact(PendingCommitmentsContractArtifact, selector)), ); oracle.getFunctionArtifactByName.mockImplementation((_, functionName: string) => Promise.resolve(getFunctionArtifact(PendingCommitmentsContractArtifact, functionName)), diff --git a/yarn-project/acir-simulator/src/client/private_execution.ts b/yarn-project/acir-simulator/src/client/private_execution.ts index d2d43ca534d2..f2273654e820 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.ts @@ -1,18 +1,20 @@ 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'; import { to2Fields } from '@aztec/foundation/serialize'; + + 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 { type ClientExecutionContext } from './client_execution_context.js'; import { ExecutionResult } from './execution_result.js'; import { AcirSimulator } from './simulator.js'; + /** * Execute a private function and return the execution result. */ @@ -75,4 +77,4 @@ export async function executePrivateFunction( encryptedLogs, unencryptedLogs, }; -} +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/client/simulator.test.ts b/yarn-project/acir-simulator/src/client/simulator.test.ts index 3b04dee7e6ad..55907ba2a4d8 100644 --- a/yarn-project/acir-simulator/src/client/simulator.test.ts +++ b/yarn-project/acir-simulator/src/client/simulator.test.ts @@ -1,6 +1,6 @@ import { CompleteAddress } from '@aztec/circuits.js'; import { computeUniqueCommitment, siloCommitment } from '@aztec/circuits.js/abis'; -import { ABIParameterVisibility } from '@aztec/foundation/abi'; +import { 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 { Note } from '@aztec/types'; 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', () => { @@ -89,7 +88,7 @@ describe('Simulator', () => { kind: 'field', }, }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], }; @@ -102,4 +101,4 @@ describe('Simulator', () => { ); }); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/client/simulator.ts b/yarn-project/acir-simulator/src/client/simulator.ts index b34093e983cc..d54f2aaa2e73 100644 --- a/yarn-project/acir-simulator/src/client/simulator.ts +++ b/yarn-project/acir-simulator/src/client/simulator.ts @@ -1,25 +1,30 @@ 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, 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'; import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { AztecNode, FunctionCall, Note, TxExecutionRequest } from '@aztec/types'; + + import { WasmBlackBoxFunctionSolver, createBlackBoxSolver } from '@noir-lang/acvm_js'; + + import { createSimulationError } from '../common/errors.js'; import { SideEffectCounter } from '../common/index.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'; import { executeUnconstrainedFunction } from './unconstrained_execution.js'; import { ViewDataOracle } from './view_data_oracle.js'; + /** * The ACIR simulator. */ @@ -67,7 +72,7 @@ export class AcirSimulator { portalContractAddress: EthAddress, msgSender = AztecAddress.ZERO, ): Promise { - if (entryPointArtifact.functionType !== FunctionType.SECRET) { + if (entryPointArtifact.functionType !== 'secret') { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as secret`); } @@ -130,7 +135,7 @@ export class AcirSimulator { contractAddress: AztecAddress, aztecNode?: AztecNode, ) { - if (entryPointArtifact.functionType !== FunctionType.UNCONSTRAINED) { + if (entryPointArtifact.functionType !== 'unconstrained') { throw new Error(`Cannot run ${entryPointArtifact.functionType} function as constrained`); } @@ -248,4 +253,4 @@ export class AcirSimulator { const { innerNullifier } = await this.computeNoteHashAndNullifier(contractAddress, nonce, storageSlot, note); return innerNullifier; } -} +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts index d197ec386fbc..fe44e8dc0e5d 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'; /** @@ -46,4 +45,4 @@ export async function executeUnconstrainedFunction( const returnValues: ACVMField[] = extractReturnWitness(acir, partialWitness); return decodeReturnValues(artifact, returnValues.map(fromACVMField)); -} +} \ No newline at end of file diff --git a/yarn-project/acir-simulator/src/test/utils.ts b/yarn-project/acir-simulator/src/test/utils.ts index f879dff69d9c..2a090ec66abb 100644 --- a/yarn-project/acir-simulator/src/test/utils.ts +++ b/yarn-project/acir-simulator/src/test/utils.ts @@ -1,10 +1,8 @@ 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 { L1Actor, L1ToL2Message, L2Actor } from '@aztec/types'; -import { FunctionArtifactWithDebugMetadata } from '../index.js'; /** * Test utility function to craft an L1 to L2 message. @@ -38,32 +36,4 @@ export const buildL1ToL2Message = ( 0, 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 }; -}; +}; \ No newline at end of file diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 55b0242145a6..562955428a7c 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -1,13 +1,12 @@ import { AztecAddress, CompleteAddress, EthAddress } from '@aztec/circuits.js'; import { L1ContractAddresses } from '@aztec/ethereum'; -import { ABIParameterVisibility, ContractArtifact, FunctionType } from '@aztec/foundation/abi'; +import { ContractArtifact } from '@aztec/foundation/abi'; import { ExtendedContractData, NodeInfo, Tx, TxExecutionRequest, TxHash, TxReceipt } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; import { Wallet } from '../wallet/index.js'; import { Contract } from './contract.js'; - describe('Contract Class', () => { let wallet: MockProxy; let resolvedExtendedContractData: ExtendedContractData; @@ -40,7 +39,7 @@ describe('Contract Class', () => { functions: [ { name: 'bar', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -48,14 +47,14 @@ describe('Contract Class', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.PUBLIC, + visibility: 'public', }, { name: 'value', type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -63,7 +62,7 @@ describe('Contract Class', () => { }, { name: 'baz', - functionType: FunctionType.OPEN, + functionType: 'open', isInternal: false, parameters: [], returnTypes: [], @@ -71,7 +70,7 @@ describe('Contract Class', () => { }, { name: 'qux', - functionType: FunctionType.UNCONSTRAINED, + functionType: 'unconstrained', isInternal: false, parameters: [ { @@ -79,7 +78,7 @@ describe('Contract Class', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.PUBLIC, + visibility: 'public', }, ], returnTypes: [ @@ -146,4 +145,4 @@ describe('Contract Class', () => { expect(() => fooContract.methods.bar().view()).toThrow(); expect(() => fooContract.methods.baz().view()).toThrow(); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index 84ac432f48aa..5becd807cfff 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -41,7 +41,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction { * @returns A Promise that resolves to a transaction instance. */ public async create(): Promise { - if (this.functionDao.functionType === FunctionType.UNCONSTRAINED) { + if (this.functionDao.functionType === 'unconstrained') { throw new Error("Can't call `create` on an unconstrained function."); } if (!this.txRequest) { @@ -70,7 +70,7 @@ export class ContractFunctionInteraction extends BaseContractInteraction { * @returns The result of the view transaction as returned by the contract function. */ public view(options: ViewMethodOptions = {}) { - if (this.functionDao.functionType !== FunctionType.UNCONSTRAINED) { + if (this.functionDao.functionType !== 'unconstrained') { throw new Error('Can only call `view` on an unconstrained function.'); } 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 8622f29e6f71..aa0c25dc7081 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 @@ -2,10 +2,15 @@ import { EthAddress, Fr, Point } from '@aztec/circuits.js'; import { ContractArtifact, FunctionType } from '@aztec/foundation/abi'; import { PXE, PublicKey, Tx, TxHash, TxReceipt } from '@aztec/types'; + + import { MockProxy, mock } from 'jest-mock-extended'; + + import { ContractDeployer } from './contract_deployer.js'; + describe.skip('Contract Deployer', () => { let pxe: MockProxy; @@ -14,7 +19,7 @@ describe.skip('Contract Deployer', () => { functions: [ { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [], returnTypes: [], @@ -53,4 +58,4 @@ describe.skip('Contract Deployer', () => { expect(pxe.sendTx).toHaveBeenCalledTimes(1); expect(pxe.sendTx).toHaveBeenCalledWith(mockTx); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts b/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts index 2f9c9a7a8b48..6d4268549f82 100644 --- a/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts +++ b/yarn-project/circuits.js/src/contract/contract_tree/contract_tree.ts @@ -2,6 +2,7 @@ import { ContractFunctionDao, Fr, FunctionData, FunctionLeafPreimage } from '@az import { computeFunctionLeaf, hashVK } from '@aztec/circuits.js/abis'; import { FunctionSelector, FunctionType } from '@aztec/foundation/abi'; + /** * Computes the hash of a hex-encoded string representation of a verification key (vk). * The input 'vk' should be a hexadecimal string, and the resulting hash is computed using 'hashVK' function. @@ -51,7 +52,7 @@ export function isConstrained({ */ functionType: FunctionType; }) { - return functionType !== FunctionType.UNCONSTRAINED && !isConstructor({ name }); + return functionType !== 'unconstrained' && !isConstructor({ name }); } /** @@ -69,7 +70,7 @@ export function generateFunctionLeaves(functions: ContractFunctionDao[]) { const f = targetFunctions[i]; const selector = FunctionSelector.fromNameAndParameters(f.name, f.parameters); const isInternal = f.isInternal; - const isPrivate = f.functionType === FunctionType.SECRET; + const isPrivate = f.functionType === 'secret'; // All non-unconstrained functions have vks // TODO we'd need to have a defined length of the VK for this to be computed in noir // const vkHash = hashVKStr(f.verificationKey!, wasm); @@ -105,4 +106,4 @@ export interface NewContractConstructor { * The hashed verification key of a function. */ vkHash: Buffer; -} +} \ No newline at end of file diff --git a/yarn-project/circuits.js/src/structs/function_data.ts b/yarn-project/circuits.js/src/structs/function_data.ts index 9838417a13b6..d76c01480caf 100644 --- a/yarn-project/circuits.js/src/structs/function_data.ts +++ b/yarn-project/circuits.js/src/structs/function_data.ts @@ -32,7 +32,7 @@ export class FunctionData { return new FunctionData( FunctionSelector.fromNameAndParameters(abi.name, abi.parameters), abi.isInternal, - abi.functionType === FunctionType.SECRET, + abi.functionType === 'secret', abi.name === 'constructor', ); } diff --git a/yarn-project/cli/src/test/mocks.ts b/yarn-project/cli/src/test/mocks.ts index f02cbdb8f75f..46275e0f608e 100644 --- a/yarn-project/cli/src/test/mocks.ts +++ b/yarn-project/cli/src/test/mocks.ts @@ -1,11 +1,12 @@ import { ABIParameterVisibility, ContractArtifact, FunctionType } from '@aztec/foundation/abi'; + export const mockContractArtifact: ContractArtifact = { name: 'MockContract', functions: [ { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -13,7 +14,7 @@ export const mockContractArtifact: ContractArtifact = { type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, ], returnTypes: [], @@ -21,28 +22,28 @@ export const mockContractArtifact: ContractArtifact = { }, { name: 'mockFunction', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { name: 'fieldParam', type: { kind: 'field' }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, { name: 'boolParam', type: { kind: 'boolean' }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, { name: 'integerParam', type: { kind: 'integer', sign: 'signed', width: 32 }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, { name: 'arrayParam', type: { kind: 'array', length: 3, type: { kind: 'field' } }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, { name: 'structParam', @@ -54,7 +55,7 @@ export const mockContractArtifact: ContractArtifact = { { name: 'subField2', type: { kind: 'boolean' } }, ], }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'secret', }, ], returnTypes: [{ kind: 'boolean' }], @@ -62,4 +63,4 @@ export const mockContractArtifact: ContractArtifact = { }, ], events: [], -}; +}; \ No newline at end of file diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index 5eda291ef1cc..673f1f93de39 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -1,194 +1,116 @@ import { inflate } from 'pako'; -/** - * A named type. - */ + +import { type FunctionSelector } from './function_selector.js'; + + +/** A named type. */ export interface ABIVariable { - /** - * The name of the variable. - */ + /** The name of the variable. */ name: string; - /** - * The type of the variable. - */ + /** The type of the variable. */ type: ABIType; } -/** - * Indicates whether a parameter is public or secret/private. - */ +/** Indicates whether a parameter is public or secret/private. */ export type ABIParameterVisibility = 'public' | 'private'; - -/** - * A function parameter. - */ + +/** A function parameter. */ export interface ABIParameter extends ABIVariable { - /** - * Indicates whether a parameter is public or secret/private. - */ + /** Indicates whether a parameter is public or secret/private. */ visibility: ABIParameterVisibility; } -/** - * A basic type. - */ +/** A basic type. */ export interface BasicType { - /** - * The kind of the type. - */ + /** The kind of the type. */ kind: T; } -/** - * A variable type. - */ +/** A variable type. */ export type ABIType = BasicType<'field'> | BasicType<'boolean'> | IntegerType | ArrayType | StringType | StructType; -/** - * An integer type. - */ +/** An integer type. */ export interface IntegerType extends BasicType<'integer'> { - /** - * The sign of the integer. - */ + /** The sign of the integer. */ sign: string; - /** - * The width of the integer in bits. - */ + /** The width of the integer in bits. */ width: number; } -/** - * An array type. - */ +/** An array type. */ export interface ArrayType extends BasicType<'array'> { - /** - * The length of the array. - */ + /** The length of the array. */ length: number; - /** - * The type of the array elements. - */ + /** The type of the array elements. */ type: ABIType; } -/** - * A string type. - */ +/** A string type. */ export interface StringType extends BasicType<'string'> { - /** - * The length of the string. - */ + /** The length of the string. */ length: number; } -/** - * A struct type. - */ +/** A struct type. */ export interface StructType extends BasicType<'struct'> { - /** - * The fields of the struct. - */ + /** The fields of the struct. */ fields: ABIVariable[]; - /** - * Fully qualified name of the struct. - */ + /** Fully qualified name of the struct. */ path: string; } -/** - * A contract event. - */ +/** A contract event. */ export interface EventAbi { - /** - * The event name. - */ + /** The event name. */ name: string; - /** - * Fully qualified name of the event. - */ + /** Fully qualified name of the event. */ path: string; - /** - * The fields of the event. - */ + /** The fields of the event. */ fields: ABIVariable[]; } -/** - * Aztec.nr function types. - */ -export enum FunctionType { - SECRET = 'secret', - OPEN = 'open', - UNCONSTRAINED = 'unconstrained', -} +/** Aztec.nr function types. */ +export type FunctionType = 'secret' | 'open' | 'unconstrained'; -/** - * The abi entry of a function. - */ +/** The abi entry of a function. */ export interface FunctionAbi { - /** - * The name of the function. - */ + /** The name of the function. */ name: string; - /** - * Whether the function is secret. - */ + /** Whether the function is secret. */ functionType: FunctionType; - /** - * Whether the function is internal. - */ + /** Whether the function is internal. */ isInternal: boolean; - /** - * Function parameters. - */ + /** Function parameters. */ parameters: ABIParameter[]; - /** - * The types of the return values. - */ + /** The types of the return values. */ returnTypes: ABIType[]; } -/** - * The artifact entry of a function. - */ +/** The artifact entry of a function. */ export interface FunctionArtifact extends FunctionAbi { - /** - * The ACIR bytecode of the function. - */ + /** The ACIR bytecode of the function. */ bytecode: string; - /** - * The verification key of the function. - */ + /** The verification key of the function. */ verificationKey?: string; + /** Debug symbols for the function, deflated as JSON, compressed using gzip and serialized with base64. */ + debugSymbols?: string; } -/** - * A file ID. It's assigned during compilation. - */ +/** A file ID. It's assigned during compilation. */ export type FileId = number; -/** - * A pointer to a specific section of the source code. - */ +/** A pointer to a specific section of the source code. */ export interface SourceCodeLocation { - /** - * The section of the source code. - */ + /** The section of the source code. */ span: { - /** - * The byte where the section starts. - */ + /** The byte where the section starts. */ start: number; - /** - * The byte where the section ends. - */ + /** The byte where the section ends. */ end: number; }; - /** - * The source code file pointed to. - */ + /** The source code file pointed to. */ file: FileId; } @@ -198,91 +120,96 @@ export interface SourceCodeLocation { */ export type OpcodeLocation = string; -/** - * The debug information for a given function. - */ +/** The debug information for a given function. */ export interface DebugInfo { - /** - * A map of the opcode location to the source code location. - */ + /** A map of the opcode location to the source code location. */ locations: Record; } -/** - * Maps a file ID to its metadata for debugging purposes. - */ +/** Maps a file ID to its metadata for debugging purposes. */ export type DebugFileMap = Record< FileId, { - /** - * The source code of the file. - */ + /** The source code of the file. */ source: string; - /** - * The path of the file. - */ + /** The path of the file. */ path: string; } >; -/** - * The debug metadata of an ABI. - */ +/** The debug metadata of an ABI. */ export interface DebugMetadata { - /** - * The DebugInfo object, deflated as JSON, compressed using gzip and serialized with base64. - */ + /** 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. - */ + /** The map of file ID to the source code and path of the file. */ fileMap: DebugFileMap; } -/** - * Defines artifact of a contract. - */ +/** Defines artifact of a contract. */ export interface ContractArtifact { - /** - * The name of the contract. - */ + /** The name of the contract. */ name: string; - /** - * The version of compiler used to create this artifact - */ + /** The version of compiler used to create this artifact */ aztecNrVersion?: string; - /** - * The functions of the contract. - */ + /** The functions of the contract. */ functions: FunctionArtifact[]; - /** - * The events of the contract. - */ + + /** The events of the contract. */ 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. + * Used to include the relevant source code section when a constraint is not met during simulation. */ - debug?: DebugMetadata; + fileMap?: DebugFileMap; } -/** - * Debug metadata for a function. - */ +/** Debug metadata for a function. */ export interface FunctionDebugMetadata { - /** - * Maps opcodes to source code pointers - */ + /** Maps opcodes to source code pointers */ debugSymbols: DebugInfo; - /** - * Maps the file IDs to the file contents to resolve pointers - */ + /** Maps the file IDs to the file contents to resolve pointers */ 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; +} + +/** + * 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 @@ -290,19 +217,12 @@ 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) { - const debugSymbols = JSON.parse( - inflate(Buffer.from(artifact.debug.debugSymbols[functionIndex], 'base64'), { to: 'string' }), - ); - const files = artifact.debug.fileMap; - return { - debugSymbols, - files, - }; + if (functionArtifact.debugSymbols && contractArtifact.fileMap) { + const debugSymbols = JSON.parse(inflate(Buffer.from(functionArtifact.debugSymbols, 'base64'), { to: 'string' })); + return { debugSymbols, files: contractArtifact.fileMap }; } return undefined; } \ No newline at end of file diff --git a/yarn-project/foundation/src/abi/encoder.test.ts b/yarn-project/foundation/src/abi/encoder.test.ts index 4dd345290390..54c9ae2620d8 100644 --- a/yarn-project/foundation/src/abi/encoder.test.ts +++ b/yarn-project/foundation/src/abi/encoder.test.ts @@ -1,13 +1,14 @@ import { AztecAddress } from '../aztec-address/index.js'; import { Fr } from '../fields/fields.js'; -import { ABIParameterVisibility, FunctionAbi, FunctionType } from './abi.js'; +import { FunctionAbi } from './abi.js'; import { encodeArguments } from './encoder.js'; + describe('abi/encoder', () => { it('serializes fields as fields', () => { const abi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -15,7 +16,7 @@ describe('abi/encoder', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -28,7 +29,7 @@ describe('abi/encoder', () => { it('serializes arrays of fields', () => { const abi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -38,7 +39,7 @@ describe('abi/encoder', () => { length: 2, type: { kind: 'field' }, }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -51,7 +52,7 @@ describe('abi/encoder', () => { it('serializes string', () => { const abi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -60,7 +61,7 @@ describe('abi/encoder', () => { kind: 'string', length: 4, }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -75,7 +76,7 @@ describe('abi/encoder', () => { it.each(['AztecAddress', 'EthAddress'])('accepts address instance for %s structs', (structType: string) => { const abi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -90,7 +91,7 @@ describe('abi/encoder', () => { }, ], }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -106,7 +107,7 @@ describe('abi/encoder', () => { it('throws when passing string argument as field', () => { const testFunctionAbi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -114,7 +115,7 @@ describe('abi/encoder', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -127,7 +128,7 @@ describe('abi/encoder', () => { it('throws when passing string argument as integer', () => { const testFunctionAbi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -137,7 +138,7 @@ describe('abi/encoder', () => { width: 5, kind: 'integer', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -151,7 +152,7 @@ describe('abi/encoder', () => { it('throws when passing object argument as field', () => { const testFunctionAbi: FunctionAbi = { name: 'constructor', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -159,7 +160,7 @@ describe('abi/encoder', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -174,4 +175,4 @@ describe('abi/encoder', () => { 'Argument for owner cannot be serialized to a field', ); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/foundation/src/abi/function_selector.ts b/yarn-project/foundation/src/abi/function_selector.ts index de0b879cf6b9..7769e85eb05d 100644 --- a/yarn-project/foundation/src/abi/function_selector.ts +++ b/yarn-project/foundation/src/abi/function_selector.ts @@ -1,6 +1,8 @@ import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; import { BufferReader } from '@aztec/foundation/serialize'; + + import { keccak } from '../crypto/keccak/index.js'; import { Fr } from '../fields/index.js'; import { ABIParameter } from './abi.js'; @@ -46,12 +48,25 @@ export class FunctionSelector { return this.toBuffer().toString('hex'); } + /** + * 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; + } + /** * Checks if this function selector is equal to another. * @param other - The other function selector. * @returns True if the function selectors are equal. */ - equals(other: FunctionSelector): boolean { + equalsNameAndParams(other: FunctionSelector): boolean { return this.value === other.value; } @@ -134,4 +149,4 @@ export class FunctionSelector { static empty(): FunctionSelector { return new FunctionSelector(0); } -} +} \ No newline at end of file diff --git a/yarn-project/foundation/src/noir/noir_artifact.ts b/yarn-project/foundation/src/noir/noir_artifact.ts index 5b5e75f6019c..3e5f08846ab1 100644 --- a/yarn-project/foundation/src/noir/noir_artifact.ts +++ b/yarn-project/foundation/src/noir/noir_artifact.ts @@ -3,6 +3,7 @@ /* eslint-disable jsdoc/require-jsdoc */ import { z } from 'zod'; + const noirBasicAbiTypeSchema = z.discriminatedUnion('kind', [ z.object({ kind: z.literal('boolean') }), z.object({ kind: z.literal('field') }), @@ -48,6 +49,7 @@ const noirContractCompilationArtifactSchema = z.object({ name: z.string(), function_type: noirFunctionVisibilitySchema, is_internal: z.boolean(), + debug_symbols: z.string().optional(), abi: z.object({ parameters: z.array( z.object({ @@ -84,6 +86,8 @@ const noirContractCompilationArtifactSchema = z.object({ fields: z.array(noirAbiVariableSchema), }), ), + warnings: z.array(z.any()).optional(), + file_map: z.record(z.number(), z.object({ source: z.string(), path: z.string() })).optional() }); /** Contract compilation artifact as outputted by Nargo */ @@ -92,4 +96,4 @@ export type NoirContractCompilationArtifact = z.infer { const tree = await this.getTree(contractAddress); const functionArtifact = tree.contract.getFunctionArtifact(selector); - - if (!functionArtifact) { - return undefined; - } - - return tree.contract.getFunctionDebugMetadataByName(functionArtifact.name); + return functionArtifact && getFunctionDebugMetadata(tree.contract, functionArtifact); } /** @@ -167,4 +165,4 @@ export class ContractDataOracle { } return tree; } -} +} \ No newline at end of file diff --git a/yarn-project/types/package.json b/yarn-project/types/package.json index 5c472ea84dc6..5ff8420a5500 100644 --- a/yarn-project/types/package.json +++ b/yarn-project/types/package.json @@ -4,6 +4,7 @@ "type": "module", "exports": { ".": "./dest/index.js", + "./abi": "./dest/abi/index.js", "./stats": "./dest/stats/index.js", "./jest": "./dest/jest/index.js", "./interfaces": "./dest/interfaces/index.js", diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index fa5124ec353a..c33e121a6773 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -1,18 +1,21 @@ import { FUNCTION_TREE_HEIGHT } from '@aztec/circuits.js'; import { ABIParameter, + ABIParameterVisibility, ABIType, ContractArtifact, - DebugMetadata, FunctionArtifact, FunctionType, } from '@aztec/foundation/abi'; import { NoirContractCompilationArtifact, parseNoirContractCompilationArtifact } from '@aztec/foundation/noir'; + + import { mockVerificationKey } from './mocked_keys.js'; /** * Parses nargo build output and returns a valid contract artifact instance. + * Will also accept a ContractArtifact and just return it. * @param input - Input object as generated by nargo compile. * @returns A valid contract artifact instance. */ @@ -21,7 +24,9 @@ export function loadContractArtifact(input: any): ContractArtifact { return input; } const noirArtifact = parseNoirContractCompilationArtifact(input); - return generateContractArtifact(noirArtifact); + const contractArtifact = generateContractArtifact(noirArtifact); + validateContractArtifact(contractArtifact); + return contractArtifact; } /** @@ -53,6 +58,7 @@ function isContractArtifact(input: any): input is ContractArtifact { return true; } +/** Parameter in a function from a noir contract compilation artifact */ type NoirContractCompilationArtifactFunctionParameter = NoirContractCompilationArtifactFunction['abi']['parameters'][number]; @@ -66,9 +72,10 @@ function generateFunctionParameter(param: NoirContractCompilationArtifactFunctio if (visibility === 'databus') { throw new Error(`Unsupported visibility ${param.visibility} for noir contract function parameter ${param.name}.`); } - return { ...param, visibility }; + return { ...param, visibility: visibility as ABIParameterVisibility }; } +/** Function from a noir contract compilation artifact */ type NoirContractCompilationArtifactFunction = NoirContractCompilationArtifact['functions'][number]; /** @@ -82,13 +89,13 @@ function generateFunctionArtifact(fn: NoirContractCompilationArtifactFunction): // 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 !== FunctionType.UNCONSTRAINED) { + 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 !== FunctionType.SECRET && fn.abi.return_type) { + if (functionType !== 'secret' && fn.abi.return_type) { returnTypes = [fn.abi.return_type.abi_type]; } @@ -103,6 +110,18 @@ function generateFunctionArtifact(fn: NoirContractCompilationArtifactFunction): }; } +/** 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'); + } + if (contract.functions.length > 2 ** FUNCTION_TREE_HEIGHT) { + throw new Error(`Contract can only have a maximum of ${2 ** FUNCTION_TREE_HEIGHT} functions`); + } + return contract; +} + /** * Given a Nargo output generates an Aztec-compatible contract artifact. * @param compiled - Noir build output. @@ -112,35 +131,11 @@ function generateContractArtifact( contract: NoirContractCompilationArtifact, 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 debug = null; - // 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), + functions: contract.functions.map(generateFunctionArtifact), events: contract.events, - debug: parsedDebug, + fileMap: contract.file_map, aztecNrVersion, }; -} +} \ No newline at end of file diff --git a/yarn-project/types/src/contract_dao.test.ts b/yarn-project/types/src/contract_dao.test.ts index 5cfa8ea7e920..846058ec9939 100644 --- a/yarn-project/types/src/contract_dao.test.ts +++ b/yarn-project/types/src/contract_dao.test.ts @@ -1,9 +1,10 @@ import { CompleteAddress, EthAddress } from '@aztec/circuits.js'; -import { ABIParameterVisibility, ContractArtifact, FunctionSelector, FunctionType } from '@aztec/foundation/abi'; +import { ContractArtifact, FunctionSelector } from '@aztec/foundation/abi'; import { ContractDao } from './contract_dao.js'; import { randomContractArtifact } from './mocks.js'; + describe('ContractDao', () => { it('serializes / deserializes correctly', () => { const artifact = randomContractArtifact(); @@ -18,7 +19,7 @@ describe('ContractDao', () => { functions: [ { name: 'bar', - functionType: FunctionType.SECRET, + functionType: 'secret', isInternal: false, parameters: [ { @@ -26,14 +27,14 @@ describe('ContractDao', () => { type: { kind: 'field', }, - visibility: ABIParameterVisibility.PUBLIC, + visibility: 'public', }, { name: 'value', type: { kind: 'field', }, - visibility: ABIParameterVisibility.SECRET, + visibility: 'private', }, ], returnTypes: [], @@ -51,4 +52,4 @@ describe('ContractDao', () => { selector: new FunctionSelector(4138634513), }); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/types/src/contract_dao.ts b/yarn-project/types/src/contract_dao.ts index 4d70d0aca4d7..e32650bb623b 100644 --- a/yarn-project/types/src/contract_dao.ts +++ b/yarn-project/types/src/contract_dao.ts @@ -1,18 +1,13 @@ import { CompleteAddress, ContractFunctionDao } from '@aztec/circuits.js'; -import { - ContractArtifact, - DebugMetadata, - EventAbi, - FunctionDebugMetadata, - FunctionSelector, - FunctionType, - getFunctionDebugMetadata, -} from '@aztec/foundation/abi'; +import { ContractArtifact, EventAbi, FunctionSelector } from '@aztec/foundation/abi'; import { EthAddress } from '@aztec/foundation/eth-address'; import { prefixBufferWithLength } from '@aztec/foundation/serialize'; + + import { BufferReader, EncodedContractFunction } from './contract_data.js'; + /** * A contract Data Access Object (DAO). * Contains the contract's address, portal contract address, and an array of ContractFunctionDao objects. @@ -46,10 +41,6 @@ export class ContractDao implements ContractArtifact { return this.contractArtifact.events; } - get debug(): DebugMetadata | undefined { - return this.contractArtifact.debug; - } - getFunctionArtifact(selector: FunctionSelector): ContractFunctionDao | undefined { return this.functions.find(f => f.selector.equals(selector)); } @@ -58,10 +49,6 @@ export class ContractDao implements ContractArtifact { return this.functions.find(f => f.name === functionName); } - getFunctionDebugMetadataByName(functionName: string): FunctionDebugMetadata | undefined { - return getFunctionDebugMetadata(this, functionName); - } - toBuffer(): Buffer { // the contract artifact was originally emitted to a JSON file by Noir // should be safe to JSON.stringify it (i.e. it doesn't contain BigInts) @@ -91,7 +78,7 @@ export class ContractDao implements ContractArtifact { */ export function getNewContractPublicFunctions(newContract: ContractDao) { return newContract.functions - .filter(c => c.functionType === FunctionType.OPEN) + .filter(c => c.functionType === 'open') .map( fn => new EncodedContractFunction( @@ -100,4 +87,4 @@ export function getNewContractPublicFunctions(newContract: ContractDao) { Buffer.from(fn.bytecode, 'base64'), ), ); -} +} \ No newline at end of file diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 1583b24e83e3..de295104c53e 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -575,6 +575,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" @@ -2758,7 +2759,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@portal:../noir/packages/backend_barretenberg::locator=%40aztec%2Faztec3-packages%40workspace%3A." dependencies: - "@aztec/bb.js": 0.17.0 + "@aztec/bb.js": 0.16.0 "@noir-lang/types": 0.22.0 fflate: ^0.8.0 languageName: node From d7a3e39acde18210194b83175ba5d88fbb68d0c9 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 11 Jan 2024 11:16:54 -0300 Subject: [PATCH 7/7] Clean up noir-compiler --- yarn-project/noir-compiler/package.json | 5 +- yarn-project/noir-compiler/src/cli.ts | 21 -- .../src/cli/add_commander_actions.ts | 24 ++ .../add_noir_compiler_commander_actions.ts | 65 ----- yarn-project/noir-compiler/src/cli/codegen.ts | 54 ++--- .../noir-compiler/src/cli/compile_noir.ts | 133 ----------- yarn-project/noir-compiler/src/cli/index.ts | 2 +- .../noir-compiler/src/compile/nargo.ts | 100 -------- .../dependencies/dependency-manager.test.ts | 107 --------- .../noir/dependencies/dependency-manager.ts | 149 ------------ .../noir/dependencies/dependency-resolver.ts | 25 -- .../github-dependency-resolver.test.ts | 131 ---------- .../github-dependency-resolver.ts | 144 ----------- .../local-dependency-resolver.test.ts | 53 ----- .../dependencies/local-dependency-resolver.ts | 32 --- .../noir/file-manager/file-manager.test.ts | 90 ------- .../compile/noir/file-manager/file-manager.ts | 156 ------------ .../noir/file-manager/memfs-file-manager.ts | 58 ----- .../noir/file-manager/nodejs-file-manager.ts | 34 --- .../src/compile/noir/noir-wasm-compiler.ts | 223 ------------------ .../noir-compiler/src/compile/noir/package.ts | 130 ---------- .../src/contract-interface-gen/abi.ts | 120 ---------- .../src/contract-interface-gen/noir.ts | 4 +- .../programTypescript.ts | 189 --------------- .../{contractTypescript.ts => typescript.ts} | 0 yarn-project/noir-compiler/src/index.test.ts | 58 +---- yarn-project/noir-compiler/src/index.ts | 46 +--- yarn-project/noir-compiler/src/mocked_keys.ts | 2 - .../noir-compiler/src/noir_artifact.ts | 186 --------------- yarn-project/noir-compiler/tsconfig.json | 3 + 30 files changed, 61 insertions(+), 2283 deletions(-) delete mode 100644 yarn-project/noir-compiler/src/cli.ts create mode 100644 yarn-project/noir-compiler/src/cli/add_commander_actions.ts delete mode 100644 yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts delete mode 100644 yarn-project/noir-compiler/src/cli/compile_noir.ts delete mode 100644 yarn-project/noir-compiler/src/compile/nargo.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.test.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.test.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/file-manager/memfs-file-manager.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/file-manager/nodejs-file-manager.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts delete mode 100644 yarn-project/noir-compiler/src/compile/noir/package.ts 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} (100%) delete mode 100644 yarn-project/noir-compiler/src/mocked_keys.ts delete mode 100644 yarn-project/noir-compiler/src/noir_artifact.ts diff --git a/yarn-project/noir-compiler/package.json b/yarn-project/noir-compiler/package.json index af5b492ecfe8..6d564c7128fa 100644 --- a/yarn-project/noir-compiler/package.json +++ b/yarn-project/noir-compiler/package.json @@ -14,9 +14,6 @@ "name": "Aztec.nr compiler", "tsconfig": "./tsconfig.json" }, - "bin": { - "aztec-compile": "dest/cli.js" - }, "scripts": { "build": "yarn clean && tsc -b", "build:dev": "tsc -b --watch", @@ -47,10 +44,10 @@ "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", - "commander": "^9.0.0", "fs-extra": "^11.1.1", "lodash.camelcase": "^4.3.0", "lodash.capitalize": "^4.2.1", diff --git a/yarn-project/noir-compiler/src/cli.ts b/yarn-project/noir-compiler/src/cli.ts deleted file mode 100644 index 7416abed4cc2..000000000000 --- a/yarn-project/noir-compiler/src/cli.ts +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node -import { createConsoleLogger } from '@aztec/foundation/log'; - -import { Command } from 'commander'; - -import { addNoirCompilerCommanderActions } from './cli/add_noir_compiler_commander_actions.js'; - -const program = new Command(); -const log = createConsoleLogger('aztec:compiler-cli'); - -const main = async () => { - program.name('aztec-compile'); - addNoirCompilerCommanderActions(program, log); - await program.parseAsync(process.argv); -}; - -main().catch(err => { - log(`Error running command`); - log(err); - process.exit(1); -}); diff --git a/yarn-project/noir-compiler/src/cli/add_commander_actions.ts b/yarn-project/noir-compiler/src/cli/add_commander_actions.ts new file mode 100644 index 000000000000..27877bd6b5b7 --- /dev/null +++ b/yarn-project/noir-compiler/src/cli/add_commander_actions.ts @@ -0,0 +1,24 @@ +import { LogFn } from '@aztec/foundation/log'; + +import { Command } from 'commander'; +import { dirname } from 'path'; + +/** + * + */ +export function addCodegenCommanderAction(program: Command, _: LogFn = () => {}) { + program + .command('codegen') + .argument('', 'Path to the Noir ABI or project dir.') + .option('-o, --outdir ', 'Output folder for the generated code.') + .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, { 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, { ts, nr }); + }); +} 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 deleted file mode 100644 index 7c756965188e..000000000000 --- a/yarn-project/noir-compiler/src/cli/add_noir_compiler_commander_actions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { LogFn } from '@aztec/foundation/log'; - -import { Command } from 'commander'; -import { dirname } from 'path'; - -/** - * CLI options for configuring behavior - */ -interface Options { - // eslint-disable-next-line jsdoc/require-jsdoc - outdir: string; - // eslint-disable-next-line jsdoc/require-jsdoc - typescript: string | undefined; - // eslint-disable-next-line jsdoc/require-jsdoc - interface: string | undefined; - // eslint-disable-next-line jsdoc/require-jsdoc - compiler: string | undefined; -} - -/** - * - */ -export function addNoirCompilerCommanderActions(program: Command, log: LogFn = () => {}) { - addCodegenCommanderAction(program, log); -} - -/** - * - */ -export function addCompileCommanderAction(program: Command, log: LogFn = () => {}) { - program - .command('compile') - .argument('', 'Path to the bin or Aztec.nr project to compile') - .option('-o, --outdir ', 'Output folder for the binary artifacts, relative to the project path', 'target') - .option('-ts, --typescript ', 'Optional output folder for generating typescript wrappers', undefined) - .option('-i, --interface ', 'Optional output folder for generating an Aztec.nr contract interface', undefined) - .option('-c --compiler ', 'Which compiler to use. Either nargo or wasm.', 'wasm') - .description('Compiles the Noir Source in the target project') - - .action(async (projectPath: string, options: Options) => { - const { compileNoir } = await import('./compile_noir.js'); - await compileNoir(projectPath, options, log); - }); -} - -/** - * - */ -export function addCodegenCommanderAction(program: Command, _: LogFn = () => {}) { - program - .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 }) => { - 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); - }); -} diff --git a/yarn-project/noir-compiler/src/cli/codegen.ts b/yarn-project/noir-compiler/src/cli/codegen.ts index 9b163e2dd7cf..fe8dd17a9c25 100644 --- a/yarn-project/noir-compiler/src/cli/codegen.ts +++ b/yarn-project/noir-compiler/src/cli/codegen.ts @@ -1,59 +1,45 @@ -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'; + +/** Options for code generation */ +type GenerateCodeOpts = { /** Typescript */ ts: boolean; /** Noir */ nr: boolean }; -/** - * - */ -export function generateCode(outputPath: string, fileOrDirPath: string, includeDebug = false, ts = false, nr = false) { +/** Generate noir or typescript interface for a given contract. */ +export function generateCode(outputPath: string, fileOrDirPath: string, opts: GenerateCodeOpts) { 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); + generateFromNoirArtifact(outputPath, fullPath, opts); } } else if (stats.isFile()) { - generateFromNoirAbi(outputPath, fileOrDirPath, includeDebug, ts, nr); + generateFromNoirArtifact(outputPath, fileOrDirPath, opts); } } -/** - * - */ -function generateFromNoirAbi(outputPath: string, noirAbiPath: string, includeDebug: boolean, ts: boolean, nr: boolean) { - console.log(`READING noir abi path`, noirAbiPath, noirDebugPath); - 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 }); +/** Writes noir or typescript interfaces from a given noir artifact */ +function generateFromNoirArtifact(outputPath: string, noirArtifactPath: string, opts: GenerateCodeOpts) { + const { nr, ts } = opts; + const noirArtifact = JSON.parse(readFileSync(noirArtifactPath, 'utf8')); + const aztecArtifact = loadContractArtifact(noirArtifact); mkdirSync(outputPath, { recursive: true }); if (nr) { - const noirContract = generateNoirContractInterface(aztecAbi); - writeFileSync(`${outputPath}/${aztecAbi.name}.nr`, noirContract); + const noirContract = generateNoirContractInterface(aztecArtifact); + writeFileSync(`${outputPath}/${aztecArtifact.name}.nr`, noirContract); return; } - writeFileSync(`${outputPath}/${aztecAbi.name}.json`, JSON.stringify(aztecAbi, undefined, 2)); - if (ts) { - const tsWrapper = generateTypescriptContractInterface(aztecAbi, `./${aztecAbi.name}.json`); - writeFileSync(`${outputPath}/${aztecAbi.name}.ts`, tsWrapper); + const tsWrapper = generateTypescriptContractInterface(aztecArtifact, `./${aztecArtifact.name}.json`); + writeFileSync(`${outputPath}/${aztecArtifact.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/cli/compile_noir.ts b/yarn-project/noir-compiler/src/cli/compile_noir.ts deleted file mode 100644 index f87055464a95..000000000000 --- a/yarn-project/noir-compiler/src/cli/compile_noir.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { ContractArtifact } from '@aztec/foundation/abi'; -import { LogFn } from '@aztec/foundation/log'; - -import { mkdirSync, writeFileSync } from 'fs'; -import { mkdirpSync } from 'fs-extra'; -import path, { resolve } from 'path'; - -import { - ProgramArtifact, - compileUsingNargo, - compileUsingNoirWasm, - generateNoirContractInterface, - generateTypescriptContractInterface, - generateTypescriptProgramInterface, -} from '../index.js'; - -/** - * CLI options for configuring behavior - */ -interface Options { - // eslint-disable-next-line jsdoc/require-jsdoc - outdir: string; - // eslint-disable-next-line jsdoc/require-jsdoc - typescript: string | undefined; - // eslint-disable-next-line jsdoc/require-jsdoc - interface: string | undefined; - // eslint-disable-next-line jsdoc/require-jsdoc - compiler: string | undefined; -} -/** - * Registers a 'contract' command on the given commander program that compiles an Aztec.nr contract project. - * @param program - Commander program. - * @param log - Optional logging function. - * @returns The program with the command registered. - */ -export async function compileNoir(projectPath: string, options: Options, log: LogFn = () => {}) { - const { compiler } = options; - if (typeof projectPath !== 'string') { - throw new Error(`Missing project path argument`); - } - if (compiler !== 'nargo' && compiler !== 'wasm') { - throw new Error(`Invalid compiler: ${compiler}`); - } - - const compile = compiler === 'wasm' ? compileUsingNoirWasm : compileUsingNargo; - log(`Compiling ${projectPath} with ${compiler} backend...`); - const results = await compile(projectPath, { log }); - for (const result of results) { - generateOutput(projectPath, result, options, log); - } -} - -/** - * - * @param contract - output from compiler, to serialize locally. branch based on Contract vs Program - */ -function generateOutput( - projectPath: string, - _result: ContractArtifact | ProgramArtifact, - options: Options, - log: LogFn, -) { - const contract = _result as ContractArtifact; - if (contract.name) { - return generateContractOutput(projectPath, contract, options, log); - } else { - const program = _result as ProgramArtifact; - if (program.abi) { - return generateProgramOutput(projectPath, program, options, log); - } - } -} -/** - * - * @param program - output from compiler, to serialize locally - */ -function generateProgramOutput(projectPath: string, program: ProgramArtifact, options: Options, log: LogFn) { - const currentDir = process.cwd(); - const { outdir, typescript, interface: noirInterface } = options; - const artifactPath = resolve(projectPath, outdir, `${program.name ? program.name : 'main'}.json`); - log(`Writing ${program.name} artifact to ${path.relative(currentDir, artifactPath)}`); - mkdirSync(path.dirname(artifactPath), { recursive: true }); - writeFileSync(artifactPath, JSON.stringify(program, null, 2)); - - if (noirInterface) { - log(`noirInterface generation not implemented for programs`); - // not implemented - } - - if (typescript) { - // just need type definitions, since a lib has just one entry point - const tsPath = resolve(projectPath, typescript, `../types/${program.name}_types.ts`); - log(`Writing ${program.name} typescript types to ${path.relative(currentDir, tsPath)}`); - const tsWrapper = generateTypescriptProgramInterface(program.abi); - mkdirpSync(path.dirname(tsPath)); - writeFileSync(tsPath, tsWrapper); - } -} - -/** - * - * @param contract - output from compiler, to serialize locally - */ -function generateContractOutput(projectPath: string, contract: ContractArtifact, options: Options, log: LogFn) { - const currentDir = process.cwd(); - const { outdir, typescript, interface: noirInterface } = options; - const artifactPath = resolve(projectPath, outdir, `${contract.name}.json`); - log(`Writing ${contract.name} artifact to ${path.relative(currentDir, artifactPath)}`); - mkdirSync(path.dirname(artifactPath), { recursive: true }); - writeFileSync(artifactPath, JSON.stringify(contract, null, 2)); - - if (noirInterface) { - const noirInterfacePath = resolve(projectPath, noirInterface, `${contract.name}_interface.nr`); - log(`Writing ${contract.name} Aztec.nr external interface to ${path.relative(currentDir, noirInterfacePath)}`); - const noirWrapper = generateNoirContractInterface(contract); - mkdirpSync(path.dirname(noirInterfacePath)); - writeFileSync(noirInterfacePath, noirWrapper); - } - - if (typescript) { - const tsPath = resolve(projectPath, typescript, `${contract.name}.ts`); - log(`Writing ${contract.name} typescript interface to ${path.relative(currentDir, tsPath)}`); - let relativeArtifactPath = path.relative(path.dirname(tsPath), artifactPath); - if (relativeArtifactPath === `${contract.name}.json`) { - // relative path edge case, prepending ./ for local import - the above logic just does - // `${contract.name}.json`, which is not a valid import for a file in the same directory - relativeArtifactPath = `./${contract.name}.json`; - } - const tsWrapper = generateTypescriptContractInterface(contract, relativeArtifactPath); - mkdirpSync(path.dirname(tsPath)); - writeFileSync(tsPath, tsWrapper); - } -} diff --git a/yarn-project/noir-compiler/src/cli/index.ts b/yarn-project/noir-compiler/src/cli/index.ts index 4868340b60d7..40258654eeab 100644 --- a/yarn-project/noir-compiler/src/cli/index.ts +++ b/yarn-project/noir-compiler/src/cli/index.ts @@ -1 +1 @@ -export * from './add_noir_compiler_commander_actions.js'; +export * from './add_commander_actions.js'; diff --git a/yarn-project/noir-compiler/src/compile/nargo.ts b/yarn-project/noir-compiler/src/compile/nargo.ts deleted file mode 100644 index f1367b0930d1..000000000000 --- a/yarn-project/noir-compiler/src/compile/nargo.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { LogFn, createDebugLogger } from '@aztec/foundation/log'; - -import { execSync } from 'child_process'; -import { emptyDir } from 'fs-extra'; -import { readFile, readdir, stat, unlink } from 'fs/promises'; -import { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; - -import { NoirCommit, NoirTag } from '../index.js'; -import { NoirCompiledContract, NoirContractCompilationArtifacts, NoirDebugMetadata } from '../noir_artifact.js'; - -/** Compilation options */ -export type CompileOpts = { - /** Silence output from nargo compile. */ - quiet?: boolean; - /** Path to the nargo binary. */ - nargoBin?: string; - /** Logging function */ - log?: LogFn; -}; - -/** - * - */ -function getCurrentDir() { - if (typeof __dirname !== 'undefined') { - return __dirname; - } else { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return dirname(fileURLToPath(import.meta.url)); - } -} - -/** - * A class that compiles Aztec.nr contracts using nargo via the shell. - */ -export class NargoContractCompiler { - private log: LogFn; - constructor(private projectPath: string, private opts: CompileOpts = {}) { - this.log = opts.log ?? createDebugLogger('aztec:noir-compiler'); - } - - /** - * Compiles the contracts in projectPath and returns the Aztec.nr artifact. - * @returns Aztec.nr artifact of the compiled contracts. - */ - public async compile(): Promise { - const stdio = this.opts.quiet ? 'ignore' : 'inherit'; - const nargoBin = this.opts.nargoBin ?? getCurrentDir() + '/../../../../noir/target/release/nargo'; - const version = execSync(`${nargoBin} --version`, { cwd: this.projectPath, stdio: 'pipe' }).toString(); - this.checkNargoBinVersion(version.replace('\n', '')); - await emptyDir(this.getTargetFolder()); - execSync(`${nargoBin} compile`, { cwd: this.projectPath, stdio }); - return Promise.resolve(this.collectArtifacts()); - } - - private checkNargoBinVersion(version: string) { - if (!version.includes(NoirCommit)) { - this.log( - `Warning: the nargo version installed locally does not match the expected one. This may cause issues when compiling or deploying contracts. Consider updating your nargo or aztec-cli installation. \n- Expected: ${NoirTag} (git version hash: ${NoirCommit})\n- Found: ${version}`, - ); - } else if (!this.opts.quiet) { - this.log(`Using ${version}`); - } - } - - private async collectArtifacts(): Promise { - const contractArtifacts = new Map(); - const debugArtifacts = new Map(); - - for (const filename of await readdir(this.getTargetFolder())) { - const file = join(this.getTargetFolder(), filename); - if ((await stat(file)).isFile() && file.endsWith('.json')) { - if (filename.startsWith('debug_')) { - debugArtifacts.set( - filename.replace('debug_', ''), - JSON.parse((await readFile(file)).toString()) as NoirDebugMetadata, - ); - } else { - contractArtifacts.set(filename, JSON.parse((await readFile(file)).toString()) as NoirCompiledContract); - } - // Delete the file as it is not needed anymore and it can cause issues with prettier - await unlink(file); - } - } - - return [...contractArtifacts.entries()].map(([filename, contractArtifact]) => { - const debugArtifact = debugArtifacts.get(filename); - return { - contract: contractArtifact, - debug: debugArtifact, - }; - }); - } - - private getTargetFolder() { - return join(this.projectPath, 'target'); - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts deleted file mode 100644 index 5f9ba570d4d1..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { NoirDependencyConfig } from '@aztec/foundation/noir'; - -import { NoirPackage } from '../package.js'; -import { NoirDependencyManager } from './dependency-manager.js'; -import { NoirDependency, NoirDependencyResolver } from './dependency-resolver.js'; - -describe('DependencyManager', () => { - let manager: NoirDependencyManager; - - beforeEach(() => { - manager = new NoirDependencyManager( - [new TestDependencyResolver()], - new NoirPackage('/test_contract', '/test_contract/src', { - dependencies: { - lib1: { - path: '/lib1', - }, - lib2: { - path: '/lib2', - }, - lib3: { - path: '/lib3', - }, - }, - package: { - name: 'test_contract', - type: 'contract', - }, - }), - ); - }); - - it('successfully resolves dependencies', async () => { - await expect(manager.resolveDependencies()).resolves.toBeUndefined(); - }); - - it('resolves all libraries', async () => { - await manager.resolveDependencies(); - expect(manager.getPackageNames()).toEqual(['lib1', 'lib2', 'lib3']); - }); - - it('resolves root dependencies', async () => { - await manager.resolveDependencies(); - expect(manager.getEntrypointDependencies()).toEqual(['lib1', 'lib2', 'lib3']); - }); - - it('resolves library dependencies', async () => { - await manager.resolveDependencies(); - expect(manager.getLibraryDependencies()).toEqual({ - lib2: ['lib3'], - }); - }); -}); - -class TestDependencyResolver implements NoirDependencyResolver { - // eslint-disable-next-line require-await - public async resolveDependency(pkg: NoirPackage, dep: NoirDependencyConfig): Promise { - if (!('path' in dep)) { - return null; - } - - switch (dep.path) { - case '/lib1': - return { - version: '', - package: new NoirPackage('/lib1', '/lib1/src', { - dependencies: {}, - package: { - name: 'lib1', - type: 'lib', - }, - }), - }; - - case '/lib2': - return { - version: '', - package: new NoirPackage('/lib2', '/lib2/src', { - dependencies: { - lib3: { - path: '../lib3', - }, - }, - package: { - name: 'lib2', - type: 'lib', - }, - }), - }; - - case '/lib3': - return { - version: '', - package: new NoirPackage('/lib3', '/lib3/src', { - dependencies: {}, - package: { - name: 'lib3', - type: 'lib', - }, - }), - }; - - default: - throw new Error(); - } - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts deleted file mode 100644 index cc4fc179d44f..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-manager.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { LogFn, createDebugOnlyLogger } from '@aztec/foundation/log'; -import { NoirDependencyConfig } from '@aztec/foundation/noir'; - -import { join } from 'node:path'; - -import { NoirPackage } from '../package.js'; -import { NoirDependency, NoirDependencyResolver } from './dependency-resolver.js'; - -/** - * Noir Dependency Resolver - */ -export class NoirDependencyManager { - #entryPoint: NoirPackage; - #libraries = new Map(); - #dependencies = new Map(); - #log: LogFn; - #resolvers: readonly NoirDependencyResolver[]; - - /** - * Creates a new dependency resolver - * @param resolvers - A list of dependency resolvers to use - * @param entryPoint - The entry point of the project - */ - constructor(resolvers: readonly NoirDependencyResolver[] = [], entryPoint: NoirPackage) { - this.#log = createDebugOnlyLogger('noir:dependency-resolver'); - this.#resolvers = resolvers; - this.#entryPoint = entryPoint; - } - - /** - * Gets dependencies for the entry point - */ - public getEntrypointDependencies() { - return this.#dependencies.get('') ?? []; - } - - /** - * Get transitive libraries used by the package - */ - public getLibraries() { - return Array.from(this.#libraries.entries()); - } - - /** - * A map of library dependencies - */ - public getLibraryDependencies() { - const entries = Array.from(this.#dependencies.entries()); - return Object.fromEntries(entries.filter(([name]) => name !== '')); - } - - /** - * Resolves dependencies for a package. - */ - public async resolveDependencies(): Promise { - await this.#breadthFirstResolveDependencies(); - } - - /** - * Gets the version of a dependency in the dependency tree - * @param name - Dependency name - * @returns The dependency's version - */ - public getVersionOf(name: string): string | undefined { - const dep = this.#libraries.get(name); - return dep?.version; - } - - async #breadthFirstResolveDependencies(): Promise { - /** Represents a package to resolve dependencies for */ - type Job = { - /** Package name */ - packageName: string; - /** The package location */ - noirPackage: NoirPackage; - }; - - const queue: Job[] = [ - { - packageName: '', - noirPackage: this.#entryPoint, - }, - ]; - - while (queue.length > 0) { - const { packageName, noirPackage } = queue.shift()!; - for (const [name, config] of Object.entries(noirPackage.getDependencies())) { - // TODO what happens if more than one package has the same name but different versions? - if (this.#libraries.has(name)) { - this.#log(`skipping already resolved dependency ${name}`); - this.#dependencies.set(packageName, [...(this.#dependencies.get(packageName) ?? []), name]); - - continue; - } - const dependency = await this.#resolveDependency(noirPackage, config); - if (dependency.package.getType() !== 'lib') { - this.#log(`Non-library package ${name}`, config); - throw new Error(`Dependency ${name} is not a library`); - } - - this.#libraries.set(name, dependency); - this.#dependencies.set(packageName, [...(this.#dependencies.get(packageName) ?? []), name]); - - queue.push({ - noirPackage: dependency.package, - packageName: name, - }); - } - } - } - - async #resolveDependency(pkg: NoirPackage, config: NoirDependencyConfig): Promise { - let dependency: NoirDependency | null = null; - for (const resolver of this.#resolvers) { - dependency = await resolver.resolveDependency(pkg, config); - if (dependency) { - break; - } - } - - if (!dependency) { - throw new Error('Dependency not resolved'); - } - - return dependency; - } - - /** - * Gets the names of the crates in this dependency list - */ - public getPackageNames() { - return [...this.#libraries.keys()]; - } - - /** - * Looks up a dependency - * @param sourceId - The source being resolved - * @returns The path to the resolved file - */ - public findFile(sourceId: string): string | null { - const [lib, ...path] = sourceId.split('/').filter(x => x); - const dep = this.#libraries.get(lib); - if (dep) { - return join(dep.package.getSrcPath(), ...path); - } else { - return null; - } - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts deleted file mode 100644 index f8a955aa2c7b..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/dependency-resolver.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NoirDependencyConfig } from '@aztec/foundation/noir'; - -import { NoirPackage } from '../package.js'; - -/** - * A Noir dependency - */ -export type NoirDependency = { - /** version string as determined by the resolver */ - version?: string; - /** the actual package source code */ - package: NoirPackage; -}; - -/** - * Resolves a dependency for a package. - */ -export interface NoirDependencyResolver { - /** - * Resolve a dependency for a package. - * @param pkg - The package to resolve dependencies for - * @param dep - The dependency config to resolve - */ - resolveDependency(pkg: NoirPackage, dep: NoirDependencyConfig): Promise; -} diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts deleted file mode 100644 index d171a6ccf6b1..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { NoirGitDependencyConfig } from '@aztec/foundation/noir'; -import { fileURLToPath } from '@aztec/foundation/url'; - -import { jest } from '@jest/globals'; -import { Volume, createFsFromVolume } from 'memfs'; -import { readFile } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; - -import { FileManager } from '../file-manager/file-manager.js'; -import { createMemFSFileManager } from '../file-manager/memfs-file-manager.js'; -import { NoirPackage } from '../package.js'; -import { NoirDependencyResolver } from './dependency-resolver.js'; -import { GithubDependencyResolver, resolveGithubCodeArchive, safeFilename } from './github-dependency-resolver.js'; - -const fixtures = join(dirname(fileURLToPath(import.meta.url)), '../../../fixtures'); - -describe('GithubDependencyResolver', () => { - let resolver: NoirDependencyResolver; - let fm: FileManager; - let pkg: NoirPackage; - let libDependency: NoirGitDependencyConfig; - let fetchMock: jest.SpiedFunction; - - beforeEach(() => { - fm = createMemFSFileManager(createFsFromVolume(new Volume()), '/'); - - libDependency = { - git: 'https://github.com/example/repo', - tag: 'v1.0.0', - }; - - pkg = new NoirPackage('/test_contract', '/test_contract/src', { - dependencies: { - // eslint-disable-next-line camelcase - test_lib: libDependency, - }, - package: { - name: 'test_contract', - type: 'contract', - }, - }); - - resolver = new GithubDependencyResolver(fm); - - // cut off outside access - fetchMock = jest.spyOn(globalThis, 'fetch').mockImplementation(() => { - throw new Error(); - }); - }); - - afterEach(() => { - fetchMock.mockRestore(); - }); - - it("returns null if it can't resolve a dependency", async () => { - const dep = await resolver.resolveDependency(pkg, { - path: '/test_lib', - }); - - expect(dep).toBeNull(); - }); - - it('resolves Github dependency', async () => { - fetchMock.mockResolvedValueOnce(new Response(await readFile(join(fixtures, 'test_lib.zip')), { status: 200 })); - const lib = await resolver.resolveDependency(pkg, libDependency); - expect(lib).toBeDefined(); - expect(lib!.version).toEqual(libDependency.tag); - expect(fm.hasFileSync(lib!.package.getEntryPointPath())).toBe(true); - }); - - it.each<[NoirGitDependencyConfig, 'zip' | 'tar', string]>([ - [ - { - git: 'https://github.com/example/lib.nr', - tag: 'v1.0.0', - }, - 'zip', - 'https://github.com/example/lib.nr/archive/v1.0.0.zip', - ], - [ - { - git: 'https://github.com/example/lib.nr', - tag: 'v1.0.0', - }, - 'tar', - 'https://github.com/example/lib.nr/archive/v1.0.0.tar.gz', - ], - [ - { - git: 'https://github.com/example/lib.nr', - tag: 'HEAD', - }, - 'zip', - 'https://github.com/example/lib.nr/archive/HEAD.zip', - ], - [ - { - git: 'https://github.com/example/lib.nr', - tag: 'HEAD', - }, - 'tar', - 'https://github.com/example/lib.nr/archive/HEAD.tar.gz', - ], - ])('resolves to the correct code archive URL', (dep, format, href) => { - const archiveUrl = resolveGithubCodeArchive(dep, format); - expect(archiveUrl.href).toEqual(href); - }); - - it.each([ - { git: 'https://github.com/', tag: 'v1' }, - { git: 'https://github.com/foo', tag: 'v1' }, - { git: 'https://example.com', tag: 'v1' }, - ])('throws if the Github URL is invalid', dep => { - expect(() => resolveGithubCodeArchive(dep, 'zip')).toThrow(); - }); - - it.each([ - ['main', 'main'], - ['v1.0.0', 'v1.0.0'], - ['../../../etc/passwd', '.._.._.._etc_passwd'], - ['/etc/passwd', 'etc_passwd'], - ['/SomeOrg/some-repo@v1.0.0', 'SomeOrg_some-repo@v1.0.0'], - ['SomeOrg/some-repo@v1.0.0', 'SomeOrg_some-repo@v1.0.0'], - ])('generates safe file names', (value, expected) => { - expect(safeFilename(value)).toEqual(expected); - }); - - it.each([''])('rejects invalid values', value => { - expect(() => safeFilename(value)).toThrow(); - }); -}); diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts deleted file mode 100644 index f7115b562588..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/github-dependency-resolver.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { createDebugOnlyLogger } from '@aztec/foundation/log'; -import { NoirDependencyConfig, NoirGitDependencyConfig } from '@aztec/foundation/noir'; - -import { delimiter, join, sep } from 'node:path'; -import { unzip } from 'unzipit'; - -import { FileManager } from '../file-manager/file-manager.js'; -import { NoirPackage } from '../package.js'; -import { NoirDependency, NoirDependencyResolver } from './dependency-resolver.js'; - -/** - * Downloads dependencies from github - */ -export class GithubDependencyResolver implements NoirDependencyResolver { - #fm: FileManager; - #log = createDebugOnlyLogger('aztec:compile:github-dependency-resolver'); - - constructor(fm: FileManager) { - this.#fm = fm; - } - - /** - * Resolves a dependency from github. Returns null if URL is for a different website. - * @param _pkg - The package to resolve the dependency for - * @param dependency - The dependency configuration - * @returns asd - */ - async resolveDependency(_pkg: NoirPackage, dependency: NoirDependencyConfig): Promise { - // TODO accept ssh urls? - // TODO github authentication? - if (!('git' in dependency) || !dependency.git.startsWith('https://github.com')) { - return null; - } - - const archivePath = await this.#fetchZipFromGithub(dependency); - const libPath = await this.#extractZip(dependency, archivePath); - return { - version: dependency.tag, - package: await NoirPackage.open(libPath, this.#fm), - }; - } - - async #fetchZipFromGithub(dependency: Pick): Promise { - if (!dependency.git.startsWith('https://github.com')) { - throw new Error('Only github dependencies are supported'); - } - - const url = resolveGithubCodeArchive(dependency, 'zip'); - const localArchivePath = join('archives', safeFilename(url.pathname)); - - // TODO should check signature before accepting any file - if (this.#fm.hasFileSync(localArchivePath)) { - this.#log('using cached archive', { url: url.href, path: localArchivePath }); - return localArchivePath; - } - - const response = await fetch(url, { - method: 'GET', - }); - - if (!response.ok || !response.body) { - throw new Error(`Failed to fetch ${url}: ${response.statusText}`); - } - - const tmpFile = localArchivePath + '.tmp'; - await this.#fm.writeFile(tmpFile, response.body); - await this.#fm.moveFile(tmpFile, localArchivePath); - - return localArchivePath; - } - - async #extractZip(dependency: NoirGitDependencyConfig, archivePath: string): Promise { - const gitUrl = new URL(dependency.git); - // extract the archive to this location - const extractLocation = join('libs', safeFilename(gitUrl.pathname + '@' + (dependency.tag ?? 'HEAD'))); - - // where we expect to find this package after extraction - // it might already exist if the archive got unzipped previously - const packagePath = join(extractLocation, dependency.directory ?? ''); - - if (this.#fm.hasFileSync(packagePath)) { - this.#log(`Using existing package at ${packagePath}`); - return packagePath; - } - - const { entries } = await unzip(await this.#fm.readFile(archivePath)); - - // extract to a temporary directory, then move it to the final location - // TODO empty the temp directory first - const tmpExtractLocation = extractLocation + '.tmp'; - for (const entry of Object.values(entries)) { - if (entry.isDirectory) { - continue; - } - - // remove the first path segment, because it'll be the archive name - const name = stripSegments(entry.name, 1); - const path = join(tmpExtractLocation, name); - await this.#fm.writeFile(path, (await entry.blob()).stream()); - } - - await this.#fm.moveFile(tmpExtractLocation, extractLocation); - - return packagePath; - } -} - -/** - * Strips the first n segments from a path - */ -function stripSegments(path: string, count: number): string { - const segments = path.split(sep).filter(Boolean); - return segments.slice(count).join(sep); -} - -/** - * Returns a safe filename for a value - * @param val - The value to convert - */ -export function safeFilename(val: string): string { - if (!val) { - throw new Error('invalid value'); - } - - return val.replaceAll(sep, '_').replaceAll(delimiter, '_').replace(/^_+/, ''); -} - -/** - * Resolves a dependency's archive URL. - * @param dependency - The dependency configuration - * @returns The URL to the library archive - */ -export function resolveGithubCodeArchive(dependency: NoirGitDependencyConfig, format: 'zip' | 'tar'): URL { - const gitUrl = new URL(dependency.git); - const [owner, repo] = gitUrl.pathname.slice(1).split('/'); - const ref = dependency.tag ?? 'HEAD'; - const extension = format === 'zip' ? 'zip' : 'tar.gz'; - - if (!owner || !repo || gitUrl.hostname !== 'github.com') { - throw new Error('Invalid Github repository URL'); - } - - return new URL(`https://github.com/${owner}/${repo}/archive/${ref}.${extension}`); -} diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.test.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.test.ts deleted file mode 100644 index 5538a571038c..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { fileURLToPath } from '@aztec/foundation/url'; - -import { createFsFromVolume } from 'memfs'; -import { Volume } from 'memfs/lib/volume.js'; -import { readFile } from 'node:fs/promises'; -import { dirname, join } from 'node:path'; - -import { FileManager } from '../file-manager/file-manager.js'; -import { createMemFSFileManager } from '../file-manager/memfs-file-manager.js'; -import { NoirPackage } from '../package.js'; -import { NoirDependencyResolver } from './dependency-resolver.js'; -import { LocalDependencyResolver } from './local-dependency-resolver.js'; - -describe('DependencyResolver', () => { - let resolver: NoirDependencyResolver; - let fm: FileManager; - let pkg: NoirPackage; - - beforeEach(async () => { - const fixtures = join(dirname(fileURLToPath(import.meta.url)), '../../../fixtures'); - const memFS = createFsFromVolume(new Volume()); - memFS.mkdirSync('/test_contract/src', { recursive: true }); - memFS.mkdirSync('/test_lib/src', { recursive: true }); - memFS.writeFileSync('/test_contract/Nargo.toml', await readFile(join(fixtures, 'test_contract/Nargo.toml'))); - memFS.writeFileSync('/test_contract/src/main.nr', await readFile(join(fixtures, 'test_contract/src/main.nr'))); - memFS.writeFileSync('/test_lib/Nargo.toml', await readFile(join(fixtures, 'test_lib/Nargo.toml'))); - memFS.writeFileSync('/test_lib/src/lib.nr', await readFile(join(fixtures, 'test_lib/src/lib.nr'))); - - fm = createMemFSFileManager(memFS, '/'); - - pkg = await NoirPackage.open('/test_contract', fm); - resolver = new LocalDependencyResolver(fm); - }); - - it("returns null if it can't resolve a dependency", async () => { - const dep = await resolver.resolveDependency(pkg, { - git: 'git@some-git-host', - directory: '/', - tag: 'v1.0.0', - }); - - expect(dep).toBeNull(); - }); - - it.each(['../test_contract', '/test_contract'])('resolves a known dependency', async path => { - const lib = await resolver.resolveDependency(pkg, { - path, - }); - expect(lib).toBeDefined(); - expect(lib!.version).toBeUndefined(); - expect(fm.hasFileSync(lib!.package.getEntryPointPath())).toBe(true); - }); -}); diff --git a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts b/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts deleted file mode 100644 index ce86063442a4..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/dependencies/local-dependency-resolver.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { NoirDependencyConfig } from '@aztec/foundation/noir'; - -import { isAbsolute, join } from 'path'; - -import { FileManager } from '../file-manager/file-manager.js'; -import { NoirPackage } from '../package.js'; -import { NoirDependency, NoirDependencyResolver } from './dependency-resolver.js'; - -/** - * Resolves dependencies on-disk, relative to current package - */ -export class LocalDependencyResolver implements NoirDependencyResolver { - #fm: FileManager; - - constructor(fm: FileManager) { - this.#fm = fm; - } - - async resolveDependency(parent: NoirPackage, config: NoirDependencyConfig): Promise { - if ('path' in config) { - const parentPath = parent.getPackagePath(); - const dependencyPath = isAbsolute(config.path) ? config.path : join(parentPath, config.path); - return { - // unknown version, Nargo.toml doesn't have a version field - version: undefined, - package: await NoirPackage.open(dependencyPath, this.#fm), - }; - } else { - return null; - } - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.test.ts b/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.test.ts deleted file mode 100644 index cf039e85dca9..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Volume, createFsFromVolume } from 'memfs'; -import { existsSync, mkdtempSync, rmSync } from 'node:fs'; -import * as fs from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; - -import { FileManager, FileSystem } from './file-manager.js'; -import { createMemFSFileManager } from './memfs-file-manager.js'; - -const memFS = (): { fm: FileManager; teardown: () => void } => { - const fm = createMemFSFileManager(createFsFromVolume(new Volume()), '/'); - return { - fm, - // no-op, it's all in memory - teardown: () => {}, - }; -}; - -const nodeFM = (): { fm: FileManager; teardown: () => void } => { - const fileSystem: FileSystem = { - existsSync: existsSync, - mkdir: async (dir: string, opts?: { recursive: boolean }) => { - await fs.mkdir(dir, opts); - }, - writeFile: fs.writeFile, - readFile: fs.readFile, - rename: fs.rename, - readdir: fs.readdir, - }; - - const dir = mkdtempSync(join(tmpdir(), 'noir-compiler-test')); - const fm = new FileManager(fileSystem, dir); - - return { - fm, - teardown: () => { - rmSync(dir, { - recursive: true, - }); - }, - }; -}; - -/** - * Declare the default test suite for a file manager - * @param setup - Function to setup a file manager - * @param teardown - Optional function to call at the end of the test - */ -describe.each([memFS, nodeFM])('FileManager', setup => { - let fm: FileManager; - let testFileContent: string; - let testFileBytes: Uint8Array; - let teardown: () => void; - - beforeEach(() => { - ({ fm, teardown } = setup()); - testFileContent = 'foo'; - testFileBytes = new TextEncoder().encode(testFileContent); - }); - - afterEach(() => { - return teardown?.(); - }); - - it('saves files and correctly reads bytes back', async () => { - await fm.writeFile('test.txt', new Blob([testFileBytes]).stream()); - await expect(fm.readFile('test.txt')).resolves.toEqual(testFileBytes); - }); - - it('saves files and correctly reads UTF-8 string back', async () => { - await fm.writeFile('test.txt', new Blob([testFileBytes]).stream()); - await expect(fm.readFile('test.txt', 'utf-8')).resolves.toEqual(testFileContent); - }); - - it('correctly checks if file exists or not', async () => { - expect(fm.hasFileSync('test.txt')).toBe(false); - await fm.writeFile('test.txt', new Blob([testFileBytes]).stream()); - expect(fm.hasFileSync('test.txt')).toBe(true); - }); - - it('moves files', async () => { - await fm.writeFile('test.txt.tmp', new Blob([testFileBytes]).stream()); - expect(fm.hasFileSync('test.txt.tmp')).toBe(true); - - await fm.moveFile('test.txt.tmp', 'test.txt'); - - expect(fm.hasFileSync('test.txt.tmp')).toBe(false); - expect(fm.hasFileSync('test.txt')).toBe(true); - }); -}); diff --git a/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.ts b/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.ts deleted file mode 100644 index 5b502bf01bc7..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/file-manager/file-manager.ts +++ /dev/null @@ -1,156 +0,0 @@ -import path, { dirname, isAbsolute, join } from 'path'; - -/** - * A file system interface that matches the node fs module. - */ -export interface FileSystem { - /** Checks if the file exists */ - existsSync: (path: string) => boolean; - /** Creates a directory structure */ - mkdir: ( - dir: string, - opts?: { - /** Create parent directories as needed */ - recursive: boolean; - }, - ) => Promise; - /** Writes a file */ - writeFile: (path: string, data: Uint8Array) => Promise; - /** Reads a file */ - readFile: (path: string, encoding?: 'utf-8') => Promise; - /** Renames a file */ - rename: (oldPath: string, newPath: string) => Promise; - /** Reads a directory */ - readdir: ( - path: string, - options?: { - /** Traverse child directories recursively */ - recursive: boolean; - }, - ) => Promise; -} - -/** - * A file manager that writes file to a specific directory but reads globally. - */ -export class FileManager { - #fs: FileSystem; - #dataDir: string; - - constructor(fs: FileSystem, dataDir: string) { - this.#fs = fs; - this.#dataDir = dataDir; - } - - /** - * Saves a file to the data directory. - * @param name - File to save - * @param stream - File contents - */ - public async writeFile(name: string, stream: ReadableStream): Promise { - if (isAbsolute(name)) { - throw new Error("can't write absolute path"); - } - - const path = this.#getPath(name); - const chunks: Uint8Array[] = []; - const reader = stream.getReader(); - - while (true) { - const { done, value } = await reader.read(); - if (done) { - break; - } - - chunks.push(value); - } - - const file = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0)); - let offset = 0; - for (const chunk of chunks) { - file.set(chunk, offset); - offset += chunk.length; - } - - await this.#fs.mkdir(dirname(path), { recursive: true }); - await this.#fs.writeFile(this.#getPath(path), file); - } - - /** - * Reads a file from the filesystem and returns a buffer - * Saves a file to the data directory. - * @param oldName - File to save - * @param newName - File contents - */ - async moveFile(oldName: string, newName: string) { - if (isAbsolute(oldName) || isAbsolute(newName)) { - throw new Error("can't move absolute path"); - } - - const oldPath = this.#getPath(oldName); - const newPath = this.#getPath(newName); - - await this.#fs.mkdir(dirname(newPath), { recursive: true }); - await this.#fs.rename(oldPath, newPath); - } - - /** - * Reads a file from the disk and returns a buffer - * @param name - File to read - */ - public async readFile(name: string): Promise; - /** - * Reads a file from the filesystem as a string - * @param name - File to read - * @param encoding - Encoding to use - */ - public async readFile(name: string, encoding: 'utf-8'): Promise; - /** - * Reads a file from the filesystem - * @param name - File to read - * @param encoding - Encoding to use - */ - public async readFile(name: string, encoding?: 'utf-8'): Promise { - const path = this.#getPath(name); - const data = await this.#fs.readFile(path, encoding); - - if (!encoding) { - return typeof data === 'string' - ? new TextEncoder().encode(data) // this branch shouldn't be hit, but just in case - : new Uint8Array(data.buffer, data.byteOffset, data.byteLength / Uint8Array.BYTES_PER_ELEMENT); - } - - return data; - } - - /** - * Checks if a file exists and is accessible - * @param name - File to check - */ - public hasFileSync(name: string): boolean { - return this.#fs.existsSync(this.#getPath(name)); - } - - #getPath(name: string) { - return isAbsolute(name) ? name : join(this.#dataDir, name); - } - - /** - * Reads a file from the filesystem - * @param dir - File to read - * @param options - Readdir options - */ - public async readdir( - dir: string, - options?: { - /** - * Traverse child directories recursively - */ - recursive: boolean; - }, - ) { - const dirPath = this.#getPath(dir); - const files = await this.#fs.readdir(dirPath, options); - return files.map(file => path.join(dirPath, file)); - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/file-manager/memfs-file-manager.ts b/yarn-project/noir-compiler/src/compile/noir/file-manager/memfs-file-manager.ts deleted file mode 100644 index cbf46def8808..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/file-manager/memfs-file-manager.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { IFs, fs } from 'memfs'; -import { IDirent } from 'memfs/lib/node/types/misc.js'; - -import { FileManager } from './file-manager.js'; - -/** - * Creates a new FileManager instance based on a MemFS instance - * @param memFS - the memfs backing instance - * @param dataDir - where to store files - */ -export function createMemFSFileManager(memFS: IFs = fs, dataDir = '/'): FileManager { - const readdirRecursive = async (dir: string): Promise => { - const contents = await memFS.promises.readdir(dir); - let files: string[] = []; - for (const handle in contents) { - if ((handle as unknown as IDirent).isFile()) { - files.push(handle.toString()); - } else { - files = files.concat(await readdirRecursive(handle.toString())); - } - } - return files; - }; - return new FileManager( - { - existsSync: memFS.existsSync.bind(memFS), - mkdir: async ( - dir: string, - options?: { - /** - * Traverse child directories - */ - recursive: boolean; - }, - ) => { - await memFS.promises.mkdir(dir, options); - }, - writeFile: memFS.promises.writeFile.bind(memFS), - rename: memFS.promises.rename.bind(memFS), - readFile: memFS.promises.readFile.bind(memFS), - readdir: async ( - dir: string, - options?: { - /** - * Traverse child directories - */ - recursive: boolean; - }, - ) => { - if (options?.recursive) { - return readdirRecursive(dir); - } - return (await memFS.promises.readdir(dir)).map(handles => handles.toString()); - }, - }, - dataDir, - ); -} diff --git a/yarn-project/noir-compiler/src/compile/noir/file-manager/nodejs-file-manager.ts b/yarn-project/noir-compiler/src/compile/noir/file-manager/nodejs-file-manager.ts deleted file mode 100644 index d001e0c072e4..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/file-manager/nodejs-file-manager.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { existsSync } from 'node:fs'; -import * as fs from 'node:fs/promises'; - -import { FileManager } from './file-manager.js'; - -/** - * Creates a new FileManager instance based on nodejs fs - * @param dataDir - where to store files - */ -export function createNodejsFileManager(dataDir: string): FileManager { - return new FileManager( - { - ...fs, - ...{ - // ExistsSync is not available in the fs/promises module - existsSync, - // This is added here because the node types are not compatible with the FileSystem type for mkdir - // Typescripts tries to use a different variant of the function that is not the one that has the optional options. - mkdir: async ( - dir: string, - opts?: { - /** - * Traverse child directories - */ - recursive: boolean; - }, - ) => { - await fs.mkdir(dir, opts); - }, - }, - }, - dataDir, - ); -} diff --git a/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts b/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts deleted file mode 100644 index 8abd9e9471aa..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/noir-wasm-compiler.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { LogFn, createDebugLogger } from '@aztec/foundation/log'; - -import { CompileError, PathToFileSourceMap, compile } from '@noir-lang/noir_wasm'; -import { isAbsolute } from 'node:path'; - -import { NoirCompilationResult, NoirProgramCompilationArtifacts } from '../../noir_artifact.js'; -import { NoirDependencyManager } from './dependencies/dependency-manager.js'; -import { GithubDependencyResolver as GithubCodeArchiveDependencyResolver } from './dependencies/github-dependency-resolver.js'; -import { LocalDependencyResolver } from './dependencies/local-dependency-resolver.js'; -import { FileManager } from './file-manager/file-manager.js'; -import { NoirPackage } from './package.js'; - -/** Compilation options */ -export type NoirWasmCompileOptions = { - /** Logging function */ - log: LogFn; - /** Log debugging information through this function */ - debugLog?: LogFn; -}; - -/** - * Noir Package Compiler - */ -export class NoirWasmContractCompiler { - #log: LogFn; - #debugLog: LogFn; - #package: NoirPackage; - #fm: FileManager; - #dependencyManager: NoirDependencyManager; - - private constructor( - entrypoint: NoirPackage, - dependencyManager: NoirDependencyManager, - fileManager: FileManager, - opts: NoirWasmCompileOptions, - ) { - this.#log = opts.log; - this.#debugLog = opts.debugLog ?? createDebugLogger('aztec:noir-compiler:wasm'); - this.#package = entrypoint; - this.#fm = fileManager; - this.#dependencyManager = dependencyManager; - } - - /** - * Creates a new compiler instance. - * @param fileManager - The file manager to use - * @param projectPath - The path to the project - * @param opts - Compilation options - */ - public static async new(fileManager: FileManager, projectPath: string, opts: NoirWasmCompileOptions) { - if (!isAbsolute(projectPath)) { - throw new Error('projectPath must be an absolute path'); - } - - const noirPackage = await NoirPackage.open(projectPath, fileManager); - - const dependencyManager = new NoirDependencyManager( - [ - new LocalDependencyResolver(fileManager), - new GithubCodeArchiveDependencyResolver(fileManager), - // TODO support actual Git repositories - ], - noirPackage, - ); - - return new NoirWasmContractCompiler(noirPackage, dependencyManager, fileManager, opts); - } - - /** - * Gets the version of Aztec.nr that was used compiling this contract. - */ - public getResolvedAztecNrVersion() { - // TODO eliminate this hardcoded library name! - // see docs/docs/dev_docs/contracts/setup.md - return this.#dependencyManager.getVersionOf('aztec'); - } - - /** - * Compile EntryPoint - */ - public async compile(): Promise { - if (this.#package.getType() === 'contract') { - this.#debugLog(`Compiling Contract at ${this.#package.getEntryPointPath()}`); - return await this.compileContract(); - } else if (this.#package.getType() === 'bin') { - this.#debugLog(`Compiling Program at ${this.#package.getEntryPointPath()}`); - return await this.compileProgram(); - } else { - this.#log( - `Compile skipped - only supports compiling "contract" and "bin" package types (${this.#package.getType()})`, - ); - return []; - } - } - - /** - * Compiles the Program. - */ - public async compileProgram(): Promise { - await this.#dependencyManager.resolveDependencies(); - this.#debugLog(`Dependencies: ${this.#dependencyManager.getPackageNames().join(', ')}`); - - try { - const isContract: boolean = false; - - const entrypoint = this.#package.getEntryPointPath(); - const deps = { - /* eslint-disable camelcase */ - root_dependencies: this.#dependencyManager.getEntrypointDependencies(), - library_dependencies: this.#dependencyManager.getLibraryDependencies(), - /* eslint-enable camelcase */ - }; - const packageSources = await this.#package.getSources(this.#fm); - const librarySources = ( - await Promise.all( - this.#dependencyManager - .getLibraries() - .map(async ([alias, library]) => await library.package.getSources(this.#fm, alias)), - ) - ).flat(); - const sourceMap: PathToFileSourceMap = new PathToFileSourceMap(); - [...packageSources, ...librarySources].forEach(sourceFile => { - sourceMap.add_source_code(sourceFile.path, sourceFile.source); - }); - const result = compile(entrypoint, isContract, deps, sourceMap); - - if (!('program' in result)) { - throw new Error('No program found in compilation result'); - } - - return [{ name: this.#package.getNoirPackageConfig().package.name, ...result }]; - } catch (err) { - if (err instanceof Error && err.name === 'CompileError') { - await this.#processCompileError(err as CompileError); - } - - throw err; - } - } - - /** - * Compiles the Contract. - */ - public async compileContract(): Promise { - if (!(this.#package.getType() === 'contract' || this.#package.getType() === 'bin')) { - this.#log( - `Compile skipped - only supports compiling "contract" and "bin" package types (${this.#package.getType()})`, - ); - return []; - } - this.#debugLog(`Compiling contract at ${this.#package.getEntryPointPath()}`); - await this.#dependencyManager.resolveDependencies(); - this.#debugLog(`Dependencies: ${this.#dependencyManager.getPackageNames().join(', ')}`); - - try { - const isContract: boolean = true; - - const entrypoint = this.#package.getEntryPointPath(); - const deps = { - /* eslint-disable camelcase */ - root_dependencies: this.#dependencyManager.getEntrypointDependencies(), - library_dependencies: this.#dependencyManager.getLibraryDependencies(), - /* eslint-enable camelcase */ - }; - const packageSources = await this.#package.getSources(this.#fm); - const librarySources = ( - await Promise.all( - this.#dependencyManager - .getLibraries() - .map(async ([alias, library]) => await library.package.getSources(this.#fm, alias)), - ) - ).flat(); - const sourceMap: PathToFileSourceMap = new PathToFileSourceMap(); - [...packageSources, ...librarySources].forEach(sourceFile => { - sourceMap.add_source_code(sourceFile.path, sourceFile.source); - }); - const result = compile(entrypoint, isContract, deps, sourceMap); - - if (!('contract' in result)) { - throw new Error('No contract found in compilation result'); - } - - return [result]; - } catch (err) { - if (err instanceof Error && err.name === 'CompileError') { - await this.#processCompileError(err as CompileError); - throw new Error('Compilation failed'); - } - - throw err; - } - } - - async #resolveFile(path: string) { - try { - const libFile = this.#dependencyManager.findFile(path); - return await this.#fm.readFile(libFile ?? path, 'utf-8'); - } catch (err) { - return ''; - } - } - - async #processCompileError(err: CompileError): Promise { - for (const diag of err.diagnostics) { - this.#log(` ${diag.message}`); - const contents = await this.#resolveFile(diag.file); - const lines = contents.split('\n'); - const lineOffsets = lines.reduce((accum, _, idx) => { - if (idx === 0) { - accum.push(0); - } else { - accum.push(accum[idx - 1] + lines[idx - 1].length + 1); - } - return accum; - }, []); - - for (const secondary of diag.secondaries) { - const errorLine = lineOffsets.findIndex(offset => offset > secondary.start); - this.#log(` ${diag.file}:${errorLine}: ${contents.slice(secondary.start, secondary.end)}`); - } - } - } -} diff --git a/yarn-project/noir-compiler/src/compile/noir/package.ts b/yarn-project/noir-compiler/src/compile/noir/package.ts deleted file mode 100644 index f144830acd9b..000000000000 --- a/yarn-project/noir-compiler/src/compile/noir/package.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { NoirDependencyConfig, NoirPackageConfig, parseNoirPackageConfig } from '@aztec/foundation/noir'; - -import { parse } from '@iarna/toml'; -import { join } from 'node:path'; - -import { FileManager } from './file-manager/file-manager.js'; - -const CONFIG_FILE_NAME = 'Nargo.toml'; -const SOURCE_EXTENSIONS = ['.nr']; - -/** - * An array of sources for a package - */ -type SourceList = Array<{ - /** - * The source path, taking into account modules and aliases. Eg: mylib/mod/mysource.nr - */ - path: string; - /** - * Resolved source plaintext - */ - source: string; -}>; - -/** - * A Noir package. - */ -export class NoirPackage { - #packagePath: string; - #srcPath: string; - #config: NoirPackageConfig; - #version: string | null = null; - - public constructor(path: string, srcDir: string, config: NoirPackageConfig) { - this.#packagePath = path; - this.#srcPath = srcDir; - this.#config = config; - } - - /** - * Gets this package's path. - */ - public getPackagePath() { - return this.#packagePath; - } - - /** - * Gets this package's Nargo.toml (NoirPackage)Config. - */ - public getNoirPackageConfig() { - return this.#config; - } - - /** - * The path to the source directory. - */ - public getSrcPath() { - return this.#srcPath; - } - - /** - * Gets the entrypoint path for this package. - */ - public getEntryPointPath(): string { - let entrypoint: string; - - switch (this.getType()) { - case 'lib': - // we shouldn't need to compile `lib` type, since the .nr source is read directly - // when the lib is used as a dependency elsewhere. - entrypoint = 'lib.nr'; - break; - case 'contract': - case 'bin': - entrypoint = 'main.nr'; - break; - default: - throw new Error(`Unknown package type: ${this.getType()}`); - } - // TODO check that `src` exists - return join(this.#srcPath, entrypoint); - } - - /** - * Gets the project type - */ - public getType() { - return this.#config.package.type; - } - - /** - * Gets this package's dependencies. - */ - public getDependencies(): Record { - return this.#config.dependencies; - } - - /** - * Gets this package's sources. - * @param fm - A file manager to use - * @param alias - An alias for the sources, if this package is a dependency - */ - public async getSources(fm: FileManager, alias?: string): Promise { - const handles = await fm.readdir(this.#srcPath, { recursive: true }); - return Promise.all( - handles - .filter(handle => SOURCE_EXTENSIONS.find(ext => handle.endsWith(ext))) - .map(async file => { - const suffix = file.replace(this.#srcPath, ''); - return { - path: this.getType() === 'lib' ? `${alias ? alias : this.#config.package.name}${suffix}` : file, - source: (await fm.readFile(file, 'utf-8')).toString(), - }; - }), - ); - } - - /** - * Opens a path on the filesystem. - * @param path - Path to the package. - * @param fm - A file manager to use. - * @returns The Noir package at the given location - */ - public static async open(path: string, fm: FileManager): Promise { - const fileContents = await fm.readFile(join(path, CONFIG_FILE_NAME), 'utf-8'); - const config = parseNoirPackageConfig(parse(fileContents)); - - return new NoirPackage(path, join(path, 'src'), config); - } -} 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 47473cdefa86..000000000000 --- 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/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index a3d1a23c5e76..2b977b76b59e 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -18,7 +18,7 @@ import times from 'lodash.times'; * @returns Whether this function type corresponds to a private call. */ function isPrivateCall(functionType: FunctionType) { - return functionType === FunctionType.SECRET; + return functionType === 'secret'; } /** @@ -281,7 +281,7 @@ ${contractImpl} export function generateNoirContractInterface(artifact: ContractArtifact) { // We don't allow calling a constructor, internal fns, or unconstrained fns from other contracts const methods = artifact.functions.filter( - f => f.name !== 'constructor' && !f.isInternal && f.functionType !== FunctionType.UNCONSTRAINED, + f => f.name !== 'constructor' && !f.isInternal && f.functionType !== 'unconstrained', ); const paramStructs = methods.flatMap(m => collectStructs(m.parameters, [m.name])).map(generateStruct); const privateContractStruct = generateContractStruct(artifact.name, 'private', methods); 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 7961344b5220..000000000000 --- 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 100% rename from yarn-project/noir-compiler/src/contract-interface-gen/contractTypescript.ts rename to yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts diff --git a/yarn-project/noir-compiler/src/index.test.ts b/yarn-project/noir-compiler/src/index.test.ts index 4922c1af04cf..cddf90b268ab 100644 --- a/yarn-project/noir-compiler/src/index.test.ts +++ b/yarn-project/noir-compiler/src/index.test.ts @@ -1,12 +1,13 @@ import { ContractArtifact } from '@aztec/foundation/abi'; import { LogFn, createDebugLogger } from '@aztec/foundation/log'; +import { NoirContractCompilationArtifact } from '@aztec/foundation/noir'; import { fileURLToPath } from '@aztec/foundation/url'; +import { loadContractArtifact } from '@aztec/types/abi'; import { execSync } from 'child_process'; import path from 'path'; import { - ProgramArtifact, compileUsingNargo, compileUsingNoirWasm, generateNoirContractInterface, @@ -31,54 +32,13 @@ describe('noir-compiler', () => { log = createDebugLogger('noir-compiler:test'); }); - const nargoAvailable = isNargoAvailable(); - const conditionalDescribe = nargoAvailable ? describe : describe.skip; - const conditionalIt = nargoAvailable ? it : it.skip; - const withoutDebug = ({ - debug: _debug, - ...rest - }: ContractArtifact | ProgramArtifact): Omit => rest; - - function compilerTest( - compileFn: (path: string, opts: { log: LogFn }) => Promise<(ProgramArtifact | ContractArtifact)[]>, - ) { - let compiled: (ProgramArtifact | ContractArtifact)[]; - let compiledContract: ContractArtifact[]; - - beforeAll(async () => { - compiled = await compileFn(projectPath, { log }); - compiledContract = compiled.map(_compiled => _compiled as ContractArtifact); - }); - - it('compiles the test contract', () => { - expect(compiledContract.map(withoutDebug)).toMatchSnapshot(); - }); - - it('generates typescript interface', () => { - const result = generateTypescriptContractInterface(compiledContract[0], `../target/test.json`); - expect(result).toMatchSnapshot(); - }); - - it('generates Aztec.nr external interface', () => { - const result = generateNoirContractInterface(compiledContract[0]); - expect(result).toMatchSnapshot(); - }); - } - - describe('using wasm binary', () => { - compilerTest(compileUsingNoirWasm); + it('generates typescript interface', () => { + const result = generateTypescriptContractInterface(compiledContract[0], `../target/test.json`); + expect(result).toMatchSnapshot(); }); - conditionalDescribe('using nargo', () => { - compilerTest(compileUsingNargo); - }); - - conditionalIt('both nargo and noir_wasm should compile identically', async () => { - const [noirWasmArtifact, nargoArtifact] = await Promise.all([ - compileUsingNoirWasm(projectPath, { log }), - compileUsingNargo(projectPath, { log }), - ]); - - expect(nargoArtifact.map(withoutDebug)).toEqual(noirWasmArtifact.map(withoutDebug)); + it('generates Aztec.nr external interface', () => { + const result = generateNoirContractInterface(compiledContract[0]); + expect(result).toMatchSnapshot(); }); -}); +}); \ No newline at end of file diff --git a/yarn-project/noir-compiler/src/index.ts b/yarn-project/noir-compiler/src/index.ts index 976c7eaf69df..0c516f3b09d1 100644 --- a/yarn-project/noir-compiler/src/index.ts +++ b/yarn-project/noir-compiler/src/index.ts @@ -1,49 +1,5 @@ -import { ContractArtifact } from '@aztec/foundation/abi'; - -import { join, resolve } from 'path'; - -import { CompileOpts, NargoContractCompiler } from './compile/nargo.js'; -import { createNodejsFileManager } from './compile/noir/file-manager/nodejs-file-manager.js'; -import { NoirWasmCompileOptions, NoirWasmContractCompiler } from './compile/noir/noir-wasm-compiler.js'; -import { generateArtifact, generateContractArtifact } from './contract-interface-gen/abi.js'; -import { ProgramArtifact } from './noir_artifact.js'; - 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 }; - -export * from './noir_artifact.js'; - -/** - * Compile Aztec.nr contracts in project path using a nargo binary available in the shell. - * @param projectPath - Path to project. - * @param opts - Compiler options. - * @returns Compiled artifacts. - */ -export async function compileUsingNargo(projectPath: string, opts: CompileOpts = {}): Promise { - return (await new NargoContractCompiler(projectPath, opts).compile()).map(artifact => - generateContractArtifact(artifact), - ); -} - -/** - * Compile Aztec.nr contracts in project path using built-in noir_wasm. - * @param projectPath - Path to project. - * @param opts - Compiler options. - * @returns Compiled artifacts. - */ -export async function compileUsingNoirWasm( - projectPath: string, - opts: NoirWasmCompileOptions, -): Promise<(ContractArtifact | ProgramArtifact)[]> { - const cacheRoot = process.env.XDG_CACHE_HOME ?? join(process.env.HOME ?? '', '.cache'); - const fileManager = createNodejsFileManager(join(cacheRoot, 'aztec-noir-compiler')); - const compiler = await NoirWasmContractCompiler.new(fileManager, resolve(projectPath), opts); - const artifacts = await compiler.compile(); - return artifacts.map(artifact => { - return generateArtifact(artifact); - }); -} diff --git a/yarn-project/noir-compiler/src/mocked_keys.ts b/yarn-project/noir-compiler/src/mocked_keys.ts deleted file mode 100644 index b8f5c677eade..000000000000 --- a/yarn-project/noir-compiler/src/mocked_keys.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const mockVerificationKey = - '0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f'; diff --git a/yarn-project/noir-compiler/src/noir_artifact.ts b/yarn-project/noir-compiler/src/noir_artifact.ts deleted file mode 100644 index f8702b3ca0fa..000000000000 --- a/yarn-project/noir-compiler/src/noir_artifact.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - ABIParameter, - ABIParameterVisibility, - ABIType, - DebugFileMap, - DebugInfo, - EventAbi, -} from '@aztec/foundation/abi'; - -/** The Aztec.nr function types. */ -type NoirFunctionType = 'Open' | 'Secret' | 'Unconstrained'; - -/** 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; - }[]; - }; - /** The return type of the function. */ - return_type: { - /** - * The type of the return value. - */ - abi_type: ABIType; - /** - * The visibility of the return value. - */ - visibility: ABIParameterVisibility; - }; - /** The witness indices of the return type. */ - return_witnesses: number[]; -} - -/** - * The compilation result of an Aztec.nr function. - */ -export interface NoirFunctionEntry { - /** The name of the function. */ - name: string; - /** The type of the function. */ - function_type: NoirFunctionType; - /** Whether the function is internal. */ - is_internal: boolean; - /** The ABI of the function. */ - abi: NoirFunctionAbi; - /** The bytecode of the function in base64. */ - bytecode: string; - /** The proving key. */ - proving_key: string; - /** The verification key. */ - verification_key: string; -} - -/** - * The compilation result of an Aztec.nr contract. - */ -export interface NoirCompiledContract { - /** The name of the contract. */ - name: string; - /** The functions of the contract. */ - functions: NoirFunctionEntry[]; - /** The events of the contract */ - events: EventAbi[]; -} - -/** - * The compilation result of an Aztec.nr contract. - */ -export interface NoirCompiledCircuit { - /** The hash of the circuit. */ - hash?: number; - /** - * The ABI of the function. - */ - 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 metadata of an Aztec.nr contract. - */ -export interface NoirDebugMetadata { - /** - * 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; -} - -/** - * The compilation artifacts of a given contract. - */ -export interface NoirContractCompilationArtifacts { - /** - * The compiled contract. - */ - contract: NoirCompiledContract; - - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: NoirDebugMetadata; -} - -/** - * The compilation artifacts of a given program. - */ -export interface NoirProgramCompilationArtifacts { - /** - * not part of the compilation output, injected later - */ - name: string; - /** - * The compiled contract. - */ - program: NoirCompiledCircuit; - - /** - * The artifact that contains the debug metadata about the contract. - */ - debug?: NoirDebugMetadata; -} - -/** - * output of Noir Wasm compilation, can be for a contract or lib/binary - */ -export type NoirCompilationResult = NoirContractCompilationArtifacts | NoirProgramCompilationArtifacts; - -/** - * Check if it has Contract unique property - */ -export function isNoirContractCompilationArtifacts( - artifact: NoirCompilationResult, -): artifact is NoirContractCompilationArtifacts { - return (artifact as NoirContractCompilationArtifacts).contract !== undefined; -} - -/** - * Check if it has Contract unique property - */ -export function isNoirProgramCompilationArtifacts( - artifact: NoirCompilationResult, -): artifact is NoirProgramCompilationArtifacts { - return (artifact as NoirProgramCompilationArtifacts).program !== undefined; -} diff --git a/yarn-project/noir-compiler/tsconfig.json b/yarn-project/noir-compiler/tsconfig.json index a6b3ad94790f..67bc5de282f8 100644 --- a/yarn-project/noir-compiler/tsconfig.json +++ b/yarn-project/noir-compiler/tsconfig.json @@ -9,6 +9,9 @@ { "path": "../circuits.js" }, + { + "path": "../types" + }, { "path": "../foundation" }