diff --git a/compiler/wasm/src/noir/package.ts b/compiler/wasm/src/noir/package.ts index a2496a03b3a..81178e6ae96 100644 --- a/compiler/wasm/src/noir/package.ts +++ b/compiler/wasm/src/noir/package.ts @@ -91,7 +91,7 @@ export class Package { * Gets this package's dependencies. */ public getDependencies(): Record { - return this.#config.dependencies; + return this.#config.dependencies ?? {}; } /** diff --git a/compiler/wasm/src/types/noir_artifact.ts b/compiler/wasm/src/types/noir_artifact.ts index 350a4053a9a..e636212a487 100644 --- a/compiler/wasm/src/types/noir_artifact.ts +++ b/compiler/wasm/src/types/noir_artifact.ts @@ -73,6 +73,8 @@ export interface ContractArtifact { * The compilation result of an Noir contract. */ export interface ProgramArtifact { + /** Version of noir used for the build. */ + noir_version: string; /** The hash of the circuit. */ hash?: number; /** * The ABI of the function. */ diff --git a/compiler/wasm/src/types/noir_package_config.ts b/compiler/wasm/src/types/noir_package_config.ts index 5f07c380cf3..0203763039a 100644 --- a/compiler/wasm/src/types/noir_package_config.ts +++ b/compiler/wasm/src/types/noir_package_config.ts @@ -20,7 +20,7 @@ type NoirPackageConfigSchema = { backend?: string; license?: string; }; - dependencies: Record; + dependencies?: Record; }; /** diff --git a/compiler/wasm/test/compiler/browser/compile.test.ts b/compiler/wasm/test/compiler/browser/compile.test.ts new file mode 100644 index 00000000000..b7e6c27427f --- /dev/null +++ b/compiler/wasm/test/compiler/browser/compile.test.ts @@ -0,0 +1,79 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +import { getPaths } from '../../shared'; +import { expect } from '@esm-bundle/chai'; +import { compile, createFileManager } from '@noir-lang/noir_wasm'; +import { ContractArtifact, ProgramArtifact } from '../../../src/types/noir_artifact'; +import { shouldCompileContractIdentically, shouldCompileProgramIdentically } from '../shared/compile.test'; + +const paths = getPaths('.'); + +async function getFile(path: string) { + // @ts-ignore + const basePath = new URL('./../../', import.meta.url).toString().replace(/\/$/g, ''); + const url = `${basePath}${path.replace('.', '')}`; + const response = await fetch(url); + return response; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function getPrecompiledSource(path: string): Promise { + const response = await getFile(path); + const compiledData = await response.text(); + return JSON.parse(compiledData); +} + +describe('noir-compiler/browser', () => { + shouldCompileProgramIdentically( + async () => { + const { simpleScriptExpectedArtifact } = paths; + const fm = createFileManager('/'); + const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); + for (const path of files) { + console.log(path); + await fm.writeFile(path, (await getFile(path)).body as ReadableStream); + } + const nargoArtifact = (await getPrecompiledSource(simpleScriptExpectedArtifact)) as ProgramArtifact; + const noirWasmArtifact = await compile(fm, '/fixtures/simple'); + + return { nargoArtifact, noirWasmArtifact }; + }, + expect, + 60 * 20e3, + ); + + shouldCompileProgramIdentically( + async () => { + const { depsScriptExpectedArtifact } = paths; + const fm = createFileManager('/'); + const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); + for (const path of files) { + console.log(path); + await fm.writeFile(path, (await getFile(path)).body as ReadableStream); + } + const nargoArtifact = (await getPrecompiledSource(depsScriptExpectedArtifact)) as ProgramArtifact; + const noirWasmArtifact = await compile(fm, '/fixtures/with-deps'); + + return { nargoArtifact, noirWasmArtifact }; + }, + expect, + 60 * 20e3, + ); + + shouldCompileContractIdentically( + async () => { + const { contractExpectedArtifact } = paths; + const fm = createFileManager('/'); + const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); + for (const path of files) { + console.log(path); + await fm.writeFile(path, (await getFile(path)).body as ReadableStream); + } + const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as ContractArtifact; + const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract'); + + return { nargoArtifact, noirWasmArtifact }; + }, + expect, + 60 * 20e3, + ); +}); diff --git a/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts b/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts deleted file mode 100644 index 0d1e22e288f..00000000000 --- a/compiler/wasm/test/compiler/browser/compile_with_deps.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { getPaths } from '../../shared'; -import { expect } from '@esm-bundle/chai'; -import { compile, createFileManager } from '@noir-lang/noir_wasm'; -import { ContractArtifact } from '../../../src/types/noir_artifact'; -import { shouldCompileIdentically } from '../shared/compile_with_deps.test'; - -const paths = getPaths('.'); - -async function getFile(path: string) { - // @ts-ignore - const basePath = new URL('./../../', import.meta.url).toString().replace(/\/$/g, ''); - const url = `${basePath}${path.replace('.', '')}`; - const response = await fetch(url); - return response; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -async function getPrecompiledSource(path: string): Promise { - const response = await getFile(path); - const compiledData = await response.text(); - return JSON.parse(compiledData); -} - -describe('noir-compiler/browser', () => { - shouldCompileIdentically( - async () => { - const { contractExpectedArtifact } = paths; - const fm = createFileManager('/'); - const files = Object.values(paths).filter((fileOrDir) => /^\.?\/.*\..*$/.test(fileOrDir)); - for (const path of files) { - console.log(path); - await fm.writeFile(path, (await getFile(path)).body as ReadableStream); - } - const nargoArtifact = (await getPrecompiledSource(contractExpectedArtifact)) as ContractArtifact; - const noirWasmArtifact = await compile(fm, '/fixtures/noir-contract'); - - return { nargoArtifact, noirWasmArtifact }; - }, - expect, - 60 * 20e3, - ); -}); diff --git a/compiler/wasm/test/compiler/node/compile.test.ts b/compiler/wasm/test/compiler/node/compile.test.ts new file mode 100644 index 00000000000..9af98195825 --- /dev/null +++ b/compiler/wasm/test/compiler/node/compile.test.ts @@ -0,0 +1,39 @@ +import { join, resolve } from 'path'; +import { getPaths } from '../../shared'; + +import { expect } from 'chai'; +import { compile, createFileManager } from '@noir-lang/noir_wasm'; +import { readFile } from 'fs/promises'; +import { ContractArtifact, ProgramArtifact } from '../../../src/types/noir_artifact'; +import { shouldCompileContractIdentically, shouldCompileProgramIdentically } from '../shared/compile.test'; + +const basePath = resolve(join(__dirname, '../../')); + +describe('noir-compiler/node', () => { + shouldCompileProgramIdentically(async () => { + const { simpleScriptProjectPath, simpleScriptExpectedArtifact } = getPaths(basePath); + + const fm = createFileManager(simpleScriptProjectPath); + const nargoArtifact = JSON.parse((await readFile(simpleScriptExpectedArtifact)).toString()) as ProgramArtifact; + const noirWasmArtifact = await compile(fm); + return { nargoArtifact, noirWasmArtifact }; + }, expect); + + shouldCompileProgramIdentically(async () => { + const { depsScriptProjectPath, depsScriptExpectedArtifact } = getPaths(basePath); + + const fm = createFileManager(depsScriptProjectPath); + const nargoArtifact = JSON.parse((await readFile(depsScriptExpectedArtifact)).toString()) as ProgramArtifact; + const noirWasmArtifact = await compile(fm); + return { nargoArtifact, noirWasmArtifact }; + }, expect); + + shouldCompileContractIdentically(async () => { + const { contractProjectPath, contractExpectedArtifact } = getPaths(basePath); + + const fm = createFileManager(contractProjectPath); + const nargoArtifact = JSON.parse((await readFile(contractExpectedArtifact)).toString()) as ContractArtifact; + const noirWasmArtifact = await compile(fm); + return { nargoArtifact, noirWasmArtifact }; + }, expect); +}); diff --git a/compiler/wasm/test/compiler/node/compile_with_deps.test.ts b/compiler/wasm/test/compiler/node/compile_with_deps.test.ts deleted file mode 100644 index 2a402dc9d02..00000000000 --- a/compiler/wasm/test/compiler/node/compile_with_deps.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { join, resolve } from 'path'; -import { getPaths } from '../../shared'; - -import { expect } from 'chai'; -import { compile, createFileManager } from '@noir-lang/noir_wasm'; -import { readFile } from 'fs/promises'; -import { ContractArtifact } from '../../../src/types/noir_artifact'; -import { shouldCompileIdentically } from '../shared/compile_with_deps.test'; - -const basePath = resolve(join(__dirname, '../../')); -const { contractProjectPath, contractExpectedArtifact } = getPaths(basePath); - -describe('noir-compiler/node', () => { - shouldCompileIdentically(async () => { - const fm = createFileManager(contractProjectPath); - const nargoArtifact = JSON.parse((await readFile(contractExpectedArtifact)).toString()) as ContractArtifact; - const noirWasmArtifact = await compile(fm); - return { nargoArtifact, noirWasmArtifact }; - }, expect); -}); diff --git a/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts b/compiler/wasm/test/compiler/shared/compile.test.ts similarity index 52% rename from compiler/wasm/test/compiler/shared/compile_with_deps.test.ts rename to compiler/wasm/test/compiler/shared/compile.test.ts index 0960cba0665..88e8e8c8e5a 100644 --- a/compiler/wasm/test/compiler/shared/compile_with_deps.test.ts +++ b/compiler/wasm/test/compiler/shared/compile.test.ts @@ -6,9 +6,47 @@ import { DebugFileMap, DebugInfo, NoirFunctionEntry, + ProgramArtifact, + ProgramCompilationArtifacts, } from '../../../src/types/noir_artifact'; -export function shouldCompileIdentically( +export function shouldCompileProgramIdentically( + compileFn: () => Promise<{ nargoArtifact: ProgramArtifact; noirWasmArtifact: CompilationResult }>, + expect: typeof Expect, + timeout = 5000, +) { + it('both nargo and noir_wasm should compile identically', async () => { + // Compile! + const { nargoArtifact, noirWasmArtifact } = await compileFn(); + + // Prepare nargo artifact + const [_nargoDebugInfos, nargoFileMap] = deleteProgramDebugMetadata(nargoArtifact); + normalizeVersion(nargoArtifact); + + // Prepare noir-wasm artifact + const noirWasmProgram = (noirWasmArtifact as unknown as ProgramCompilationArtifacts).program; + expect(noirWasmProgram).not.to.be.undefined; + const [_noirWasmDebugInfos, norWasmFileMap] = deleteProgramDebugMetadata(noirWasmProgram); + normalizeVersion(noirWasmProgram); + + // We first compare both contracts without considering debug info + delete (noirWasmProgram as Partial).hash; + delete (nargoArtifact as Partial).hash; + expect(nargoArtifact).to.deep.eq(noirWasmProgram); + + // Compare the file maps, ignoring keys, since those depend in the order in which files are visited, + // which may change depending on the file manager implementation. Also ignores paths, since the base + // path is reported differently between nargo and noir-wasm. + expect(getSources(nargoFileMap)).to.have.members(getSources(norWasmFileMap)); + + // Compare the debug symbol information, ignoring the actual ids used for file identifiers. + // Debug symbol info looks like the following, what we need is to ignore the 'file' identifiers + // {"locations":{"0":[{"span":{"start":141,"end":156},"file":39},{"span":{"start":38,"end":76},"file":38},{"span":{"start":824,"end":862},"file":23}]}} + // expect(nargoDebugInfos).to.deep.eq(noirWasmDebugInfos); + }).timeout(timeout); +} + +export function shouldCompileContractIdentically( compileFn: () => Promise<{ nargoArtifact: ContractArtifact; noirWasmArtifact: CompilationResult }>, expect: typeof Expect, timeout = 5000, @@ -18,13 +56,13 @@ export function shouldCompileIdentically( const { nargoArtifact, noirWasmArtifact } = await compileFn(); // Prepare nargo artifact - const [nargoDebugInfos, nargoFileMap] = deleteDebugMetadata(nargoArtifact); + const [nargoDebugInfos, nargoFileMap] = deleteContractDebugMetadata(nargoArtifact); normalizeVersion(nargoArtifact); // Prepare noir-wasm artifact - const noirWasmContract = (noirWasmArtifact as ContractCompilationArtifacts).contract; + const noirWasmContract = (noirWasmArtifact as unknown as ContractCompilationArtifacts).contract; expect(noirWasmContract).not.to.be.undefined; - const [noirWasmDebugInfos, norWasmFileMap] = deleteDebugMetadata(noirWasmContract); + const [noirWasmDebugInfos, norWasmFileMap] = deleteContractDebugMetadata(noirWasmContract); normalizeVersion(noirWasmContract); // We first compare both contracts without considering debug info @@ -43,7 +81,7 @@ export function shouldCompileIdentically( } /** Remove commit identifier from version, which may not match depending on cached nargo and noir-wasm */ -function normalizeVersion(contract: ContractArtifact) { +function normalizeVersion(contract: ProgramArtifact | ContractArtifact) { contract.noir_version = contract.noir_version.replace(/\+.+$/, ''); } @@ -57,8 +95,18 @@ function extractDebugInfos(fns: NoirFunctionEntry[]) { }); } +/** Deletes all debug info from a program and returns it. */ +function deleteProgramDebugMetadata(program: ProgramArtifact) { + const debugSymbols = inflateDebugSymbols(program.debug_symbols); + const fileMap = program.file_map; + + delete (program as Partial).debug_symbols; + delete (program as Partial).file_map; + return [debugSymbols, fileMap]; +} + /** Deletes all debug info from a contract and returns it. */ -function deleteDebugMetadata(contract: ContractArtifact) { +function deleteContractDebugMetadata(contract: ContractArtifact) { contract.functions.sort((a, b) => a.name.localeCompare(b.name)); const fileMap = contract.file_map; delete (contract as Partial).file_map; diff --git a/compiler/wasm/test/shared.ts b/compiler/wasm/test/shared.ts index 9181919ff39..9f4d417a614 100644 --- a/compiler/wasm/test/shared.ts +++ b/compiler/wasm/test/shared.ts @@ -1,14 +1,23 @@ export function getPaths(basePath: string) { const fixtures = `${basePath}/fixtures`; - const simpleScriptSourcePath = `${fixtures}/simple/src/main.nr`; - const simpleScriptExpectedArtifact = `${fixtures}/simple/target/noir_wasm_testing.json`; + const simpleScriptProjectPath = `${fixtures}/simple`; + const simpleScriptSourcePath = `${simpleScriptProjectPath}/src/main.nr`; + const simpleScriptTOMLPath = `${simpleScriptProjectPath}/Nargo.toml`; + const simpleScriptExpectedArtifact = `${simpleScriptProjectPath}/target/noir_wasm_testing.json`; - const depsScriptSourcePath = `${fixtures}/with-deps/src/main.nr`; - const depsScriptExpectedArtifact = `${fixtures}/with-deps/target/noir_wasm_testing.json`; + const depsScriptProjectPath = `${fixtures}/with-deps`; + const depsScriptSourcePath = `${depsScriptProjectPath}/src/main.nr`; + const depsScriptTOMLPath = `${depsScriptProjectPath}/Nargo.toml`; + const depsScriptExpectedArtifact = `${depsScriptProjectPath}/target/noir_wasm_testing.json`; - const libASourcePath = `${fixtures}/deps/lib-a/src/lib.nr`; - const libBSourcePath = `${fixtures}/deps/lib-b/src/lib.nr`; + const libAProjectPath = `${fixtures}/deps/lib-a`; + const libASourcePath = `${libAProjectPath}/src/lib.nr`; + const libATOMLPath = `${libAProjectPath}/Nargo.toml`; + + const libBProjectPath = `${fixtures}/deps/lib-b`; + const libBSourcePath = `${libBProjectPath}/src/lib.nr`; + const libBTOMLPath = `${libBProjectPath}/Nargo.toml`; const contractProjectPath = `${fixtures}/noir-contract`; const contractSourcePath = `${contractProjectPath}/src/main.nr`; @@ -22,12 +31,18 @@ export function getPaths(basePath: string) { const libCTOMLPath = `${libCProjectPath}/Nargo.toml`; return { + simpleScriptProjectPath, simpleScriptSourcePath, + simpleScriptTOMLPath, simpleScriptExpectedArtifact, + depsScriptProjectPath, depsScriptSourcePath, + depsScriptTOMLPath, depsScriptExpectedArtifact, libASourcePath, + libATOMLPath, libBSourcePath, + libBTOMLPath, contractProjectPath, contractSourcePath, contractTOMLPath,