diff --git a/.circleci/config.yml b/.circleci/config.yml index 67b99229f69..4ac8815bdfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,7 @@ parameters: orbs: browser-tools: circleci/browser-tools@1.4.4 win: circleci/windows@5.0 + node: circleci/node@7.0.0 jobs: build: docker: @@ -107,23 +108,25 @@ jobs: test-remixdesktop-linux: machine: - image: ubuntu-2004:current + image: ubuntu-2204:current resource_class: xlarge working_directory: ~/remix-project parallelism: 15 steps: - run: ldd --version + - checkout - attach_workspace: at: . - run: unzip ./persist/desktopbuild.zip + - node/install: + install-yarn: true + node-version: '20.2' - run: command: | - nvm install 20.2 - nvm use 20.2 node -v - npm install --global yarn node-gyp + yarn global add node-gyp@10.2.0 python -m pip install --upgrade pip pip install setuptools mkdir apps/remixdesktop/build @@ -136,13 +139,11 @@ jobs: - run: name: "Run tests" command: | - nvm use 20.2 cd apps/remixdesktop/ ./run_ci_test.sh - run: name: "Run isogit tests" command: | - nvm use 20.2 cd apps/remixdesktop/ ./run_git_ui_isogit_tests.sh - store_test_results: @@ -521,6 +522,7 @@ jobs: ls -la dist/apps/remix-ide nvm install 20.2 nvm use 20.2 + /usr/sbin/softwareupdate --install-rosetta --agree-to-license - restore_cache: keys: - remixdesktop-deps-mac-{{ checksum "apps/remixdesktop/yarn.lock" }} diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml index 8e53271e6d9..7d1717165b4 100644 --- a/.github/workflows/pr-reminder.yml +++ b/.github/workflows/pr-reminder.yml @@ -3,7 +3,7 @@ name: PRs reviews reminder on: schedule: - cron: "0 8 * * 1-5" - - cron: '58 9 * * 1-5' + - cron: '55 9 * * 1-5' workflow_dispatch: jobs: @@ -19,7 +19,7 @@ jobs: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} freeze-date: '2024-12-23T18:00:00Z' - name: Reminder for standup - if: github.event.schedule == '58 9 * * 1-5' + if: github.event.schedule == '55 9 * * 1-5' uses: Aniket-Engg/pr-reviews-reminder-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 9e408323247..7955bf6455c 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,8 @@ apps/remix-ide/src/assets/esbuild.wasm apps/remixdesktop/build* apps/remixdesktop/reports apps/remixdesktop/logs/ +apps/remixdesktop/bin/ +apps/remixdesktop/circom-download +apps/remixdesktop/log_input_signals.txt +apps/remixdesktop/log_input_signals_new.txt logs diff --git a/apps/circuit-compiler/src/app/actions/index.ts b/apps/circuit-compiler/src/app/actions/index.ts index 84417c7feac..894ef998b5a 100644 --- a/apps/circuit-compiler/src/app/actions/index.ts +++ b/apps/circuit-compiler/src/app/actions/index.ts @@ -3,6 +3,7 @@ import type { CircomPluginClient } from "../services/circomPluginClient" import { ActionPayloadTypes, AppState, ICircuitAppContext } from "../types" import { GROTH16_VERIFIER, PLONK_VERIFIER } from './constant' import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper' +import isElectron from 'is-electron' export const compileCircuit = async (plugin: CircomPluginClient, appState: AppState) => { try { @@ -28,7 +29,7 @@ export const computeWitness = async (plugin: CircomPluginClient, appState: AppSt const wtns = await snarkjs.wtns.exportJson(witness) const wtnsJson = wtns.map(wtn => wtn.toString()) const fileName = extractNameFromKey(appState.filePath) - const writePath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '.wtn.json')}` + const writePath = extractParentFromKey(appState.filePath) + `/.bin/${fileName.replace('.circom', '_js')}/${fileName.replace('.circom', '.wtn.json')}` await plugin.call('fileManager', 'writeFile', writePath, JSON.stringify(wtnsJson, null, 2)) plugin._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'wtns.exportJson', writePath]) @@ -116,7 +117,7 @@ export const generateProof = async (plugin: CircomPluginClient, appState: AppSta const r1csBuffer = await plugin.call('fileManager', 'readFile', r1csPath, { encoding: null }) // @ts-ignore const r1cs = new Uint8Array(r1csBuffer) - const wtnsPath = r1csPath.replace('.r1cs', '.wtn') + const wtnsPath = isElectron() ? extractParentFromKey(appState.filePath) + "/.bin/" + fileName.replace('.circom', '_js') + "/" + fileName.replace('.circom', '.wtn') : r1csPath.replace('.r1cs', '.wtn') // @ts-ignore const wtnsBuffer = await plugin.call('fileManager', 'readFile', wtnsPath, { encoding: null }) // @ts-ignore diff --git a/apps/circuit-compiler/src/app/app.tsx b/apps/circuit-compiler/src/app/app.tsx index 1e3e6a4a9c3..b39b3cddc72 100644 --- a/apps/circuit-compiler/src/app/app.tsx +++ b/apps/circuit-compiler/src/app/app.tsx @@ -21,6 +21,11 @@ function App() { useEffect(() => { plugin.internalEvents.on('circom_activated', () => { + (async () => { + const downloadList = await plugin.getCompilerDownloadList() + + dispatch({ type: 'SET_VERSION_DOWNLOAD_LIST', payload: downloadList }) + })(); // @ts-ignore plugin.on('locale', 'localeChanged', (locale: any) => { setLocale(locale) @@ -47,6 +52,7 @@ function App() { signalInputs = (signalInputs || []).filter(input => input) dispatch({ type: 'SET_SIGNAL_INPUTS', payload: signalInputs }) dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }) + dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null }) }) plugin.internalEvents.on('circuit_compiling_errored', compilerErrored) @@ -56,7 +62,16 @@ function App() { dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }) dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: null }) }) - plugin.internalEvents.on('circuit_computing_witness_errored', compilerErrored) + plugin.internalEvents.on('circuit_computing_witness_errored', (err) => { + dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }) + try { + const report = JSON.parse(err.message) + + dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report }) + } catch (e) { + dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message }) + } + }) // parsing events plugin.internalEvents.on('circuit_parsing_done', (_, filePathToId) => { @@ -73,6 +88,13 @@ function App() { dispatch({ type: 'SET_COMPILER_STATUS', payload: 'warning' }) dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report }) }) + plugin.internalEvents.on('download_success', (version) => { + dispatch({ type: 'REMOVE_VERSION_FROM_DOWNLOAD_LIST', payload: version }) + dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null }) + }) + plugin.internalEvents.on('download_failed', (error) => { + dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: 'Download failed! Please check your internet connection. ' + error.message }) + }) }, []) useEffect(() => { @@ -120,9 +142,10 @@ function App() { try { const report = JSON.parse(err.message) - dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: report }) + dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report }) } catch (e) { - dispatch({ type: 'SET_COMPUTE_FEEDBACK', payload: err.message }) + if (process.platform === 'win32' && err.message.includes('3221225781')) return dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: 'The compiler failed to start because of some missing dependencies. Please install or repair the Microsoft Visual C++ Redistributable package to resolve this issue.' }) + dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: err.message }) } } diff --git a/apps/circuit-compiler/src/app/components/configurations.tsx b/apps/circuit-compiler/src/app/components/configurations.tsx index 4ef4c98b14b..d7d0e91eecb 100644 --- a/apps/circuit-compiler/src/app/components/configurations.tsx +++ b/apps/circuit-compiler/src/app/components/configurations.tsx @@ -52,7 +52,7 @@ export function Configurations ({primeValue, setPrimeValue, versionValue}: Confi vesta > - + <> bn128 bls12381 diff --git a/apps/circuit-compiler/src/app/components/container.tsx b/apps/circuit-compiler/src/app/components/container.tsx index 765f33abe39..c1034576f70 100644 --- a/apps/circuit-compiler/src/app/components/container.tsx +++ b/apps/circuit-compiler/src/app/components/container.tsx @@ -118,7 +118,7 @@ export function Container () { > showCompilerLicense()}> - + diff --git a/apps/circuit-compiler/src/app/components/generateProof.tsx b/apps/circuit-compiler/src/app/components/generateProof.tsx index 84f3a018f0c..25fcd773862 100644 --- a/apps/circuit-compiler/src/app/components/generateProof.tsx +++ b/apps/circuit-compiler/src/app/components/generateProof.tsx @@ -27,7 +27,7 @@ export function GenerateProof () { className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-1" onClick={() => generateProof(circuitApp.plugin, circuitApp.appState, circuitApp.dispatch)} disabled={(status === "compiling") || (status === "computing") || (status === "proving") || (status === "exporting")} - data-id="compute_witness_btn" + data-id="generateProofBtn" > diff --git a/apps/circuit-compiler/src/app/components/versions.tsx b/apps/circuit-compiler/src/app/components/versions.tsx index 4b129d82259..959f7c68992 100644 --- a/apps/circuit-compiler/src/app/components/versions.tsx +++ b/apps/circuit-compiler/src/app/components/versions.tsx @@ -1,26 +1,96 @@ -import { RenderIf } from "@remix-ui/helper"; -import { AppState } from "../types"; +import { AppState } from "../types" +import { Dropdown } from "react-bootstrap" +import React, { Ref } from "react" +import isElectron from 'is-electron' -export function VersionList ({ currentVersion, versionList, setVersion }: { versionList: AppState['versionList'], currentVersion: string, setVersion: (version: string) => void }) { +export function VersionList ({ currentVersion, versionList, downloadList, setVersion }: { versionList: AppState['versionList'], currentVersion: string, setVersion: (version: string) => void , downloadList: string[]}) { const versionListKeys = Object.keys(versionList) - return ( - setVersion(e.target.value)} - className="custom-select" - > - 0}> - <> - { - versionListKeys.map((version, index) => ( - - { versionList[version].name } - - )) - } - > - - + + + + { versionList[currentVersion].name } + + + + + { + versionListKeys.reverse().map((version, index) => ( + { + setVersion(version) + }}> + + + + + { isElectron() ? versionList[version].name.replace('wasm', '') : versionList[version].name } + + + { isElectron() ? downloadList.includes(version) ? : : null } + + + )) + } + + ) } + +const CircomVersionMenuToggle = React.forwardRef( + ( + { + children, + onClick, + className = '' + }: { + children: React.ReactNode + onClick: (e) => void + className: string + }, + ref: Ref + ) => ( + { + e.preventDefault() + onClick(e) + }} + className={className.replace('dropdown-toggle', '')} + > + + {children} + + + + + + ) +) + +const CircomVersionMenu = React.forwardRef( + ( + { + children, + style, + 'data-id': dataId, + className, + 'aria-labelledby': labeledBy + }: { + 'children': React.ReactNode + 'style'?: React.CSSProperties + 'data-id'?: string + 'className': string + 'aria-labelledby'?: string + }, + ref: Ref + ) => { + const height = window.innerHeight * 0.6 + return ( + + + {children} + + + ) + } +) diff --git a/apps/circuit-compiler/src/app/reducers/state.ts b/apps/circuit-compiler/src/app/reducers/state.ts index cc3db06ca2e..069dd8d9b44 100644 --- a/apps/circuit-compiler/src/app/reducers/state.ts +++ b/apps/circuit-compiler/src/app/reducers/state.ts @@ -1,10 +1,21 @@ import { PTAU_LIST } from '../actions/constant' import { Actions, AppState } from '../types' import { compiler_list } from 'circom_wasm' +import isElectron from 'is-electron' + +const VersionList = isElectron() ? { ...compiler_list.wasm_builds, + "latest": { + "name": "latest", + "version": "latest", + "repo": "", + "build_source": "" + } +} : compiler_list.wasm_builds export const appInitialState: AppState = { version: compiler_list.latest, versionList: compiler_list.wasm_builds, + versionDownloadList: [], filePath: "", filePathToId: {}, status: "idle", @@ -157,6 +168,18 @@ export const appReducer = (state = appInitialState, action: Actions): AppState = zKey: action.payload } + case 'SET_VERSION_DOWNLOAD_LIST': + return { + ...state, + versionDownloadList: action.payload + } + + case 'REMOVE_VERSION_FROM_DOWNLOAD_LIST': + return { + ...state, + versionDownloadList: state.versionDownloadList.filter(version => version !== action.payload) + } + default: throw new Error() } diff --git a/apps/circuit-compiler/src/app/services/circomPluginClient.ts b/apps/circuit-compiler/src/app/services/circomPluginClient.ts index 15bfa518a0b..f9421eb5282 100644 --- a/apps/circuit-compiler/src/app/services/circomPluginClient.ts +++ b/apps/circuit-compiler/src/app/services/circomPluginClient.ts @@ -8,12 +8,13 @@ import * as compilerV217 from 'circom_wasm/v2.1.7' import * as compilerV216 from 'circom_wasm/v2.1.6' import * as compilerV215 from 'circom_wasm/v2.1.5' import { extractNameFromKey, extractParentFromKey } from '@remix-ui/helper' -import { CompilationConfig, CompilerReport, PrimeValue, ResolverOutput } from '../types' +import { CompilationConfig, CompilerReport, PrimeValue } from '../types' +import isElectron from 'is-electron' export class CircomPluginClient extends PluginClient { public internalEvents: EventManager private _compilationConfig: CompilationConfig = { - version: "2.1.8", + version: compiler_list.latest, prime: "bn128" } private lastCompiledCircuitPath: string = '' @@ -43,13 +44,23 @@ export class CircomPluginClient extends PluginClient { } set compilerVersion (version: string) { - if (!compiler_list.versions.includes(version)) throw new Error("Unsupported compiler version") + if (!compiler_list.versions.includes(version) && version !== "latest") throw new Error("Unsupported compiler version") this._compilationConfig.version = version - if (version === '2.1.5') this.compiler = compilerV215 - else if (version === '2.1.6') this.compiler = compilerV216 - else if (version === '2.1.7') this.compiler = compilerV217 - else if (version === '2.1.8') this.compiler = compilerV218 - else this.compiler = null + if (isElectron()) { + const versionToInstall = version === 'latest' ? 'latest' : `v${version}` + // @ts-ignore + this.call('circom', 'install', versionToInstall).then(() => { + this.internalEvents.emit('download_success', version) + }).catch((e) => { + this.internalEvents.emit('download_failed', e) + }) + } else { + if (version === '2.1.5') this.compiler = compilerV215 + else if (version === '2.1.6') this.compiler = compilerV216 + else if (version === '2.1.7') this.compiler = compilerV217 + else if (version === '2.1.8') this.compiler = compilerV218 + else this.compiler = null + } } set compilerPrime (prime: PrimeValue) { @@ -135,6 +146,75 @@ export class CircomPluginClient extends PluginClient { } async compile(path: string, compilationConfig?: CompilationConfig): Promise { + if (isElectron()) { + await this.compileElectron(path) + } else { + this.internalEvents.emit('circuit_compiling_start') + this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' }) + // @ts-ignore + this.call('terminal', 'log', { type: 'log', value: 'Compiling ' + path }) + const [parseErrors, filePathToId] = await this.parse(path) + + if (parseErrors && (parseErrors.length > 0)) { + if (parseErrors[0].type === 'Error') { + this.internalEvents.emit('circuit_parsing_errored', parseErrors, filePathToId) + this.logCompilerReport(parseErrors) + return + } + } + if (compilationConfig) { + const { prime, version } = compilationConfig + + this.compilerVersion = version + this.compilerPrime = prime + } + const circuitApi = this.compiler ? this.compiler.compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) + const circuitProgram = circuitApi.program() + + if (circuitProgram.length < 1) { + const circuitErrors = circuitApi.report() + + this.logCompilerReport(circuitErrors) + this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed']) + throw new Error(circuitErrors) + } else { + this.lastCompiledFile = path + const fileName = extractNameFromKey(path) + + this.lastCompiledCircuitPath = extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '_js') + '/' + fileName.replace('.circom', '.wasm') + // @ts-ignore + await this.call('fileManager', 'writeFile', this.lastCompiledCircuitPath, circuitProgram, { encoding: null }) + const fileContent = this.lastParsedFiles[path] + const searchComponentName = fileContent.match(/component\s+main\s*(?:{[^{}]*})?\s*=\s*([A-Za-z_]\w*)\s*\(.*\)/) + + if (searchComponentName) { + const componentName = searchComponentName[1] + const signals = circuitApi.input_signals(componentName) + + this.internalEvents.emit('circuit_compiling_done', signals) + } else { + this.internalEvents.emit('circuit_compiling_done', []) + } + if (parseErrors && (parseErrors.length > 0)) { + if (parseErrors[0].type === 'Warning') { + this.internalEvents.emit('circuit_parsing_warning', parseErrors, filePathToId) + this.logCompilerReport(parseErrors) + } + } else { + this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId) + this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' }) + } + this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful']) + circuitApi.log().map(log => { + log && this.call('terminal', 'log', { type: 'log', value: log }) + }) + // @ts-ignore + this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' }) + } + } + } + + async compileElectron (path: string): Promise { this.internalEvents.emit('circuit_compiling_start') this.emit('statusChanged', { key: 'loading', title: 'Compiling...', type: 'info' }) // @ts-ignore @@ -146,7 +226,30 @@ export class CircomPluginClient extends PluginClient { this.internalEvents.emit('circuit_parsing_errored', parseErrors, filePathToId) this.logCompilerReport(parseErrors) return - } else if (parseErrors[0].type === 'Warning') { + } + } + const { version, prime } = this._compilationConfig + const versionToInstall = version === 'latest' ? 'latest' : `v${version}` + try { + // @ts-ignore + const { stdout, stderr } = await this.call('circom', 'run', path, versionToInstall, { prime: prime, wasm: "", inputs: "" }) + const fileName = extractNameFromKey(path) + + this.lastCompiledCircuitPath = pathModule.normalize(extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '_js') + "/" + fileName.replace('.circom', '.wasm')) + if (stderr) this.call('terminal', 'log', { type: 'error', value: stderr }) + if (stdout) this.call('terminal', 'log', { type: 'log', value: stdout }) + } catch (error) { + this.call('terminal', 'log', { type: 'error', value: error.message }) + this.internalEvents.emit('circuit_compiling_errored', error) + this.emit('statusChanged', { key: 'errored', title: 'Compilation failed', type: 'error' }) + return + } + // @ts-ignore + const signals = await this.call('circom', 'getInputs') + + this.internalEvents.emit('circuit_compiling_done', signals) + if (parseErrors && (parseErrors.length > 0)) { + if (parseErrors[0].type === 'Warning') { this.internalEvents.emit('circuit_parsing_warning', parseErrors, filePathToId) this.logCompilerReport(parseErrors) } @@ -154,49 +257,58 @@ export class CircomPluginClient extends PluginClient { this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId) this.emit('statusChanged', { key: 'succeed', title: 'circuit compiled successfully', type: 'success' }) } - if (compilationConfig) { - const { prime, version } = compilationConfig - - this.compilerVersion = version - this.compilerPrime = prime - } - const circuitApi = this.compiler ? this.compiler.compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : compile(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) - const circuitProgram = circuitApi.program() - - if (circuitProgram.length < 1) { - const circuitErrors = circuitApi.report() + } - this.logCompilerReport(circuitErrors) - this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation failed']) - throw new Error(circuitErrors) + async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise { + if (isElectron()) { + await this.generateR1csElectron(path) } else { - this.lastCompiledFile = path - const fileName = extractNameFromKey(path) + const [parseErrors, filePathToId] = await this.parse(path) + + if (parseErrors && (parseErrors.length > 0)) { + if (parseErrors[0].type === 'Error') { + this.logCompilerReport(parseErrors) + return + } else if (parseErrors[0].type === 'Warning') { + this.logCompilerReport(parseErrors) + } + } + if (compilationConfig) { + const { prime, version } = compilationConfig - this.lastCompiledCircuitPath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'wasm') - // @ts-ignore - await this.call('fileManager', 'writeFile', this.lastCompiledCircuitPath, circuitProgram, { encoding: null }) - const fileContent = this.lastParsedFiles[path] - const searchComponentName = fileContent.match(/component\s+main\s*(?:{[^{}]*})?\s*=\s*([A-Za-z_]\w*)\s*\(.*\)/) + this.compilerVersion = version + this.compilerPrime = prime + } + const r1csApi = this.compiler ? this.compiler.generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) + const r1csProgram = r1csApi.program() - if (searchComponentName) { - const componentName = searchComponentName[1] - const signals = circuitApi.input_signals(componentName) + if (r1csProgram.length < 1) { + const r1csErrors = r1csApi.report() - this.internalEvents.emit('circuit_compiling_done', signals) + this.logCompilerReport(r1csErrors) + this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed']) + throw new Error(r1csErrors) } else { - this.internalEvents.emit('circuit_compiling_done', []) + const fileName = extractNameFromKey(path) + const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('.circom', '.r1cs') + + // @ts-ignore + await this.call('fileManager', 'writeFile', writePath, r1csProgram, true) + this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful']) + r1csApi.log().map(log => { + log && this.call('terminal', 'log', { type: 'log', value: log }) + }) + // @ts-ignore + this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' }) } - this._paq.push(['trackEvent', 'circuit-compiler', 'compile', 'Compilation successful']) - circuitApi.log().map(log => { - log && this.call('terminal', 'log', { type: 'log', value: log }) - }) - // @ts-ignore - this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' }) } } - async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise { + async generateR1csElectron (path: string): Promise { + this.internalEvents.emit('circuit_generating_r1cs_start') + this.emit('statusChanged', { key: 'loading', title: 'Generating...', type: 'info' }) + // @ts-ignore + this.call('terminal', 'log', { type: 'log', value: 'Generating R1CS for ' + path }) const [parseErrors, filePathToId] = await this.parse(path) if (parseErrors && (parseErrors.length > 0)) { @@ -207,34 +319,15 @@ export class CircomPluginClient extends PluginClient { this.logCompilerReport(parseErrors) } } - if (compilationConfig) { - const { prime, version } = compilationConfig - - this.compilerVersion = version - this.compilerPrime = prime - } - const r1csApi = this.compiler ? this.compiler.generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) : generate_r1cs(path, this.lastParsedFiles, { prime: this._compilationConfig.prime }) - const r1csProgram = r1csApi.program() - - if (r1csProgram.length < 1) { - const r1csErrors = r1csApi.report() - - this.logCompilerReport(r1csErrors) - this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation failed']) - throw new Error(r1csErrors) - } else { - const fileName = extractNameFromKey(path) - const writePath = extractParentFromKey(path) + "/.bin/" + fileName.replace('circom', 'r1cs') - - // @ts-ignore - await this.call('fileManager', 'writeFile', writePath, r1csProgram, true) - this._paq.push(['trackEvent', 'circuit-compiler', 'generateR1cs', 'R1CS Generation successful']) - r1csApi.log().map(log => { - log && this.call('terminal', 'log', { type: 'log', value: log }) - }) - // @ts-ignore - this.call('terminal', 'log', { type: 'typewritersuccess', value: 'Everything went okay' }) - } + const { version, prime } = this._compilationConfig + const versionToInstall = version === 'latest' ? 'latest' : `v${version}` + // @ts-ignore + await this.call('circom', 'run', path, versionToInstall, { + prime: prime, + r1cs: "" + }) + this.internalEvents.emit('circuit_generating_r1cs_done') + this.emit('statusChanged', { key: 'succeed', title: 'r1cs generated successfully', type: 'success' }) } async computeWitness (input: string): Promise { @@ -248,7 +341,7 @@ export class CircomPluginClient extends PluginClient { const dataRead = new Uint8Array(buffer) const witness = this.compiler ? await this.compiler.generate_witness(dataRead, input) : await generate_witness(dataRead, input) // @ts-ignore - await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, true) + await this.call('fileManager', 'writeFile', wasmPath.replace('.wasm', '.wtn'), witness, { encoding: null }) this._paq.push(['trackEvent', 'circuit-compiler', 'computeWitness', 'compiler.generate_witness', wasmPath.replace('.wasm', '.wtn')]) this.internalEvents.emit('circuit_computing_witness_done') this.emit('statusChanged', { key: 'succeed', title: 'witness computed successfully', type: 'success' }) @@ -298,7 +391,7 @@ export class CircomPluginClient extends PluginClient { path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/${version[0]}/${splitInclude.slice(2).join('/')}` dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null) } else { - path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}` + path = `.deps/https/raw.githubusercontent.com/iden3/circomlib/${splitInclude.slice(1).join('/')}` dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null) } } catch (e) { @@ -308,7 +401,15 @@ export class CircomPluginClient extends PluginClient { dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null) } else { path = `https://raw.githubusercontent.com/iden3/circomlib/master/${splitInclude.slice(1).join('/')}` - dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null) + const { content } = await this.call('contentImport', 'resolve', path) + + if (content) { + dependencyContent = await this.call('contentImport', 'resolveAndSave', path, null) + const savePath = `.deps/https/raw.githubusercontent.com/iden3/${include}` + + // @ts-ignore + await this.call('fileManager', 'writeFile', savePath, dependencyContent) + } } } } else { @@ -420,4 +521,17 @@ export class CircomPluginClient extends PluginClient { this.emit('statusChanged', { key: report.length, title: `You have ${report.length} problem${report.length === 1 ? '' : 's'}`, type: 'warning' }) } } + + async getCompilerDownloadList () { + if (!isElectron()) return [] + else { + return await Promise.all(compiler_list.versions.map(async (version) => { + const versionToInstall = version === 'latest' ? 'latest' : `v${version}` + // @ts-ignore + const exists = await this.call('circom', 'isVersionInstalled', versionToInstall) + + if (!exists) return version + })) + } + } } diff --git a/apps/circuit-compiler/src/app/types/index.ts b/apps/circuit-compiler/src/app/types/index.ts index b1ad621b941..bd770bbec17 100644 --- a/apps/circuit-compiler/src/app/types/index.ts +++ b/apps/circuit-compiler/src/app/types/index.ts @@ -23,6 +23,8 @@ export interface ICircuitAppContext { export interface ActionPayloadTypes { SET_COMPILER_VERSION: string, + SET_VERSION_DOWNLOAD_LIST: string[], + REMOVE_VERSION_FROM_DOWNLOAD_LIST: string, SET_FILE_PATH: string, SET_COMPILER_STATUS: CompilerStatus, SET_PRIME_VALUE: PrimeValue, @@ -54,6 +56,7 @@ export type Actions = {[A in keyof ActionPayloadTypes]: Action}[keyof ActionP export interface AppState { version: string, versionList: typeof compiler_list.wasm_builds, + versionDownloadList: string[], filePath: string, filePathToId: Record, status: CompilerStatus, diff --git a/apps/circuit-compiler/src/css/app.css b/apps/circuit-compiler/src/css/app.css index f229a913b43..d1743dffbb8 100644 --- a/apps/circuit-compiler/src/css/app.css +++ b/apps/circuit-compiler/src/css/app.css @@ -84,3 +84,18 @@ body { bottom: 0; right: 0; } +.custom-dropdown-items { + padding: 0.25rem 0.25rem; + border-radius: .25rem; + background: var(--custom-select); +} +.custom-dropdown-items a { + border-radius: .25rem; + text-transform: none; + text-decoration: none; + font-weight: normal; + font-size: 0.875rem; + padding: 0.25rem 0.25rem; + width: auto; + color: var(--text); +} diff --git a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts index 8554cb29f6b..c68ecf5cdbc 100644 --- a/apps/contract-verification/src/app/ContractVerificationPluginClient.ts +++ b/apps/contract-verification/src/app/ContractVerificationPluginClient.ts @@ -1,12 +1,16 @@ import { PluginClient } from '@remixproject/plugin' import { createClient } from '@remixproject/plugin-webview' import EventManager from 'events' +import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier } from './types' +import { mergeChainSettingsWithDefaults, validConfiguration } from './utils' +import { getVerifier } from './Verifiers' export class ContractVerificationPluginClient extends PluginClient { public internalEvents: EventManager constructor() { super() + this.methods = ['lookupAndSave'] this.internalEvents = new EventManager() createClient(this) this.onload() @@ -15,4 +19,57 @@ export class ContractVerificationPluginClient extends PluginClient { onActivation(): void { this.internalEvents.emit('verification_activated') } + + async lookupAndSave(verifierId: string, chainId: string, contractAddress: string): Promise { + const canonicalVerifierId = VERIFIERS.find((id) => id.toLowerCase() === verifierId.toLowerCase()) + if (!canonicalVerifierId) { + console.error(`lookupAndSave failed: Unknown verifier: ${verifierId}`) + return + } + + const userSettings = this.getUserSettingsFromLocalStorage() + const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) + + try { + const lookupResult = await this.lookup(canonicalVerifierId, chainSettings, chainId, contractAddress) + await this.saveToRemix(lookupResult) + return lookupResult + } catch (err) { + console.error(`lookupAndSave failed: ${err}`) + } + } + + async lookup(verifierId: VerifierIdentifier, chainSettings: ChainSettings, chainId: string, contractAddress: string): Promise { + if (!validConfiguration(chainSettings, verifierId)) { + throw new Error(`Error during lookup: Invalid configuration given for verifier ${verifierId}`) + } + const verifier = getVerifier(verifierId, chainSettings.verifiers[verifierId]) + return await verifier.lookup(contractAddress, chainId) + } + + async saveToRemix(lookupResponse: LookupResponse): Promise { + for (const source of lookupResponse.sourceFiles ?? []) { + try { + await this.call('fileManager', 'setFile', source.path, source.content) + } catch (err) { + throw new Error(`Error while creating file ${source.path}: ${err.message}`) + } + } + try { + await this.call('fileManager', 'open', lookupResponse.targetFilePath) + } catch (err) { + throw new Error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`) + } + } + + private getUserSettingsFromLocalStorage(): ContractVerificationSettings { + const fallbackSettings = { chains: {} }; + try { + const settings = window.localStorage.getItem("contract-verification:settings") + return settings ? JSON.parse(settings) : fallbackSettings + } catch (error) { + console.error(error) + return fallbackSettings + } + } } diff --git a/apps/contract-verification/src/app/views/LookupView.tsx b/apps/contract-verification/src/app/views/LookupView.tsx index 00de368cac1..d3e4c530954 100644 --- a/apps/contract-verification/src/app/views/LookupView.tsx +++ b/apps/contract-verification/src/app/views/LookupView.tsx @@ -5,7 +5,6 @@ import type { LookupResponse, VerifierIdentifier } from '../types' import { VERIFIERS } from '../types' import { AppContext } from '../AppContext' import { CustomTooltip } from '@remix-ui/helper' -import { getVerifier } from '../Verifiers' import { useNavigate } from 'react-router-dom' import { VerifyFormContext } from '../VerifyFormContext' import { useSourcifySupported } from '../hooks/useSourcifySupported' @@ -25,7 +24,7 @@ export const LookupView = () => { const sourcifySupported = useSourcifySupported(selectedChain, chainSettings) const noVerifierEnabled = VERIFIERS.every((verifierId) => !validConfiguration(chainSettings, verifierId) || (verifierId === 'Sourcify' && !sourcifySupported)) - const submitDisabled = !!contractAddressError || !contractAddress || !selectedChain || noVerifierEnabled + const submitDisabled = !!contractAddressError || !contractAddress || !selectedChain || noVerifierEnabled || Object.values(loadingVerifiers).some((loading) => loading) // Reset results when chain or contract changes useEffect(() => { @@ -47,9 +46,7 @@ export const LookupView = () => { } setLoadingVerifiers((prev) => ({ ...prev, [verifierId]: true })) - const verifier = getVerifier(verifierId, chainSettings.verifiers[verifierId]) - verifier - .lookup(contractAddress, selectedChain.chainId.toString()) + clientInstance.lookup(verifierId, chainSettings, selectedChain.chainId.toString(), contractAddress) .then((result) => setLookupResult((prev) => ({ ...prev, [verifierId]: result }))) .catch((err) => setLookupResult((prev) => { @@ -66,18 +63,11 @@ export const LookupView = () => { } const handleOpenInRemix = async (lookupResponse: LookupResponse) => { - for (const source of lookupResponse.sourceFiles ?? []) { - try { - await clientInstance.call('fileManager', 'setFile', source.path, source.content) - } catch (err) { - console.error(`Error while creating file ${source.path}: ${err.message}`) - } - } try { - await clientInstance.call('fileManager', 'open', lookupResponse.targetFilePath) + await clientInstance.saveToRemix(lookupResponse) await sendToMatomo('lookup', 'openInRemix On: ' + selectedChain) } catch (err) { - console.error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`) + console.error(`Error while trying to open in Remix: ${err.message}`) } } diff --git a/apps/remix-dapp/src/locales/en/terminal.json b/apps/remix-dapp/src/locales/en/terminal.json index 562ec9873c3..a3d32452c91 100644 --- a/apps/remix-dapp/src/locales/en/terminal.json +++ b/apps/remix-dapp/src/locales/en/terminal.json @@ -15,7 +15,7 @@ "terminal.welcomeText8": "Right-click on a JavaScript file in the file explorer and then click `Run`", "terminal.welcomeText9": "The following libraries are accessible", "terminal.welcomeText10": "Type the library name to see available commands", - "terminal.text1": "This type of command has been deprecated and is not functionning anymore. Please run remix.help() to list available commands.", + "terminal.text1": "This type of command has been deprecated and is not functioning anymore. Please run remix.help() to list available commands.", "terminal.hideTerminal": "Hide Terminal", "terminal.showTerminal": "Show Terminal", "terminal.clearConsole": "Clear console", diff --git a/apps/remix-ide-e2e/src/tests/circom.test.ts b/apps/remix-ide-e2e/src/tests/circom.test.ts index 6228cda5caa..41edb0332c0 100644 --- a/apps/remix-ide-e2e/src/tests/circom.test.ts +++ b/apps/remix-ide-e2e/src/tests/circom.test.ts @@ -38,8 +38,8 @@ module.exports = { .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="play-editor"]') - .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') }, 'Should compute a witness for a simple circuit #group1': function (browser: NightwatchBrowser) { browser @@ -55,8 +55,8 @@ module.exports = { .click('[data-id="compute_witness_btn"]') .frameParent() .clickLaunchIcon('filePanel') - .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wtn"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wtn"]') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]') }, 'Should compile a simple circuit using compile button in circom plugin #group2': function (browser: NightwatchBrowser) { browser @@ -70,8 +70,8 @@ module.exports = { .click('button[data-id="compile_circuit_btn"]') .frameParent() .clickLaunchIcon('filePanel') - .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') }, 'Should run Groth16 setup and export for a simple circuit using the GUI #group2': function (browser: NightwatchBrowser) { browser @@ -116,8 +116,8 @@ module.exports = { return actions.keyDown(this.Keys.CONTROL).sendKeys('s') }) - .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') }, 'Should display warnings for compiled circuit without pragma version #group4': function (browser: NightwatchBrowser) { browser @@ -164,7 +164,7 @@ module.exports = { .waitForElementNotPresent('[data-id="circuit_feedback"]') .frameParent() .clickLaunchIcon('filePanel') - .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') }, 'Should create a new workspace using hash checker template #group5 #group6': function (browser: NightwatchBrowser) { browser diff --git a/apps/remix-ide-e2e/src/tests/metamask.test.ts b/apps/remix-ide-e2e/src/tests/metamask.test.ts index 4e63909f2f2..f89016d76c1 100644 --- a/apps/remix-ide-e2e/src/tests/metamask.test.ts +++ b/apps/remix-ide-e2e/src/tests/metamask.test.ts @@ -46,14 +46,13 @@ const tests = { 'Should connect to Sepolia Test Network using MetaMask #group1': function (browser: NightwatchBrowser) { browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') .setupMetamask(passphrase, password) - .useCss().switchBrowserTab(0) + .useCss() + .switchBrowserTab(0) .refreshPage() .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .click('*[data-id="landingPageStartSolidity"]') .clickLaunchIcon('udapp') .switchEnvironment('injected-MetaMask') - .waitForElementPresent('*[data-id="settingsNetworkEnv"]') - .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') .pause(5000) .switchBrowserWindow(extension_url, 'MetaMask', (browser) => { browser @@ -65,6 +64,8 @@ const tests = { .click('*[data-testid="page-container-footer-next"]') }) .switchBrowserTab(0) // back to remix + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') }, 'Should add a contract file #group1': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/quickDapp.test.ts b/apps/remix-ide-e2e/src/tests/quickDapp.test.ts index a09581fc485..c557a9740c4 100644 --- a/apps/remix-ide-e2e/src/tests/quickDapp.test.ts +++ b/apps/remix-ide-e2e/src/tests/quickDapp.test.ts @@ -40,8 +40,7 @@ const tests = { .click('*[data-id="landingPageStartSolidity"]') .clickLaunchIcon('udapp') .switchEnvironment('injected-MetaMask') - .waitForElementPresent('*[data-id="settingsNetworkEnv"]') - .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') + .pause(5000) .switchBrowserWindow(extension_url, 'MetaMask', (browser) => { browser @@ -54,6 +53,8 @@ const tests = { // .click('*[data-testid="popover-close"]') }) .switchBrowserTab(0) // back to remix + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') }, 'Should load quick-dapp plugin #group1': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts index 142e0833ee4..25c093d5d8c 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy_injected.test.ts @@ -45,8 +45,6 @@ const tests = { .click('*[data-id="landingPageStartSolidity"]') .clickLaunchIcon('udapp') .switchEnvironment('injected-MetaMask') - .waitForElementPresent('*[data-id="settingsNetworkEnv"]') - .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') .pause(5000) .switchBrowserWindow(extension_url, 'MetaMask', (browser) => { browser @@ -60,6 +58,8 @@ const tests = { // .click('*[data-testid="popover-close"]') }) .switchBrowserTab(0) // back to remix + .waitForElementPresent('*[data-id="settingsNetworkEnv"]') + .assert.containsText('*[data-id="settingsNetworkEnv"]', 'Sepolia (11155111) network') }, 'Should add a contract file #group1': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 23addcdf905..ab3b9ff2464 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -66,7 +66,7 @@ import { FoundryHandle } from './app/files/foundry-handle' import { FoundryHandleDesktop } from './app/plugins/electron/foundryPlugin' import { HardhatHandle } from './app/files/hardhat-handle' import { HardhatHandleDesktop } from './app/plugins/electron/hardhatPlugin' - +import { circomPlugin } from './app/plugins/electron/circomElectronPlugin' import { GitPlugin } from './app/plugins/git' import { Matomo } from './app/plugins/matomo' @@ -419,6 +419,8 @@ class AppComponent { this.engine.register([xterm]) const ripgrep = new ripgrepPlugin() this.engine.register([ripgrep]) + const circom = new circomPlugin() + this.engine.register([circom]) const appUpdater = new appUpdaterPlugin() this.engine.register([appUpdater]) const remixAIDesktop = new remixAIDesktopPlugin() @@ -565,7 +567,7 @@ class AppComponent { await this.appManager.activatePlugin(['solidity-script', 'remix-templates']) if (isElectron()) { - await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat']) // 'remixAID' + await this.appManager.activatePlugin(['isogit', 'electronconfig', 'electronTemplates', 'xterm', 'ripgrep', 'appUpdater', 'slither', 'foundry', 'hardhat', 'circom']) // 'remixAID' } this.appManager.on( diff --git a/apps/remix-ide/src/app/plugins/electron/circomElectronPlugin.ts b/apps/remix-ide/src/app/plugins/electron/circomElectronPlugin.ts new file mode 100644 index 00000000000..ef45b393437 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/electron/circomElectronPlugin.ts @@ -0,0 +1,12 @@ +import { ElectronPlugin } from '@remixproject/engine-electron' + +export class circomPlugin extends ElectronPlugin { + constructor() { + super({ + displayName: 'circom', + name: 'circom', + description: 'circom language compiler', + }) + this.methods = [] + } +} diff --git a/apps/remix-ide/src/app/providers/injected-provider.tsx b/apps/remix-ide/src/app/providers/injected-provider.tsx index a57d4e0cfa3..70634f9594c 100644 --- a/apps/remix-ide/src/app/providers/injected-provider.tsx +++ b/apps/remix-ide/src/app/providers/injected-provider.tsx @@ -42,10 +42,14 @@ export abstract class InjectedProvider extends Plugin implements IProvider { } } - askPermission(throwIfNoInjectedProvider) { + async askPermission(throwIfNoInjectedProvider) { const web3Provider = this.getInjectedProvider() if (typeof web3Provider !== 'undefined' && typeof web3Provider.request === 'function') { - web3Provider.request({ method: 'eth_requestAccounts' }) + try { + await web3Provider.request({ method: 'eth_requestAccounts' }) + } catch (error) { + throw new Error(this.notFound()) + } } else if (throwIfNoInjectedProvider) { throw new Error(this.notFound()) } @@ -61,7 +65,12 @@ export abstract class InjectedProvider extends Plugin implements IProvider { this.call('notification', 'toast', this.notFound()) throw new Error(this.notFound()) } else { - this.askPermission(true) + try { + await this.askPermission(true) + } catch (error) { + this.call('notification', 'toast', 'Please make sure your Injected Provider is connected to Remix.') + throw new Error(this.notFound()) + } } return {} } diff --git a/apps/remix-ide/src/app/tabs/locale-module.js b/apps/remix-ide/src/app/tabs/locale-module.js index 413fe9df354..f4b3252df18 100644 --- a/apps/remix-ide/src/app/tabs/locale-module.js +++ b/apps/remix-ide/src/app/tabs/locale-module.js @@ -38,7 +38,7 @@ export class LocaleModule extends Plugin { config: Registry.getInstance().get('config') && Registry.getInstance().get('config').api } this.locales = {} - locales.map((locale) => { + locales.forEach((locale) => { this.locales[locale.code.toLocaleLowerCase()] = locale }) this._paq = _paq diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index a8fc8c4de1a..c514350909d 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -175,7 +175,7 @@ export class RemixAppManager extends PluginManager { this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json' this.pluginLoader = new PluginLoader() if (Registry.getInstance().get('platform').api.isDesktop()) { - requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID'] + requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep', 'slither', 'remixAID', 'circom'] } } diff --git a/apps/remix-ide/src/remixEngine.js b/apps/remix-ide/src/remixEngine.js index f81bcb5037f..b883655e55e 100644 --- a/apps/remix-ide/src/remixEngine.js +++ b/apps/remix-ide/src/remixEngine.js @@ -30,6 +30,7 @@ export class RemixEngine extends Engine { if (name === 'remixAI') return { queueTimeout: 60000 * 20 } if (name === 'cookbookdev') return { queueTimeout: 60000 * 3 } if (name === 'contentImport') return { queueTimeout: 60000 * 3 } + if (name === 'circom') return { queueTimeout: 60000 * 4 } return { queueTimeout: 10000 } } diff --git a/apps/remix-ide/team-best-practices.md b/apps/remix-ide/team-best-practices.md index 573b003dd9c..7517e3483c4 100644 --- a/apps/remix-ide/team-best-practices.md +++ b/apps/remix-ide/team-best-practices.md @@ -58,7 +58,7 @@ Related links: # Prerequisites: -Before starting coding, we should ensure all devs / contributors are aware of: +Before starting to coding, we should ensure all devs / contributors are aware of: - Where the codebase is. - How to setup and get started (always up to date). - How to run tests. @@ -87,7 +87,7 @@ Before starting coding, we should ensure all devs / contributors are aware of: - Mark unfinished pull requests with the `Work in Progress` label - Large pull requests (above 200-400 lines of code changed) cannot be effectively reviewed and should be split into smaller pieces. - Code should comply to the `JavaScript standard style` - https://www.npmjs.com/package/standard - - You should not expect complete review on a pull request which is not passing CI. + - You should not expect a complete review on a pull request which is not passing CI. - You can obviously ask for feedback on your approach. - You should assign a reviewer(s). - Pull requests should be used as a reference to update coding best practices whenever it is needed. @@ -109,7 +109,7 @@ Before starting coding, we should ensure all devs / contributors are aware of: - You should take the responsibility of the PR you are reviewing. - You should make sure the app is viable after the PR is being merged. - You should make sure the PR is correctly tested (e2e tests, unit tests) - - Ideally You should have enough knowledge to be able to fix related bugs. + - Ideally you should have enough knowledge to be able to fix related bugs. ### 3) Merge: @@ -143,7 +143,7 @@ Before starting coding, we should ensure all devs / contributors are aware of: - We release an `m.x.0` whenever there is a new feature. - We release an `x.0.0` after each milestone we consider being an important progress. - We release an `x.0.0` if there's an API breaking change in one of our libraries. - - After a new release we should stay in alert for possible regression and better not release Friday at 5pm :) + - After a new release we should stay in alert for possible regression and better not release on Friday at 5pm :) ### 2) Community: - Before the official release, we should select a group of power users and invite them to test and give feedbacks. @@ -197,7 +197,7 @@ Before starting coding, we should ensure all devs / contributors are aware of: - We release an `x.0.0` if there's a fundamental change in our UX design, which means users will need to readapt the way they use the app - after a week finishes, we publish/release a new version as **remix-beta.ethereum.org** and inform users so early adopters can test. after another week, when the next finished work is released as **remix-beta.ethereum.org**, the previous one becomes **remix.ethereum.org** and all users can start using it - a bot to automatically notify users about upcoming features on all channels whenever **remix-beta.ethereum.org** is updated - - in case it's a major version increase - this announcement should be specially marked so ppl can check early instead of being confronted with drastic changes when **remix.ethereum.org** updates + - in case it's a major version increase - this announcement should be specially marked so people can check early instead of being confronted with drastic changes when **remix.ethereum.org** updates ### maintenance: - Setting up a "bug" time where we each take a bug for which: - We feel comfortable to deal with diff --git a/apps/remixdesktop/src/engine.ts b/apps/remixdesktop/src/engine.ts index 7fc425033eb..7c573a752fb 100644 --- a/apps/remixdesktop/src/engine.ts +++ b/apps/remixdesktop/src/engine.ts @@ -14,6 +14,7 @@ import { AppUpdaterPlugin } from './plugins/appUpdater'; import { RemixAIDesktopPlugin } from './plugins/remixAIDektop'; import { FoundryPlugin } from './plugins/foundryPlugin'; import { HardhatPlugin } from './plugins/hardhatPlugin'; +import { CircomElectronPlugin } from './plugins/circomElectronBasePlugin'; import { isE2E } from './main'; const engine = new Engine() @@ -30,6 +31,7 @@ const appUpdaterPlugin = new AppUpdaterPlugin() const foundryPlugin = new FoundryPlugin() const hardhatPlugin = new HardhatPlugin() const remixAIDesktopPlugin = new RemixAIDesktopPlugin() +const circomPlugin = new CircomElectronPlugin() engine.register(appManager) engine.register(fsPlugin) @@ -44,6 +46,7 @@ engine.register(foundryPlugin) engine.register(appUpdaterPlugin) engine.register(hardhatPlugin) engine.register(remixAIDesktopPlugin) +engine.register(circomPlugin) appManager.activatePlugin('electronconfig') appManager.activatePlugin('fs') diff --git a/apps/remixdesktop/src/plugins/circomElectronBasePlugin.ts b/apps/remixdesktop/src/plugins/circomElectronBasePlugin.ts new file mode 100644 index 00000000000..f8b73c43a24 --- /dev/null +++ b/apps/remixdesktop/src/plugins/circomElectronBasePlugin.ts @@ -0,0 +1,96 @@ +import { ElectronBasePlugin, ElectronBasePluginClient } from "@remixproject/plugin-electron" +import { Profile } from "@remixproject/plugin-utils" +import { getInstallationPath, circomCli, extractParentFromKey, getInstallationUrl, getLogInputSignalsPath, extractNameFromKey } from "../tools/circom" +import path from "path" +import { existsSync, readFileSync } from "fs" + +const profile: Profile = { + displayName: 'circom', + name: 'circom', + description: 'Circom language compiler' +} + +export class CircomElectronPlugin extends ElectronBasePlugin { + clients: CircomElectronPluginClient[] = [] + + constructor() { + super(profile, clientProfile, CircomElectronPluginClient) + this.methods = [...super.methods] + } +} + +const clientProfile: Profile = { + name: 'circom', + displayName: 'circom', + description: 'Circom Language Compiler', + methods: ['install', 'run', 'getInputs', 'isVersionInstalled'] +} + +class CircomElectronPluginClient extends ElectronBasePluginClient { + isCircomInstalled: boolean = false + + constructor(webContentsId: number, profile: Profile) { + super(webContentsId, profile) + this.onload() + } + + async install(version = 'latest') { + this.call('terminal' as any, 'logHtml', `Checking if circom compiler (${version}) is installed in ${getInstallationPath(version)}`) + + this.isCircomInstalled = await circomCli.isCircomInstalled(version) + if (!this.isCircomInstalled) { + this.call('terminal' as any, 'logHtml', 'Downloading circom compiler from ' + getInstallationUrl(version)) + await circomCli.installCircom(version) + this.isCircomInstalled = true + this.call('terminal' as any, 'logHtml', `Circom compiler (${version}) downloaded from ${getInstallationUrl(version)} to ${getInstallationPath(version)}`) + } + } + + async run(filePath: string, version = 'latest', options: Record) { + if (!this.isCircomInstalled) await this.install(version) + // @ts-ignore + const wd = await this.call('fs', 'getWorkingDir') + const binDir = path.join(wd, path.join(extractParentFromKey(filePath), '.bin')) + // @ts-ignore + const outputDirExists = await this.call('fs', 'exists', path.join(extractParentFromKey(filePath), '.bin')) + // @ts-ignore + if (!outputDirExists) await this.call('fs', 'mkdir', path.join(extractParentFromKey(filePath), '.bin')) + else { + // @ts-ignore + if (process.platform === 'win32' && 'wasm' in options) { + try{ + // @ts-ignore + await this.call('fs', 'rmdir', path.join(extractParentFromKey(filePath), '.bin', extractNameFromKey(filePath).replace('.circom', '_js'))) + } catch (e) { + } + } + } + filePath = path.join(wd, filePath) + const depPath = path.join(wd, '.deps/https/raw.githubusercontent.com/iden3/') + const outputDir = process.platform !== 'win32' ? path.normalize(extractParentFromKey(filePath) + '/' + '.bin') : binDir + + this.call('terminal' as any, 'logHtml', `Compiling ${filePath} with circom compiler (${version})`) + return await circomCli.run(`${filePath} -l ${depPath} -o ${outputDir}`, version, options) + } + + getInputs() { + const inputsFile = getLogInputSignalsPath() + const inputsFileExists = existsSync(inputsFile) + const signals: string[] = [] + + if (inputsFileExists) { + const inputsContent = readFileSync(inputsFile, 'utf-8') + const regexPattern = /main\.(\w+)/g + + let match + while ((match = regexPattern.exec(inputsContent)) !== null) { + signals.push(match[1]) + } + return signals + } + } + + async isVersionInstalled(version: string) { + return await circomCli.isCircomInstalled(version) + } +} \ No newline at end of file diff --git a/apps/remixdesktop/src/preload.ts b/apps/remixdesktop/src/preload.ts index f82de04f79f..6eb9d717441 100644 --- a/apps/remixdesktop/src/preload.ts +++ b/apps/remixdesktop/src/preload.ts @@ -6,7 +6,7 @@ console.log('preload.ts', new Date().toLocaleTimeString()) /* preload script needs statically defined API for each plugin */ -const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID'] +const exposedPLugins = ['fs', 'git', 'xterm', 'isogit', 'electronconfig', 'electronTemplates', 'ripgrep', 'compilerloader', 'appUpdater', 'slither', 'foundry', 'hardhat', 'remixAID', 'circom'] let webContentsId: number | undefined @@ -18,7 +18,7 @@ contextBridge.exposeInMainWorld('electronAPI', { isPackaged: () => ipcRenderer.invoke('config:isPackaged'), isE2E: () => ipcRenderer.invoke('config:isE2E'), canTrackMatomo: () => ipcRenderer.invoke('config:canTrackMatomo'), - trackEvent: (args: any[]) => ipcRenderer.invoke('matomo:trackEvent', args), + trackEvent: (args: any[]) => ipcRenderer.invoke('matomo:trackEvent', args), openFolder: (path: string) => ipcRenderer.invoke('fs:openFolder', webContentsId, path), openFolderInSameWindow: (path: string) => ipcRenderer.invoke('fs:openFolderInSameWindow', webContentsId, path), activatePlugin: (name: string) => { diff --git a/apps/remixdesktop/src/tools/circom.ts b/apps/remixdesktop/src/tools/circom.ts new file mode 100644 index 00000000000..1d3c68e099f --- /dev/null +++ b/apps/remixdesktop/src/tools/circom.ts @@ -0,0 +1,136 @@ +import { app } from 'electron' +import os from 'os' +import { exec } from 'child_process' +import path from 'path' +import fs, { existsSync } from 'fs' +import axios from 'axios' + +async function downloadFile(url: string, dest: string) { + const writer = fs.createWriteStream(dest) + const response = await axios({ + url, + method: 'GET', + responseType: 'stream' + }) + + response.data.pipe(writer) + + return new Promise((resolve, reject) => { + writer.on('finish', () => { + if (process.platform !== 'win32') { + // Sets permission to make the file executable + fs.chmod(dest, 0o775, (err) => { + if (err) { + reject(`Error making file executable: ${err}`) + } else { + resolve(dest) + } + }) + } + resolve(true) + }) + writer.on('error', reject) + }) +} + +export function getInstallationPath(version) { + const fileNames = { + win32: 'circom-windows-amd64.exe', + darwin: 'circom-macos-amd64', + linux: 'circom-linux-amd64' + }; + + return path.join(os.tmpdir(), 'circom-download', version, fileNames[process.platform]); +} + +export function getInstallationUrl(version) { + switch (process.platform) { + case 'win32': + return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-windows-amd64.exe' : `https://github.com/iden3/circom/releases/download/${version}/circom-windows-amd64.exe` + + case 'darwin': + return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-macos-amd64' : `https://github.com/iden3/circom/releases/download/${version}/circom-macos-amd64` + + case 'linux': + return version === 'latest' ? 'https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64' : `https://github.com/iden3/circom/releases/download/${version}/circom-linux-amd64` + } +} + +export function getLogInputSignalsPath() { + + const tempFilePath = path.join(os.tmpdir(), 'log_input_signals.txt'); + return tempFilePath; +} + +export const circomCli = { + async installCircom (version) { + const installationPath = getInstallationPath(version) + const installationUrl = getInstallationUrl(version) + + if (!existsSync(path.dirname(installationPath))) fs.mkdirSync(path.dirname(installationPath), { recursive: true }) + try { + await downloadFile(installationUrl, installationPath) + } catch (e) { + fs.rmSync(installationPath) + throw new Error(e.message) + } + }, + + async isCircomInstalled (version) { + try { + const installationPath = getInstallationPath(version) + return existsSync(installationPath) + } catch (e) { + return false + } + }, + + async run (filePath: string, version: string, options?: Record) { + const installationPath = getInstallationPath(version) + const cmd = `${installationPath} ${filePath} ${Object.keys(options || {}).map((key) => options[key] ? `--${key} ${options[key]}` : `--${key}`).join(' ')}` + console.log(cmd) + if(process.platform === 'darwin') { + const rosettaInstalled = await checkRosettaInstalled(); + if(rosettaInstalled === 'Rosetta is not installed') { + throw new Error('Rosetta is not installed. Please install Rosetta to run this command.'); + } + } + return new Promise((resolve, reject) => { + exec(cmd, { cwd: os.tmpdir() }, (error, stdout, stderr) => { + if (error) { + reject(`${error.message} with error code ${error.code}`) + } else { + resolve({ stdout, stderr }) + } + }) + }) + } +} + +export const extractParentFromKey = (key: string):string => { + if (!key) return + const keyPath = key.split('/') + + keyPath.pop() + + return keyPath.join('/') +} + +export const extractNameFromKey = (key: string): string => { + if (!key) return + const keyPath = key.split('/') + + return keyPath[keyPath.length - 1] +} + +async function checkRosettaInstalled(): Promise { + return new Promise((resolve, reject) => { + exec("/usr/bin/pgrep oahd > /dev/null && echo 'Rosetta is installed' || echo 'Rosetta is not installed'", (error, stdout, stderr) => { + if (error) { + reject(`Error: ${stderr}`); + } else { + resolve(stdout.trim()); + } + }); + }); +} diff --git a/apps/remixdesktop/test/tests/app/circom-compiler.test.ts b/apps/remixdesktop/test/tests/app/circom-compiler.test.ts new file mode 100644 index 00000000000..eec7885f5f7 --- /dev/null +++ b/apps/remixdesktop/test/tests/app/circom-compiler.test.ts @@ -0,0 +1,86 @@ +import { NightwatchBrowser } from "nightwatch" + + +const tests = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + browser.hideToolTips() + done() + }, + 'Should create semaphore workspace': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="homeTabGetStartedsemaphore"]', 20000) + .click('*[data-id="homeTabGetStartedsemaphore"]') + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) + browser.switchWindow(result.value[1]) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]') + .click('*[data-id="treeViewLitreeViewItemcircuits"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .click('*[data-id="treeViewLitreeViewItemscripts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16"]') + .click('*[data-id="treeViewLitreeViewItemscripts/groth16"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_trusted_setup.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_zkproof.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk"]') + .click('*[data-id="treeViewLitreeViewItemscripts/plonk"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates"]') + .click('*[data-id="treeViewLitreeViewItemtemplates"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/plonk_verifier.sol.ejs"]') + }) + }, + 'Should compile a simple circuit using editor play button': function (browser: NightwatchBrowser) { + browser + .click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]') + .waitForElementVisible('[data-id="play-editor"]') + .click('[data-id="play-editor"]') + .pause(3000) + .click('[data-id="play-editor"]') + .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin"]') + .click('[data-id="treeViewLitreeViewItemcircuits/.bin"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js"]') + .click('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wasm"]') + }, + 'Should run setup script for simple circuit': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('circuit-compiler') + .frame(0) + .waitForElementVisible('[data-id="runSetupBtn"]') + .click('[data-id="runSetupBtn"]') + }, + 'Should compute a witness for a simple circuit': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('[data-id="compute_witness_btn"]', 60000) + .waitForElementVisible('[data-id="circuit_input_a"]') + .waitForElementVisible('[data-id="circuit_input_b"]') + .setValue('[data-id="circuit_input_a"]', '1') + .setValue('[data-id="circuit_input_b"]', '2') + .click('[data-id="compute_witness_btn"]') + .frameParent() + .clickLaunchIcon('filePanel') + .waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple_js/simple.wtn"]') + }, + 'Should generate proof for a simple circuit': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('circuit-compiler') + .frame(0) + .waitForElementVisible('[data-id="generateProofBtn"]') + .click('[data-id="generateProofBtn"]') + .frameParent() + .waitForElementVisible({ + locateStrategy: 'xpath', + selector: "//span[@class='text-log' and contains(., 'zk proof validity true')]", + timeout: 60000 + }) + + } +} + +module.exports = tests diff --git a/apps/remixdesktop/test/tests/app/circom-script.test.ts b/apps/remixdesktop/test/tests/app/circom-script.test.ts new file mode 100644 index 00000000000..4c675aaf117 --- /dev/null +++ b/apps/remixdesktop/test/tests/app/circom-script.test.ts @@ -0,0 +1,67 @@ +import { NightwatchBrowser } from "nightwatch" + + +const tests = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + browser.hideToolTips() + done() + }, + 'Should create semaphore workspace': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="homeTabGetStartedsemaphore"]', 20000) + .click('*[data-id="homeTabGetStartedsemaphore"]') + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) + browser.switchWindow(result.value[1]) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]') + .click('*[data-id="treeViewLitreeViewItemcircuits"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .click('*[data-id="treeViewLitreeViewItemscripts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16"]') + .click('*[data-id="treeViewLitreeViewItemscripts/groth16"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_trusted_setup.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/groth16/groth16_zkproof.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk"]') + .click('*[data-id="treeViewLitreeViewItemscripts/plonk"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates"]') + .click('*[data-id="treeViewLitreeViewItemtemplates"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/plonk_verifier.sol.ejs"]') + }) + }, + 'Should run plonk trusted setup script for hash checker #group6': function (browser: NightwatchBrowser) { + browser + .click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]') + .pause(2000) + .click('[data-id="play-editor"]') + .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') + .waitForElementVisible({ + locateStrategy: 'xpath', + selector: "//span[@class='text-log' and contains(., 'setup done.')]", + timeout: 60000 + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk"]') + .click('*[data-id="treeViewLitreeViewItemscripts/plonk/zk"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys"]') + .click('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/plonk/zk/keys/verification_key.json"]') + }, + 'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) { + browser + .click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]') + .pause(2000) + .click('[data-id="play-editor"]') + .waitForElementVisible({ + locateStrategy: 'xpath', + selector: "//span[@class='text-log' and contains(., 'proof done')]", + timeout: 60000 + }) + } + +} + +module.exports = tests diff --git a/libs/remix-api/src/lib/plugins/remixAIDesktop-api.ts b/libs/remix-api/src/lib/plugins/remixAIDesktop-api.ts index 38127699ce1..9e4cdd47162 100644 --- a/libs/remix-api/src/lib/plugins/remixAIDesktop-api.ts +++ b/libs/remix-api/src/lib/plugins/remixAIDesktop-api.ts @@ -20,4 +20,4 @@ export interface IRemixAID { chatPipe(pipeMessage: string): Promise, ProcessChatRequestBuffer(params:IParams): Promise, } -} \ No newline at end of file +} diff --git a/libs/remix-ws-templates/src/templates/hashchecker/scripts/groth16/groth16_zkproof.ts b/libs/remix-ws-templates/src/templates/hashchecker/scripts/groth16/groth16_zkproof.ts index 1072ea7873c..1f7299f6248 100644 --- a/libs/remix-ws-templates/src/templates/hashchecker/scripts/groth16/groth16_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/hashchecker/scripts/groth16/groth16_zkproof.ts @@ -18,7 +18,7 @@ const logger = { // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/calculate_hash.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', { encoding: null }); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash_js/calculate_hash.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer); diff --git a/libs/remix-ws-templates/src/templates/hashchecker/scripts/plonk/plonk_zkproof.ts b/libs/remix-ws-templates/src/templates/hashchecker/scripts/plonk/plonk_zkproof.ts index 3d6d7feadc1..22952be6452 100644 --- a/libs/remix-ws-templates/src/templates/hashchecker/scripts/plonk/plonk_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/hashchecker/scripts/plonk/plonk_zkproof.ts @@ -15,7 +15,7 @@ const logger = { // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/calculate_hash.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash.wasm', { encoding: null }); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/calculate_hash_js/calculate_hash.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer); const zkey_final = { diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts index 860c9dca61f..ce02aba364a 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/groth16/groth16_zkproof.ts @@ -79,7 +79,7 @@ async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) { // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/rln.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.wasm', { encoding: null }); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln_js/rln.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer); diff --git a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts index 2f95cb5b828..1ca7d611165 100644 --- a/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/rln/scripts/plonk/plonk_zkproof.ts @@ -102,7 +102,7 @@ async function prove (signals, wasm, wtns, r1cs, zkey_final, vKey) { // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/rln.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln.wasm', { encoding: null }); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/rln_js/rln.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer); diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts index f395efe5f94..79e9679bb7b 100644 --- a/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/groth16/groth16_zkproof.ts @@ -25,13 +25,13 @@ function hash(message: any): bigint { (async () => { try { // @ts-ignore - const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null }); // @ts-ignore const r1cs = new Uint8Array(r1csBuffer); // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore_js/semaphore.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer); diff --git a/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts index 77b996a4026..8205b842d10 100644 --- a/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts +++ b/libs/remix-ws-templates/src/templates/semaphore/scripts/plonk/plonk_zkproof.ts @@ -25,13 +25,13 @@ function hash(message: any): bigint { (async () => { try { // @ts-ignore - const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', true); + const r1csBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.r1cs', { encoding: null }); // @ts-ignore const r1cs = new Uint8Array(r1csBuffer); // @ts-ignore await remix.call('circuit-compiler', 'compile', 'circuits/semaphore.circom'); // @ts-ignore - const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore.wasm', true); + const wasmBuffer = await remix.call('fileManager', 'readFile', 'circuits/.bin/semaphore_js/semaphore.wasm', { encoding: null }); // @ts-ignore const wasm = new Uint8Array(wasmBuffer);