diff --git a/.eslintignore b/.eslintignore index ddb212c..51bfcf9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ dist/*.js *.md -test/fixture-projects/**/*.* \ No newline at end of file +test/fixture-projects/**/*.* +src/core/compiler/vendor \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 19a8f4d..0939e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,29 @@ { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "license": "MIT", "workspaces": [ "test/fixture-projects/*" ], "dependencies": { - "@distributedlab/circom-parser": "0.1.2", - "@distributedlab/circom2": "0.2.18-rc.4", + "@distributedlab/circom-parser": "0.1.3", "@solarity/zkit": "0.2.6", "@solarity/zktype": "0.3.1", + "@wasmer/wasi": "0.12.0", "chalk": "4.1.2", "cli-progress": "3.12.0", "cli-table3": "0.6.5", "debug": "4.3.5", + "is-typed-array": "1.1.13", "lodash": "4.17.21", "ora": "5.4.1", + "path-browserify": "1.0.1", "resolve": "1.22.8", "semver": "7.6.3", "snarkjs": "0.7.3", @@ -66,6 +68,9 @@ "typechain": "^8.3.2", "typescript": "5.3.3" }, + "engines": { + "node": ">=16" + }, "peerDependencies": { "hardhat": "^2.16.0" } @@ -483,9 +488,9 @@ } }, "node_modules/@distributedlab/circom-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.2.tgz", - "integrity": "sha512-q5IaKs60LPxQhLCdMH8fcpvUP2IKItukrDdLFurdHVfrIypEZRSTW3FeFwqSC/ruFcWwdDhHIZd0tG2x5iFryA==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.3.tgz", + "integrity": "sha512-nU2/+1hVnorZ5DQeR0La78QJPYFAaSPjxShIuc8YBi+co5SSHONli04DbYEnCs44Bbqj2VH+4dTqIt9Tjof/Bg==", "dependencies": { "antlr4": "4.13.1-patch-1", "ejs": "3.1.10" @@ -495,6 +500,8 @@ "version": "0.2.18-rc.4", "resolved": "https://registry.npmjs.org/@distributedlab/circom2/-/circom2-0.2.18-rc.4.tgz", "integrity": "sha512-5ALgnpk+mdzZDeKwR3ZIjJXjVeWt+Qz4g/lWAR4cxTrnEhiQptDacg0wtd7WKBUghDpEruLZ4wXyIdRoXuJSaQ==", + "dev": true, + "peer": true, "dependencies": { "@wasmer/wasi": "^0.12.0", "is-typed-array": "^1.1.8", @@ -2177,6 +2184,17 @@ "hardhat": "^2.16.0" } }, + "node_modules/@solarity/hardhat-zkit/node_modules/@distributedlab/circom-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.2.tgz", + "integrity": "sha512-q5IaKs60LPxQhLCdMH8fcpvUP2IKItukrDdLFurdHVfrIypEZRSTW3FeFwqSC/ruFcWwdDhHIZd0tG2x5iFryA==", + "dev": true, + "peer": true, + "dependencies": { + "antlr4": "4.13.1-patch-1", + "ejs": "3.1.10" + } + }, "node_modules/@solarity/zkit": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@solarity/zkit/-/zkit-0.2.6.tgz", @@ -3041,7 +3059,9 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "dev": true, "optional": true, + "peer": true, "dependencies": { "follow-redirects": "1.5.10" } @@ -3124,7 +3144,9 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.0.1.tgz", "integrity": "sha512-axr6lqB4ec/pkEOb/JMnZpfcroWv1zT48pVz1oQHG7XmGkS77vmdxmP1btuH79lWQdy9e2MVw/uW0D8siopkRg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "axios": "^0.19.0", "env-paths": "^2.2.0", @@ -3520,7 +3542,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/ci-info": { "version": "2.0.0", @@ -4150,7 +4174,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -5166,7 +5190,9 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "debug": "=3.1.0" }, @@ -5178,7 +5204,9 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -5187,7 +5215,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/for-each": { "version": "0.3.3", @@ -5255,7 +5285,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -5267,7 +5299,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -5406,7 +5438,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5438,7 +5470,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5448,7 +5480,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6004,7 +6036,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/hasurl/-/hasurl-1.0.0.tgz", "integrity": "sha512-43ypUd3DbwyCT01UYpA99AEZxZ4aKtRxWGBHEIbjcOsUghd9YUON0C+JF6isNjaiwC/UF5neaUudy6JS9jZPZQ==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -6172,7 +6206,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6943,7 +6977,9 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -7097,7 +7133,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "devOptional": true, + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7106,7 +7142,9 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7118,7 +7156,9 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -7131,7 +7171,9 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -7596,7 +7638,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, + "dev": true, "dependencies": { "wrappy": "1" } @@ -7762,7 +7804,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7965,7 +8007,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -8192,7 +8234,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -8267,8 +8309,10 @@ "version": "0.1.29", "resolved": "https://registry.npmjs.org/rustwasmc/-/rustwasmc-0.1.29.tgz", "integrity": "sha512-yYqYQ61W1P/DUIcUF/djhltHqLQwETuiVorJ52ZqDSYeU2D6+4FrVPbICe7vhz7Y9FeMPz0K1SkBoJjnLjFS6g==", + "dev": true, "hasInstallScript": true, "optional": true, + "peer": true, "dependencies": { "binary-install": "0.0.1" }, @@ -8922,7 +8966,9 @@ "version": "5.0.11", "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.11.tgz", "integrity": "sha512-E6q48d5y4XSCD+Xmwc0yc8lXuyDK38E0FB8N4S/drQRtXOMUhfhDxbB0xr2KKDhNfO51CFmoa6Oz00nAkWsjnA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "chownr": "^1.1.4", "fs-minipass": "^2.1.0", @@ -9023,7 +9069,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -9473,7 +9521,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universal-url/-/universal-url-2.0.0.tgz", "integrity": "sha512-3DLtXdm/G1LQMCnPj+Aw7uDoleQttNHp2g5FnNQKR6cP6taNWS1b/Ehjjx4PVyvejKi3TJyu8iBraKM4q3JQPg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "hasurl": "^1.0.0", "whatwg-url": "^7.0.0" @@ -9592,13 +9642,17 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -9729,7 +9783,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -9777,7 +9831,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/yargs": { "version": "16.2.0", diff --git a/package.json b/package.json index 3374b02..25ed6cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "description": "The ultimate TypeScript environment for Circom development", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -49,11 +49,14 @@ "text" ] }, + "engines": { + "node": ">=16" + }, "dependencies": { - "@distributedlab/circom-parser": "0.1.2", - "@distributedlab/circom2": "0.2.18-rc.4", + "@distributedlab/circom-parser": "0.1.3", "@solarity/zkit": "0.2.6", "@solarity/zktype": "0.3.1", + "@wasmer/wasi": "0.12.0", "chalk": "4.1.2", "cli-progress": "3.12.0", "cli-table3": "0.6.5", @@ -64,7 +67,9 @@ "semver": "7.6.3", "snarkjs": "0.7.3", "uuid": "9.0.1", - "zod": "3.23.8" + "zod": "3.23.8", + "is-typed-array": "1.1.13", + "path-browserify": "1.0.1" }, "peerDependencies": { "hardhat": "^2.16.0" diff --git a/src/constants.ts b/src/constants.ts index f404b83..2a0a634 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,8 +28,8 @@ export const COMPILER_ARM_REPOSITORY_URL = "https://github.com/distributed-lab/c export const COMPILER_WASM_REPOSITORY_URL = "https://github.com/distributed-lab/circom-wasm/releases/download"; export const LATEST_SUPPORTED_CIRCOM_VERSION = "2.1.9"; +export const OLDEST_SUPPORTED_CIRCOM_VERSION = "2.0.5"; -export const OLDEST_SUPPORTED_ARM_CIRCOM_VERSION = "2.0.5"; export const WASM_COMPILER_VERSIONING: { [key: string]: string } = { "2.1.8": "0.2.18-rc.3", "2.1.9": "0.2.19-rc.0", diff --git a/src/core/compiler/CircomCompiler.ts b/src/core/compiler/CircomCompiler.ts index 0b6abfa..894a911 100644 --- a/src/core/compiler/CircomCompiler.ts +++ b/src/core/compiler/CircomCompiler.ts @@ -7,7 +7,7 @@ import { ICircomCompiler, CompileConfig, BaseCompileConfig } from "../../types/c import { MAGIC_DESCRIPTOR } from "../../constants"; // eslint-disable-next-line -const { Context, CircomRunner, bindings } = require("@distributedlab/circom2"); +const { Context, CircomRunner, bindings, preopens } = require("./vendor"); /** * An abstract class that serves as the base for all `circom` compiler implementations. @@ -167,7 +167,7 @@ export class WASMCircomCompiler extends BaseCircomCompiler { private _getCircomRunner(callArgs: string[], quiet: boolean, errDescriptor: number): typeof CircomRunner { return new CircomRunner({ args: callArgs, - preopens: { "/": "/" }, + preopens, bindings: { ...bindings, exit(code: number) { diff --git a/src/core/compiler/CircomCompilerDownloader.ts b/src/core/compiler/CircomCompilerDownloader.ts index bf818b8..3c3f4c9 100644 --- a/src/core/compiler/CircomCompilerDownloader.ts +++ b/src/core/compiler/CircomCompilerDownloader.ts @@ -182,7 +182,9 @@ export class CircomCompilerDownloader { * @param version The version of the compiler to download * @param isVersionStrict Indicates whether to enforce strict version matching * @param verifyCompiler Indicates whether to perform verification on the downloaded compiler - * @throws `HardhatZKitError` If an error occurs during the download or verification process + * + * @throws `HardhatZKitError` If an error occurs during the download, verification process + * or getting WASM download URL for the specific version */ public async downloadCompiler(version: string, isVersionStrict: boolean, verifyCompiler: boolean): Promise { await this._mutex.use(async () => { @@ -252,7 +254,7 @@ export class CircomCompilerDownloader { url = `${COMPILER_ARM_REPOSITORY_URL}/v${version}/${this._platform}`; break; default: - url = `${COMPILER_WASM_REPOSITORY_URL}/v${WASM_COMPILER_VERSIONING[version]}/circom.wasm`; + url = this._getWasmDownloadURL(version); } if ( @@ -279,6 +281,15 @@ export class CircomCompilerDownloader { return path.join(this._compilersDir, version, "circom.wasm"); } + private _getWasmDownloadURL(version: string): string { + const wasmVersion = WASM_COMPILER_VERSIONING[version]; + if (!wasmVersion) { + throw new HardhatZKitError(`Unsupported WASM version - ${version}`); + } + + return `${COMPILER_WASM_REPOSITORY_URL}/v${wasmVersion}/circom.wasm`; + } + private async _postProcessCompilerDownload(downloadPath: string): Promise { if ( this._platform !== CompilerPlatformBinary.WINDOWS_AMD && diff --git a/src/core/compiler/CircomCompilerFactory.ts b/src/core/compiler/CircomCompilerFactory.ts index 43a4293..68ec16c 100644 --- a/src/core/compiler/CircomCompilerFactory.ts +++ b/src/core/compiler/CircomCompilerFactory.ts @@ -7,7 +7,7 @@ import { exec } from "child_process"; import { Reporter } from "../../reporter"; import { HardhatZKitError } from "../../errors"; -import { LATEST_SUPPORTED_CIRCOM_VERSION, OLDEST_SUPPORTED_ARM_CIRCOM_VERSION } from "../../constants"; +import { LATEST_SUPPORTED_CIRCOM_VERSION, OLDEST_SUPPORTED_CIRCOM_VERSION } from "../../constants"; import { BinaryCircomCompiler, WASMCircomCompiler } from "./CircomCompiler"; import { CircomCompilerDownloader } from "./CircomCompilerDownloader"; @@ -24,24 +24,19 @@ const { Context } = require("@distributedlab/circom2"); * based on the specified version and platform. It includes logic to handle versioning, * ensure compatibility with supported architectures, and determine the appropriate * compiler to use (native, binary, or WASM) for the compilation process. - * - * The factory also includes error handling for unsupported versions and facilitates - * the use of binary translators on ARM architectures when necessary. This allows - * developers to seamlessly work with various Circom compiler versions while - * managing dependencies and ensuring optimal performance during circuit compilation. */ export class BaseCircomCompilerFactory { /** * Creates an instance of a Circom compiler based on the specified version and architecture. * - * This method first checks if the requested Circom compiler version is supported. If the version - * exceeds the latest supported version, an error is thrown. The method attempts to create a native - * compiler first; if successful, the instance is returned immediately. + * This method first checks if the requested Circom compiler version is supported. + * If the version is strict and falls outside the range between the latest supported and + * the earliest supported versions, an error is thrown. If it's not strict, only the upper limit is checked. + * + * The method attempts to create a native compiler first; if successful, the instance is returned immediately. * * If the native compiler cannot be created, the method determines the appropriate compiler binary - * for the current platform. If the requested version is strictly enforced and the system architecture - * is arm64, but the version is older than the supported arm64 version, it falls back to using the - * x64 binary. + * for the current platform. * * The method then attempts to create a binary compiler if the platform binary is not WASM. If this * also fails, it defaults to creating a WASM compiler instance. This provides flexibility in compiler @@ -59,7 +54,11 @@ export class BaseCircomCompilerFactory { isVersionStrict: boolean, verifyCompiler: boolean = true, ): Promise { - if (!semver.gte(LATEST_SUPPORTED_CIRCOM_VERSION, version)) { + // Always check the latest supported version and check the oldest version only if it is strict + if ( + (isVersionStrict && semver.lt(version, OLDEST_SUPPORTED_CIRCOM_VERSION)) || + semver.gt(version, LATEST_SUPPORTED_CIRCOM_VERSION) + ) { throw new HardhatZKitError(`Unsupported Circom compiler version - ${version}. Please provide another version.`); } @@ -69,13 +68,7 @@ export class BaseCircomCompilerFactory { return compiler; } - let compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary(); - - // Utilize binary translators like Rosetta (macOS) or Prism (Windows) - // to run x64 binaries on arm64 systems when no arm64 versions are available. - if (isVersionStrict && os.arch() === "arm64" && !semver.gte(version, OLDEST_SUPPORTED_ARM_CIRCOM_VERSION)) { - compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary("x64"); - } + const compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary(); if (compilerPlatformBinary !== CompilerPlatformBinary.WASM) { compiler = await this._tryCreateBinaryCompiler(compilerPlatformBinary, version, isVersionStrict, verifyCompiler); diff --git a/src/core/compiler/vendor/index.js b/src/core/compiler/vendor/index.js new file mode 100644 index 0000000..cf6ae05 --- /dev/null +++ b/src/core/compiler/vendor/index.js @@ -0,0 +1,136 @@ +const isTypedArray = require("is-typed-array"); +const path = require("path-browserify"); + +const { WASI, WASIExitError, WASIKillError } = require("./wasi"); + +const baseNow = Math.floor((Date.now() - performance.now()) * 1e-3); + +function hrtime() { + let clocktime = performance.now() * 1e-3; + let seconds = Math.floor(clocktime) + baseNow; + let nanoseconds = Math.floor((clocktime % 1) * 1e9); + // return BigInt(seconds) * BigInt(1e9) + BigInt(nanoseconds) + return seconds * 1e9 + nanoseconds; +} + +function randomFillSync(buf, offset, size) { + if ( + typeof crypto !== "undefined" && + typeof crypto.getRandomValues === "function" + ) { + // Similar to the implementation of `randomfill` on npm + let uint = new Uint8Array(buf.buffer, offset, size); + crypto.getRandomValues(uint); + return buf; + } else { + try { + // Try to load webcrypto in node + let crypto = require("crypto"); + // TODO: Update to webcrypto in nodejs + return crypto.randomFillSync(buf, offset, size); + } catch { + // If an error occurs, fall back to the least secure version + // TODO: Should we throw instead since this would be a crazy old browser + // or nodejs built without crypto APIs + if (buf instanceof Uint8Array) { + for (let i = offset; i < offset + size; i++) { + buf[i] = Math.floor(Math.random() * 256); + } + } + return buf; + } + } +} + +const defaultBindings = { + hrtime: hrtime, + exit(code) { + throw new WASIExitError(code); + }, + kill(signal) { + throw new WASIKillError(signal); + }, + randomFillSync: randomFillSync, + isTTY: () => true, + path: path, + fs: null, +}; + +const defaultPreopens = { + ".": ".", + "/": "/", +}; + +class CircomRunner { + constructor({ + args, + env, + preopens = defaultPreopens, + bindings = defaultBindings, + descriptors = undefined, + } = {}) { + if (!bindings.fs) { + throw new Error( + "You must specify an `fs`-compatible API as part of bindings", + ); + } + this.wasi = new WASI({ + args: ["circom2", ...args], + env, + preopens, + bindings, + descriptors, + }); + } + + async compile(bufOrResponse) { + // TODO: Handle ArrayBuffer + if (isTypedArray(bufOrResponse)) { + return WebAssembly.compile(bufOrResponse); + } + + // Require Response object if not a TypedArray + const response = await bufOrResponse; + if (!(response instanceof Response)) { + throw new Error("Expected TypedArray or Response object"); + } + + const contentType = response.headers.get("Content-Type") || ""; + + if ( + "instantiateStreaming" in WebAssembly && + contentType.startsWith("application/wasm") + ) { + return WebAssembly.compileStreaming(response); + } + + const buffer = await response.arrayBuffer(); + return WebAssembly.compile(buffer); + } + + async execute(bufOrResponse) { + const mod = await this.compile(bufOrResponse); + const instance = await WebAssembly.instantiate(mod, { + ...this.wasi.getImports(mod), + }); + + try { + this.wasi.start(instance); + } catch (err) { + // The circom devs decided to start forcing an exit call instead of exiting gracefully + // so we look for WASIExitError with success code so we can actually be graceful + if (err instanceof WASIExitError && err.code === 0) { + return instance; + } + + throw err; + } + + // Return the instance in case someone wants to access exports or something + return instance; + } +} + +module.exports.CircomRunner = CircomRunner; +module.exports.preopens = defaultPreopens; +module.exports.bindings = defaultBindings; diff --git a/src/core/compiler/vendor/wasi.js b/src/core/compiler/vendor/wasi.js new file mode 100644 index 0000000..0af9323 --- /dev/null +++ b/src/core/compiler/vendor/wasi.js @@ -0,0 +1,1327 @@ +"use strict"; +/* eslint-disable no-unused-vars */ +Object.defineProperty(exports, "__esModule", { value: true }); +const bigint_1 = require("@wasmer/wasi/lib/polyfills/bigint"); +const dataview_1 = require("@wasmer/wasi/lib/polyfills/dataview"); +const buffer_1 = require("@wasmer/wasi/lib/polyfills/buffer"); + +const constants_1 = require("@wasmer/wasi/lib/constants"); +const STDIN_DEFAULT_RIGHTS = + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE; +const STDOUT_DEFAULT_RIGHTS = + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE; +const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS; +const msToNs = (ms) => { + const msInt = Math.trunc(ms); + const decimal = bigint_1.BigIntPolyfill(Math.round((ms - msInt) * 1000000)); + const ns = bigint_1.BigIntPolyfill(msInt) * bigint_1.BigIntPolyfill(1000000); + return ns + decimal; +}; +const nsToMs = (ns) => { + if (typeof ns === "number") { + ns = Math.trunc(ns); + } + const nsInt = bigint_1.BigIntPolyfill(ns); + return Number(nsInt / bigint_1.BigIntPolyfill(1000000)); +}; +const wrap = + (f) => + (...args) => { + try { + return f(...args); + } catch (e) { + // If it's an error from the fs + if (e && e.code && typeof e.code === "string") { + return constants_1.ERROR_MAP[e.code] || constants_1.WASI_EINVAL; + } + // If it's a WASI error, we return it directly + if (e instanceof WASIError) { + return e.errno; + } + // Otherwise we let the error bubble up + throw e; + } + }; +const stat = (wasi, fd) => { + const entry = wasi.FD_MAP.get(fd); + if (!entry) { + throw new WASIError(constants_1.WASI_EBADF); + } + if (entry.filetype === undefined) { + const stats = wasi.bindings.fs.fstatSync(entry.real); + const { filetype, rightsBase, rightsInheriting } = translateFileAttributes( + wasi, + fd, + stats, + ); + entry.filetype = filetype; + if (!entry.rights) { + entry.rights = { + base: rightsBase, + inheriting: rightsInheriting, + }; + } + } + return entry; +}; +const translateFileAttributes = (wasi, fd, stats) => { + switch (true) { + case stats.isBlockDevice(): + return { + filetype: constants_1.WASI_FILETYPE_BLOCK_DEVICE, + rightsBase: constants_1.RIGHTS_BLOCK_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_BLOCK_DEVICE_INHERITING, + }; + case stats.isCharacterDevice(): { + const filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE; + if (fd !== undefined && wasi.bindings.isTTY(fd)) { + return { + filetype, + rightsBase: constants_1.RIGHTS_TTY_BASE, + rightsInheriting: constants_1.RIGHTS_TTY_INHERITING, + }; + } + return { + filetype, + rightsBase: constants_1.RIGHTS_CHARACTER_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_CHARACTER_DEVICE_INHERITING, + }; + } + case stats.isDirectory(): + return { + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + rightsBase: constants_1.RIGHTS_DIRECTORY_BASE, + rightsInheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + }; + case stats.isFIFO(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + }; + case stats.isFile(): + return { + filetype: constants_1.WASI_FILETYPE_REGULAR_FILE, + rightsBase: constants_1.RIGHTS_REGULAR_FILE_BASE, + rightsInheriting: constants_1.RIGHTS_REGULAR_FILE_INHERITING, + }; + case stats.isSocket(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + }; + case stats.isSymbolicLink(): + return { + filetype: constants_1.WASI_FILETYPE_SYMBOLIC_LINK, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + }; + default: + return { + filetype: constants_1.WASI_FILETYPE_UNKNOWN, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + }; + } +}; +class WASIError extends Error { + constructor(errno) { + super(); + this.errno = errno; + Object.setPrototypeOf(this, WASIError.prototype); + } +} +exports.WASIError = WASIError; +class WASIExitError extends Error { + constructor(code) { + super(`WASI Exit error: ${code}`); + this.code = code; + Object.setPrototypeOf(this, WASIExitError.prototype); + } +} +exports.WASIExitError = WASIExitError; +class WASIKillError extends Error { + constructor(signal) { + super(`WASI Kill signal: ${signal}`); + this.signal = signal; + Object.setPrototypeOf(this, WASIKillError.prototype); + } +} +exports.WASIKillError = WASIKillError; +class WASIDefault { + constructor(wasiConfig) { + // Destructure our wasiConfig + let preopens = {}; + if (wasiConfig && wasiConfig.preopens) { + preopens = wasiConfig.preopens; + } else if (wasiConfig && wasiConfig.preopenDirectories) { + preopens = wasiConfig.preopenDirectories; + } + let env = {}; + if (wasiConfig && wasiConfig.env) { + env = wasiConfig.env; + } + let args = []; + if (wasiConfig && wasiConfig.args) { + args = wasiConfig.args; + } + let bindings; + if (wasiConfig && wasiConfig.bindings) { + bindings = wasiConfig.bindings; + } + // @ts-ignore + this.memory = undefined; + // @ts-ignore + this.view = undefined; + this.bindings = bindings; + this.FD_MAP = new Map([ + [ + constants_1.WASI_STDIN_FILENO, + { + real: 0, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDIN_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDOUT_FILENO, + { + real: 1, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDOUT_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDERR_FILENO, + { + real: 2, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDERR_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + ]); + + if (wasiConfig && wasiConfig.descriptors) { + if (wasiConfig.descriptors.stdout) { + this.FD_MAP.get(constants_1.WASI_STDOUT_FILENO).real = + wasiConfig.descriptors.stdout; + } + + if (wasiConfig.descriptors.stderr) { + this.FD_MAP.get(constants_1.WASI_STDERR_FILENO).real = + wasiConfig.descriptors.stderr; + } + } + + let fs = this.bindings.fs; + let path = this.bindings.path; + for (const [k, v] of Object.entries(preopens)) { + const real = fs.openSync(v, fs.constants.O_RDONLY); + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; + this.FD_MAP.set(newfd, { + real, + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + // offset: BigInt(0), + rights: { + base: constants_1.RIGHTS_DIRECTORY_BASE, + inheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + }, + fakePath: k, + path: v, + }); + } + const getiovs = (iovs, iovsLen) => { + // iovs* -> [iov, iov, ...] + // __wasi_ciovec_t { + // void* buf, + // size_t buf_len, + // } + this.refreshMemory(); + const buffers = Array.from({ length: iovsLen }, (_, i) => { + const ptr = iovs + i * 8; + const buf = this.view.getUint32(ptr, true); + const bufLen = this.view.getUint32(ptr + 4, true); + return new Uint8Array(this.memory.buffer, buf, bufLen); + }); + return buffers; + }; + const CHECK_FD = (fd, rights) => { + const stats = stat(this, fd); + // console.log(`CHECK_FD: stats.real: ${stats.real}, stats.path:`, stats.path); + // console.log('fd_check', fd, rights, stats) + if ( + rights !== bigint_1.BigIntPolyfill(0) && + (stats.rights.base & rights) === bigint_1.BigIntPolyfill(0) + ) { + throw new WASIError(constants_1.WASI_EPERM); + } + return stats; + }; + const CPUTIME_START = bindings.hrtime(); + const now = (clockId) => { + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + return bindings.hrtime(); + case constants_1.WASI_CLOCK_REALTIME: + return msToNs(Date.now()); + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: + // return bindings.hrtime(CPUTIME_START) + return bindings.hrtime() - CPUTIME_START; + default: + return null; + } + }; + this.wasiImport = { + args_get: (argv, argvBuf) => { + this.refreshMemory(); + let coffset = argv; + let offset = argvBuf; + args.forEach((a) => { + this.view.setUint32(coffset, offset, true); + coffset += 4; + offset += buffer_1.default + .from(this.memory.buffer) + .write(`${a}\0`, offset); + }); + return constants_1.WASI_ESUCCESS; + }, + args_sizes_get: (argc, argvBufSize) => { + this.refreshMemory(); + this.view.setUint32(argc, args.length, true); + const size = args.reduce( + (acc, a) => acc + buffer_1.default.byteLength(a) + 1, + 0, + ); + this.view.setUint32(argvBufSize, size, true); + return constants_1.WASI_ESUCCESS; + }, + environ_get: (environ, environBuf) => { + this.refreshMemory(); + let coffset = environ; + let offset = environBuf; + Object.entries(env).forEach(([key, value]) => { + this.view.setUint32(coffset, offset, true); + coffset += 4; + offset += buffer_1.default + .from(this.memory.buffer) + .write(`${key}=${value}\0`, offset); + }); + return constants_1.WASI_ESUCCESS; + }, + environ_sizes_get: (environCount, environBufSize) => { + this.refreshMemory(); + const envProcessed = Object.entries(env).map( + ([key, value]) => `${key}=${value}\0`, + ); + const size = envProcessed.reduce( + (acc, e) => acc + buffer_1.default.byteLength(e), + 0, + ); + this.view.setUint32(environCount, envProcessed.length, true); + this.view.setUint32(environBufSize, size, true); + return constants_1.WASI_ESUCCESS; + }, + clock_res_get: (clockId, resolution) => { + let res; + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: { + res = bigint_1.BigIntPolyfill(1); + break; + } + case constants_1.WASI_CLOCK_REALTIME: { + res = bigint_1.BigIntPolyfill(1000); + break; + } + } + this.view.setBigUint64(resolution, res); + return constants_1.WASI_ESUCCESS; + }, + clock_time_get: (clockId, precision, time) => { + this.refreshMemory(); + const n = now(clockId); + if (n === null) { + return constants_1.WASI_EINVAL; + } + this.view.setBigUint64(time, bigint_1.BigIntPolyfill(n), true); + return constants_1.WASI_ESUCCESS; + }, + fd_advise: wrap((fd, offset, len, advice) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE); + return constants_1.WASI_ENOSYS; + }), + fd_allocate: wrap((fd, offset, len) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE); + return constants_1.WASI_ENOSYS; + }), + fd_close: wrap((fd) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + fs.closeSync(stats.real); + this.FD_MAP.delete(fd); + return constants_1.WASI_ESUCCESS; + }), + fd_datasync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC); + fs.fdatasyncSync(stats.real); + return constants_1.WASI_ESUCCESS; + }), + fd_fdstat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + this.refreshMemory(); + this.view.setUint8(bufPtr, stats.filetype); // FILETYPE u8 + this.view.setUint16(bufPtr + 2, 0, true); // FDFLAG u16 + this.view.setUint16(bufPtr + 4, 0, true); // FDFLAG u16 + this.view.setBigUint64( + bufPtr + 8, + bigint_1.BigIntPolyfill(stats.rights.base), + true, + ); // u64 + this.view.setBigUint64( + bufPtr + 8 + 8, + bigint_1.BigIntPolyfill(stats.rights.inheriting), + true, + ); // u64 + return constants_1.WASI_ESUCCESS; + }), + fd_fdstat_set_flags: wrap((fd, flags) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS); + return constants_1.WASI_ENOSYS; + }), + fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + const nrb = stats.rights.base | fsRightsBase; + if (nrb > stats.rights.base) { + return constants_1.WASI_EPERM; + } + const nri = stats.rights.inheriting | fsRightsInheriting; + if (nri > stats.rights.inheriting) { + return constants_1.WASI_EPERM; + } + stats.rights.base = fsRightsBase; + stats.rights.inheriting = fsRightsInheriting; + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET); + const rstats = fs.fstatSync(stats.real); + this.refreshMemory(); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.dev), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + this.view.setUint8(bufPtr, stats.filetype); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.nlink), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.size), + true, + ); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_set_size: wrap((fd, stSize) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE); + fs.ftruncateSync(stats.real, Number(stSize)); + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES, + ); + const rstats = fs.fstatSync(stats.real); + let atim = rstats.atime; + let mtim = rstats.mtime; + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | + constants_1.WASI_FILESTAT_SET_ATIM_NOW; + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL; + } + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | + constants_1.WASI_FILESTAT_SET_MTIM_NOW; + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n; + } + fs.futimesSync(stats.real, new Date(atim), new Date(mtim)); + return constants_1.WASI_ESUCCESS; + }), + fd_prestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR); + this.view.setUint32( + bufPtr + 4, + buffer_1.default.byteLength(stats.fakePath), + true, + ); + return constants_1.WASI_ESUCCESS; + }), + fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + buffer_1.default + .from(this.memory.buffer) + .write(stats.fakePath, pathPtr, pathLen, "utf8"); + return constants_1.WASI_ESUCCESS; + }), + fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_WRITE | constants_1.WASI_RIGHT_FD_SEEK, + ); + let written = 0; + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0; + while (w < iov.byteLength) { + w += fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + Number(offset) + written + w, + ); + } + written += w; + }); + this.view.setUint32(nwritten, written, true); + return constants_1.WASI_ESUCCESS; + }), + fd_write: wrap((fd, iovs, iovsLen, nwritten) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE); + let written = 0; + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0; + while (w < iov.byteLength) { + const i = fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + stats.offset !== undefined ? Number(stats.offset) : null, + ); + if (stats.offset !== undefined) + stats.offset += bigint_1.BigIntPolyfill(i); + w += i; + } + written += w; + }); + this.view.setUint32(nwritten, written, true); + return constants_1.WASI_ESUCCESS; + }), + fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_SEEK, + ); + let read = 0; + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0; + while (r < iov.byteLength) { + const length = iov.byteLength - r; + const rr = fs.readSync( + stats.real, + iov, + r, + iov.byteLength - r, + Number(offset) + read + r, + ); + r += rr; + read += rr; + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer; + } + } + read += r; + } + this.view.setUint32(nread, read, true); + return constants_1.WASI_ESUCCESS; + }), + fd_read: wrap((fd, iovs, iovsLen, nread) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ); + const IS_STDIN = stats.real === 0; + let read = 0; + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0; + while (r < iov.byteLength) { + let length = iov.byteLength - r; + let position = + IS_STDIN || stats.offset === undefined + ? null + : Number(stats.offset); + let rr = fs.readSync( + stats.real, // fd + iov, // buffer + r, // offset + length, // length + position, // position + ); + if (!IS_STDIN) { + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(rr); + } + r += rr; + read += rr; + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer; + } + } + } + // We should not modify the offset of stdin + this.view.setUint32(nread, read, true); + return constants_1.WASI_ESUCCESS; + }), + fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READDIR); + this.refreshMemory(); + const entries = fs.readdirSync(stats.path, { withFileTypes: true }); + const startPtr = bufPtr; + for (let i = Number(cookie); i < entries.length; i += 1) { + const entry = entries[i]; + let nameLength = buffer_1.default.byteLength(entry.name); + if (bufPtr - startPtr > bufLen) { + break; + } + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(i + 1), true); + bufPtr += 8; + if (bufPtr - startPtr > bufLen) { + break; + } + const rstats = fs.statSync(path.resolve(stats.path, entry.name)); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + if (bufPtr - startPtr > bufLen) { + break; + } + this.view.setUint32(bufPtr, nameLength, true); + bufPtr += 4; + if (bufPtr - startPtr > bufLen) { + break; + } + let filetype; + switch (true) { + case rstats.isBlockDevice(): + filetype = constants_1.WASI_FILETYPE_BLOCK_DEVICE; + break; + case rstats.isCharacterDevice(): + filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE; + break; + case rstats.isDirectory(): + filetype = constants_1.WASI_FILETYPE_DIRECTORY; + break; + case rstats.isFIFO(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM; + break; + case rstats.isFile(): + filetype = constants_1.WASI_FILETYPE_REGULAR_FILE; + break; + case rstats.isSocket(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM; + break; + case rstats.isSymbolicLink(): + filetype = constants_1.WASI_FILETYPE_SYMBOLIC_LINK; + break; + default: + filetype = constants_1.WASI_FILETYPE_UNKNOWN; + break; + } + this.view.setUint8(bufPtr, filetype); + bufPtr += 1; + bufPtr += 3; // padding + if (bufPtr + nameLength >= startPtr + bufLen) { + // It doesn't fit in the buffer + break; + } + let memory_buffer = buffer_1.default.from(this.memory.buffer); + memory_buffer.write(entry.name, bufPtr); + bufPtr += nameLength; + } + const bufused = bufPtr - startPtr; + this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true); + return constants_1.WASI_ESUCCESS; + }), + fd_renumber: wrap((from, to) => { + CHECK_FD(from, bigint_1.BigIntPolyfill(0)); + CHECK_FD(to, bigint_1.BigIntPolyfill(0)); + fs.closeSync(this.FD_MAP.get(from).real); + this.FD_MAP.set(from, this.FD_MAP.get(to)); + this.FD_MAP.delete(to); + return constants_1.WASI_ESUCCESS; + }), + fd_seek: wrap((fd, offset, whence, newOffsetPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SEEK); + // console.log('fd_seek', fd, offset, whence, newOffsetPtr, '=', stats.offset); + this.refreshMemory(); + switch (whence) { + case constants_1.WASI_WHENCE_CUR: + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(offset); + break; + case constants_1.WASI_WHENCE_END: + const { size } = fs.fstatSync(stats.real); + stats.offset = + bigint_1.BigIntPolyfill(size) + bigint_1.BigIntPolyfill(offset); + break; + case constants_1.WASI_WHENCE_SET: + stats.offset = bigint_1.BigIntPolyfill(offset); + break; + } + this.view.setBigUint64(newOffsetPtr, stats.offset, true); + return constants_1.WASI_ESUCCESS; + }), + fd_tell: wrap((fd, offsetPtr) => { + // console.log('fd_tell') + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL); + this.refreshMemory(); + if (!stats.offset) { + stats.offset = bigint_1.BigIntPolyfill(0); + } + this.view.setBigUint64(offsetPtr, stats.offset, true); + return constants_1.WASI_ESUCCESS; + }), + fd_sync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC); + fs.fsyncSync(stats.real); + return constants_1.WASI_ESUCCESS; + }), + path_create_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.mkdirSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const rstats = fs.statSync(path.resolve(stats.path, p)); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.dev), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + this.view.setUint8( + bufPtr, + translateFileAttributes(this, undefined, rstats).filetype, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.nlink), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.size), + true, + ); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); + return constants_1.WASI_ESUCCESS; + }), + path_filestat_set_times: wrap( + (fd, dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const rstats = fs.fstatSync(stats.real); + let atim = rstats.atime; + let mtim = rstats.mtime; + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | + constants_1.WASI_FILESTAT_SET_ATIM_NOW; + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL; + } + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | + constants_1.WASI_FILESTAT_SET_MTIM_NOW; + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n; + } + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.utimesSync( + path.resolve(stats.path, p), + new Date(atim), + new Date(mtim), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_link: wrap( + (oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD( + oldFd, + constants_1.WASI_RIGHT_PATH_LINK_SOURCE, + ); + const nstats = CHECK_FD( + newFd, + constants_1.WASI_RIGHT_PATH_LINK_TARGET, + ); + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.linkSync( + path.resolve(ostats.path, op), + path.resolve(nstats.path, np), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_open: wrap( + ( + dirfd, + dirflags, + pathPtr, + pathLen, + oflags, + fsRightsBase, + fsRightsInheriting, + fsFlags, + fd, + ) => { + const stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN); + fsRightsBase = bigint_1.BigIntPolyfill(fsRightsBase); + fsRightsInheriting = bigint_1.BigIntPolyfill(fsRightsInheriting); + const read = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_READDIR)) !== + bigint_1.BigIntPolyfill(0); + const write = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_ALLOCATE | + constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !== + bigint_1.BigIntPolyfill(0); + let noflags; + if (write && read) { + noflags = fs.constants.O_RDWR; + } else if (read) { + noflags = fs.constants.O_RDONLY; + } else if (write) { + noflags = fs.constants.O_WRONLY; + } + // fsRightsBase is needed here but perhaps we should do it in neededInheriting + let neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN; + let neededInheriting = fsRightsBase | fsRightsInheriting; + if ((oflags & constants_1.WASI_O_CREAT) !== 0) { + noflags |= fs.constants.O_CREAT; + neededBase |= constants_1.WASI_RIGHT_PATH_CREATE_FILE; + } + if ((oflags & constants_1.WASI_O_DIRECTORY) !== 0) { + noflags |= fs.constants.O_DIRECTORY; + } + if ((oflags & constants_1.WASI_O_EXCL) !== 0) { + noflags |= fs.constants.O_EXCL; + } + if ((oflags & constants_1.WASI_O_TRUNC) !== 0) { + noflags |= fs.constants.O_TRUNC; + neededBase |= constants_1.WASI_RIGHT_PATH_FILESTAT_SET_SIZE; + } + // Convert file descriptor flags. + if ((fsFlags & constants_1.WASI_FDFLAG_APPEND) !== 0) { + noflags |= fs.constants.O_APPEND; + } + if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) { + if (fs.constants.O_DSYNC) { + noflags |= fs.constants.O_DSYNC; + } else { + noflags |= fs.constants.O_SYNC; + } + neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC; + } + if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) { + noflags |= fs.constants.O_NONBLOCK; + } + if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) { + if (fs.constants.O_RSYNC) { + noflags |= fs.constants.O_RSYNC; + } else { + noflags |= fs.constants.O_SYNC; + } + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC; + } + if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) { + noflags |= fs.constants.O_SYNC; + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC; + } + if ( + write && + (noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0 + ) { + neededInheriting |= constants_1.WASI_RIGHT_FD_SEEK; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const fullUnresolved = path.resolve(stats.path, p); + if (path.relative(stats.path, fullUnresolved).startsWith("..")) { + return constants_1.WASI_ENOTCAPABLE; + } + let full; + try { + full = fs.realpathSync(fullUnresolved); + if (path.relative(stats.path, full).startsWith("..")) { + return constants_1.WASI_ENOTCAPABLE; + } + } catch (e) { + if (e.code === "ENOENT") { + full = fullUnresolved; + } else { + throw e; + } + } + /* check if the file is a directory (unless opening for write, + * in which case the file may not exist and should be created) */ + let isDirectory; + try { + isDirectory = fs.statSync(full).isDirectory(); + } catch (e) {} + let realfd; + if (!write && isDirectory) { + realfd = fs.openSync(full, fs.constants.O_RDONLY); + } else { + realfd = fs.openSync(full, noflags); + } + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; + this.FD_MAP.set(newfd, { + real: realfd, + filetype: undefined, + offset: BigInt(0), + rights: { + base: neededBase, + inheriting: neededInheriting, + }, + path: full, + }); + stat(this, newfd); + this.view.setUint32(fd, newfd, true); + return constants_1.WASI_ESUCCESS; + }, + ), + path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const full = path.resolve(stats.path, p); + const r = fs.readlinkSync(full); + const used = buffer_1.default + .from(this.memory.buffer) + .write(r, buf, bufLen); + this.view.setUint32(bufused, used, true); + return constants_1.WASI_ESUCCESS; + }), + path_remove_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.rmdirSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + path_rename: wrap( + (oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD( + oldFd, + constants_1.WASI_RIGHT_PATH_RENAME_SOURCE, + ); + const nstats = CHECK_FD( + newFd, + constants_1.WASI_RIGHT_PATH_RENAME_TARGET, + ); + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.renameSync( + path.resolve(ostats.path, op), + path.resolve(nstats.path, np), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_SYMLINK); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.symlinkSync(op, path.resolve(stats.path, np)); + return constants_1.WASI_ESUCCESS; + }), + path_unlink_file: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_UNLINK_FILE); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.unlinkSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + poll_oneoff: (sin, sout, nsubscriptions, nevents) => { + let eventc = 0; + let waitEnd = 0; + this.refreshMemory(); + for (let i = 0; i < nsubscriptions; i += 1) { + const userdata = this.view.getBigUint64(sin, true); + sin += 8; + const type = this.view.getUint8(sin); + sin += 1; + switch (type) { + case constants_1.WASI_EVENTTYPE_CLOCK: { + sin += 7; // padding + const identifier = this.view.getBigUint64(sin, true); + sin += 8; + const clockid = this.view.getUint32(sin, true); + sin += 4; + sin += 4; // padding + const timestamp = this.view.getBigUint64(sin, true); + sin += 8; + const precision = this.view.getBigUint64(sin, true); + sin += 8; + const subclockflags = this.view.getUint16(sin, true); + sin += 2; + sin += 6; // padding + const absolute = subclockflags === 1; + let e = constants_1.WASI_ESUCCESS; + const n = bigint_1.BigIntPolyfill(now(clockid)); + if (n === null) { + e = constants_1.WASI_EINVAL; + } else { + const end = absolute ? timestamp : n + timestamp; + waitEnd = end > waitEnd ? end : waitEnd; + } + this.view.setBigUint64(sout, userdata, true); + sout += 8; + this.view.setUint16(sout, e, true); // error + sout += 2; // pad offset 2 + this.view.setUint8(sout, constants_1.WASI_EVENTTYPE_CLOCK); + sout += 1; // pad offset 3 + sout += 5; // padding to 8 + eventc += 1; + break; + } + case constants_1.WASI_EVENTTYPE_FD_READ: + case constants_1.WASI_EVENTTYPE_FD_WRITE: { + sin += 3; // padding + const fd = this.view.getUint32(sin, true); + sin += 4; + this.view.setBigUint64(sout, userdata, true); + sout += 8; + this.view.setUint16(sout, constants_1.WASI_ENOSYS, true); // error + sout += 2; // pad offset 2 + this.view.setUint8(sout, type); + sout += 1; // pad offset 3 + sout += 5; // padding to 8 + eventc += 1; + break; + } + default: + return constants_1.WASI_EINVAL; + } + } + this.view.setUint32(nevents, eventc, true); + while (bindings.hrtime() < waitEnd) { + // nothing + } + return constants_1.WASI_ESUCCESS; + }, + proc_exit: (rval) => { + bindings.exit(rval); + return constants_1.WASI_ESUCCESS; + }, + proc_raise: (sig) => { + if (!(sig in constants_1.SIGNAL_MAP)) { + return constants_1.WASI_EINVAL; + } + bindings.kill(constants_1.SIGNAL_MAP[sig]); + return constants_1.WASI_ESUCCESS; + }, + random_get: (bufPtr, bufLen) => { + this.refreshMemory(); + bindings.randomFillSync( + new Uint8Array(this.memory.buffer), + bufPtr, + bufLen, + ); + return constants_1.WASI_ESUCCESS; + }, + sched_yield() { + // Single threaded environment + // This is a no-op in JS + return constants_1.WASI_ESUCCESS; + }, + sock_recv() { + return constants_1.WASI_ENOSYS; + }, + sock_send() { + return constants_1.WASI_ENOSYS; + }, + sock_shutdown() { + return constants_1.WASI_ENOSYS; + }, + }; + // Wrap each of the imports to show the calls in the console + if (wasiConfig.traceSyscalls) { + Object.keys(this.wasiImport).forEach((key) => { + const prevImport = this.wasiImport[key]; + this.wasiImport[key] = function (...args) { + console.log(`WASI: wasiImport called: ${key} (${args})`); + try { + let result = prevImport(...args); + console.log(`WASI: => ${result}`); + return result; + } catch (e) { + console.log(`Catched error: ${e}`); + throw e; + } + }; + }); + } + } + refreshMemory() { + // @ts-ignore + if (!this.view || this.view.buffer.byteLength === 0) { + this.view = new dataview_1.DataViewPolyfill(this.memory.buffer); + } + } + setMemory(memory) { + this.memory = memory; + } + start(instance) { + const exports = instance.exports; + if (exports === null || typeof exports !== "object") { + throw new Error( + `instance.exports must be an Object. Received ${exports}.`, + ); + } + const { memory } = exports; + if (!(memory instanceof WebAssembly.Memory)) { + throw new Error( + `instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`, + ); + } + this.setMemory(memory); + if (exports._start) { + exports._start(); + } + } + getImportNamespace(module) { + let namespace = null; + for (let imp of WebAssembly.Module.imports(module)) { + // We only check for the functions + if (imp.kind !== "function") { + continue; + } + // We allow functions in other namespaces other than wasi + if (!imp.module.startsWith("wasi_")) { + continue; + } + if (!namespace) { + namespace = imp.module; + } else { + if (namespace !== imp.module) { + throw new Error("Multiple namespaces detected."); + } + } + } + return namespace; + } + getImports(module) { + let namespace = this.getImportNamespace(module); + switch (namespace) { + case "wasi_unstable": + return { + wasi_unstable: this.wasiImport, + }; + case "wasi_snapshot_preview1": + return { + wasi_snapshot_preview1: this.wasiImport, + }; + default: + throw new Error( + "Can't detect a WASI namespace for the WebAssembly Module", + ); + } + } +} +exports.default = WASIDefault; +exports.WASI = WASIDefault; diff --git a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts index d71a72f..b43c213 100644 --- a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts +++ b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts @@ -99,7 +99,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }); return { - name: identifier.ID().getText(), + name: identifier.ID(0).getText(), dimension: inputDimension, }; } diff --git a/test/unit/core/compile/circom-compiler-downloader.test.ts b/test/unit/core/compile/circom-compiler-downloader.test.ts index 66227ea..298b950 100644 --- a/test/unit/core/compile/circom-compiler-downloader.test.ts +++ b/test/unit/core/compile/circom-compiler-downloader.test.ts @@ -165,6 +165,18 @@ describe("CircomCompilerDownloader", () => { expect(reporterSpy.called).to.be.false; }); + it("should throw an error if pass unsupported WASM compiler version", async function () { + const compilersDir = getNormalizedFullPath(this.hre.config.paths.root, "compilers"); + await fsExtra.ensureDir(compilersDir); + + const platform = CompilerPlatformBinary.WASM; + const circomCompilerDownloader = CircomCompilerDownloader.getCircomCompilerDownloader(platform, compilersDir); + + await expect(circomCompilerDownloader.downloadCompiler("2.0.5", true, true)).to.be.rejectedWith( + "Unsupported WASM version - 2.0.5", + ); + }); + it("should throw an error if the downloaded compiler is not working", async function () { const compilersDir = getNormalizedFullPath(this.hre.config.paths.root, "compilers"); await fsExtra.ensureDir(compilersDir); diff --git a/test/unit/core/compile/circom-compiler-factory.test.ts b/test/unit/core/compile/circom-compiler-factory.test.ts index 76e03fb..7fedc0c 100644 --- a/test/unit/core/compile/circom-compiler-factory.test.ts +++ b/test/unit/core/compile/circom-compiler-factory.test.ts @@ -66,7 +66,7 @@ describe("CircomCompilerFactory", () => { it("should correctly create circom compiler instance", async function () { createCircomCompilerFactory(); - const compiler: ICircomCompiler = await CircomCompilerFactory!.createCircomCompiler("0.2.18", false); + const compiler: ICircomCompiler = await CircomCompilerFactory!.createCircomCompiler("2.1.7", false); const circuitFullPath: string = getNormalizedFullPath(this.hre.config.paths.root, "circuits/main/mul2.circom"); const artifactsFullPath: string = getNormalizedFullPath( @@ -96,12 +96,21 @@ describe("CircomCompilerFactory", () => { }); it("should correctly throw error if pass invalid version", async function () { - const invalidVersion = "2.1.10"; - - const reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version.`; + let invalidVersion = "2.1.10"; + let reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version.`; createCircomCompilerFactory(); + await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, true)).to.be.rejectedWith(reason); + + invalidVersion = "2.2.0"; + reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version.`; + await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, false)).to.be.rejectedWith(reason); + + invalidVersion = "2.0.4"; + reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version.`; + + await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, true)).to.be.rejectedWith(reason); }); it("should create compiler for each platform properly", async function () { diff --git a/tsconfig.json b/tsconfig.json index cbc6415..a9a7b51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", + "allowJs": true, "declaration": true, "declarationMap": true, "sourceMap": true,