Merge pull request #4184 from ethereum/circuit-compiler-e2e

E2E: Circom Plugin
pull/4219/head
David Disu 1 year ago committed by GitHub
commit 655ae40eac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      apps/circuit-compiler/src/app/components/compileBtn.tsx
  2. 2
      apps/circuit-compiler/src/app/components/container.tsx
  3. 6
      apps/circuit-compiler/src/app/components/feedback.tsx
  4. 4
      apps/circuit-compiler/src/app/components/options.tsx
  5. 1
      apps/circuit-compiler/src/app/components/r1csBtn.tsx
  6. 6
      apps/circuit-compiler/src/app/components/witness.tsx
  7. 2
      apps/circuit-compiler/src/app/components/witnessToggler.tsx
  8. 18
      apps/circuit-compiler/src/app/services/circomPluginClient.ts
  9. 178
      apps/remix-ide-e2e/src/tests/circom.test.ts
  10. 2
      libs/remix-ui/solidity-compiler/src/lib/api/compiler-api.ts

@ -9,20 +9,21 @@ export function CompileBtn () {
return (
<CustomTooltip
placement="auto"
tooltipId="overlay-tooltip-compile"
tooltipText={
<div className="text-left">
<div>
<b>Ctrl+S</b> to compile {appState.filePath}
</div>
placement="auto"
tooltipId="overlay-tooltip-compile"
tooltipText={
<div className="text-left">
<div>
<b>Ctrl+S</b> to compile {appState.filePath}
</div>
}
>
</div>
}
>
<button
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-3"
onClick={() => { compileCircuit(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating")}
data-id="compile_circuit_btn"
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={appState.status === 'compiling'}>

@ -70,7 +70,7 @@ export function Container () {
<WitnessSection plugin={circuitApp.plugin} signalInputs={circuitApp.appState.signalInputs} status={circuitApp.appState.status} />
</WitnessToggler>
</RenderIf>
<RenderIf condition={circuitApp.appState.status !== 'compiling' && circuitApp.appState.status !== 'computing' && circuitApp.appState.status !== 'generating'}>
<RenderIf condition={(circuitApp.appState.status !== 'compiling') && (circuitApp.appState.status !== 'computing') && (circuitApp.appState.status !== 'generating')}>
<CompilerFeedback feedback={circuitApp.appState.feedback} filePathToId={circuitApp.appState.filePathToId} openErrorLocation={handleOpenErrorLocation} hideWarnings={circuitApp.appState.hideWarnings} />
</RenderIf>
</div>

@ -21,7 +21,7 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
<div>
<div className="circuit_errors_box py-4">
<RenderIf condition={ (typeof feedback === "string") && showException }>
<div className="circuit_feedback error alert alert-danger">
<div className="circuit_feedback error alert alert-danger" data-id="circuit_feedback">
<span> { feedback } </span>
<div className="close" data-id="renderer" onClick={handleCloseException}>
<i className="fas fa-times"></i>
@ -39,12 +39,12 @@ export function CompilerFeedback ({ feedback, filePathToId, hideWarnings, openEr
Array.isArray(feedback) && feedback.map((response, index) => (
<div key={index} onClick={() => handleOpenError(response)}>
<RenderIf condition={response.type === 'Error'}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-danger`} data-id="circuit_feedback">
<FeedbackAlert message={response.message} location={ response.labels[0] ? response.labels[0].message + ` ${filePathToId[response.labels[0].file_id]}:${response.labels[0].range.start}:${response.labels[0].range.end}` : null} />
</div>
</RenderIf>
<RenderIf condition={(response.type === 'Warning') && !hideWarnings}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`}>
<div className={`circuit_feedback ${response.type.toLowerCase()} alert alert-warning`} data-id="circuit_feedback">
<FeedbackAlert message={response.message} location={null} />
</div>
</RenderIf>

@ -14,7 +14,7 @@ export function CompileOptions ({autoCompile, hideWarnings, setCircuitAutoCompil
checked={autoCompile}
id="autoCompileCircuit"
/>
<label className="form-check-label custom-control-label" htmlFor="autoCompileCircuit">
<label className="form-check-label custom-control-label" htmlFor="autoCompileCircuit" data-id="auto_compile_circuit_checkbox_input">
<FormattedMessage id="circuit.autoCompile" />
</label>
</div>
@ -27,7 +27,7 @@ export function CompileOptions ({autoCompile, hideWarnings, setCircuitAutoCompil
title="Hide warnings"
checked={hideWarnings}
/>
<label className="form-check-label custom-control-label" htmlFor="hideCircuitWarnings">
<label className="form-check-label custom-control-label" htmlFor="hideCircuitWarnings" data-id="hide_circuit_warnings_checkbox_input">
<FormattedMessage id="solidity.hideWarnings" />
</label>
</div>

@ -12,6 +12,7 @@ export function R1CSBtn () {
className="btn btn-secondary btn-block d-block w-100 text-break mb-1 mt-2"
onClick={() => { generateR1cs(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating") || (appState.status === "computing")}
data-id="generate_r1cs_btn"
>
<CustomTooltip
placement="auto"

@ -26,14 +26,16 @@ export function WitnessSection ({ plugin, signalInputs, status }: {plugin: Circo
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.signalInput" /> { input }
</label>
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} />
<input className="form-control m-0 txinput" placeholder={input} name={input} onChange={handleSignalInput} data-id={`circuit_input_${input}`} />
</div>
))
}
<button
className="btn btn-sm btn-secondary"
onClick={() => { computeWitness(plugin, status, witnessValues) }}
disabled={(status === "compiling") || (status === "generating") || (status === "computing")}>
disabled={(status === "compiling") || (status === "generating") || (status === "computing")}
data-id="compute_witness_btn"
>
<RenderIf condition={status === 'computing'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>

@ -11,7 +11,7 @@ export function WitnessToggler ({ children }: { children: JSX.Element }) {
return (
<div>
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations} data-id="witness_toggler">
<div className="d-flex">
<label className="mt-1 circuit_config_section">
<FormattedMessage id="circuit.computeWitness" />

@ -32,7 +32,7 @@ export class CircomPluginClient extends PluginClient {
this.internalEvents.emit('circom_activated')
}
async parse(path: string, fileContent?: string): Promise<CompilerReport[]> {
async parse(path: string, fileContent?: string): Promise<[CompilerReport[], Record<string, string>]> {
if (!fileContent) {
// @ts-ignore
fileContent = await this.call('fileManager', 'readFile', path)
@ -45,7 +45,7 @@ export class CircomPluginClient extends PluginClient {
try {
const result: CompilerReport[] = JSON.parse(parsedOutput.report())
const mapReportFilePathToId = {}
const mapReportFilePathToId: Record<string, string> = {}
if (result.length === 0) {
// @ts-ignore
@ -101,10 +101,8 @@ export class CircomPluginClient extends PluginClient {
await this.call('editor', 'clearErrorMarkers', [path])
}
}
this.internalEvents.emit('circuit_parsing_done', result, mapReportFilePathToId)
return result
return [result, mapReportFilePathToId]
} catch (e) {
throw new Error(e)
}
@ -112,7 +110,7 @@ export class CircomPluginClient extends PluginClient {
async compile(path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_compiling_start')
const parseErrors = await this.parse(path)
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
@ -121,6 +119,8 @@ export class CircomPluginClient extends PluginClient {
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
}
if (compilationConfig) {
const { prime, version } = compilationConfig
@ -160,7 +160,7 @@ export class CircomPluginClient extends PluginClient {
async generateR1cs (path: string, compilationConfig?: CompilationConfig): Promise<void> {
this.internalEvents.emit('circuit_generating_r1cs_start')
const parseErrors = await this.parse(path)
const [parseErrors, filePathToId] = await this.parse(path)
if (parseErrors && (parseErrors.length > 0)) {
if (parseErrors[0].type === 'Error') {
@ -169,6 +169,8 @@ export class CircomPluginClient extends PluginClient {
} else if (parseErrors[0].type === 'Warning') {
this.internalEvents.emit('circuit_parsing_warning', parseErrors)
}
} else {
this.internalEvents.emit('circuit_parsing_done', parseErrors, filePathToId)
}
if (compilationConfig) {
const { prime, version } = compilationConfig

@ -0,0 +1,178 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Should create semaphore workspace template #group1 #group2 #group3 #group4': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('*[data-id="workspacesMenuDropdown"]')
.click('*[data-id="workspacecreate"]')
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=semaphore]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/run_setup.ts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtemplates/groth16_verifier.sol.ejs"]')
},
'Should compile a simple circuit using editor play button #group1': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.click('[data-id="play-editor"]')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
},
'Should compute a witness for a simple circuit #group1': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementVisible('[data-id="witness_toggler"]')
.click('[data-id="witness_toggler"]')
.waitForElementVisible('[data-id="compute_witness_btn"]')
.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.wtn"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wtn"]')
},
'Should compile a simple circuit using compile button in circom plugin #group2': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementPresent('button[data-id="compile_circuit_btn"]')
.waitForElementVisible('button[data-id="compile_circuit_btn"]')
.click('button[data-id="compile_circuit_btn"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
},
'Should generate R1CS for a simple circuit #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementPresent('button[data-id="generate_r1cs_btn"]')
.waitForElementVisible('button[data-id="generate_r1cs_btn"]')
.click('button[data-id="generate_r1cs_btn"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.r1cs"]')
},
'Should compile a simple circuit using CTRL + S from the editor #group3': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.perform(function () {
const actions = this.actions({async: true})
return actions.keyDown(this.Keys.CONTROL).sendKeys('s')
})
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
},
'Should display warnings for compiled circuit without pragma version #group4': function (browser: NightwatchBrowser) {
browser
.click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]')
.waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]')
.waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]')
.setEditorValue(warningCircuit)
.clickLaunchIcon('circuit-compiler')
.frame(0)
.waitForElementPresent('button[data-id="compile_circuit_btn"]')
.waitForElementVisible('button[data-id="compile_circuit_btn"]')
.click('button[data-id="compile_circuit_btn"]')
.waitForElementPresent('[data-id="circuit_feedback"]')
.waitForElementVisible('[data-id="circuit_feedback"]')
.assert.hasClass('[data-id="circuit_feedback"]', 'alert-warning')
.waitForElementContainsText('[data-id="circuit_feedback"]', 'File circuits/simple.circom does not include pragma version. Assuming pragma version (2, 1, 5)')
},
'Should hide/show warnings for compiled circuit #group4': function (browser: NightwatchBrowser) {
browser
.click('[data-id="hide_circuit_warnings_checkbox_input"]')
.waitForElementNotPresent('[data-id="circuit_feedback"]')
.click('[data-id="hide_circuit_warnings_checkbox_input"]')
.waitForElementVisible('[data-id="circuit_feedback"]')
.waitForElementContainsText('[data-id="circuit_feedback"]', 'File circuits/simple.circom does not include pragma version. Assuming pragma version (2, 1, 5)')
},
'Should display error for invalid circuit #group4': function (browser: NightwatchBrowser) {
browser
.frameParent()
.setEditorValue(errorCircuit)
.frame(0)
.waitForElementPresent('button[data-id="compile_circuit_btn"]')
.waitForElementVisible('button[data-id="compile_circuit_btn"]')
.click('button[data-id="compile_circuit_btn"]')
.waitForElementPresent('[data-id="circuit_feedback"]')
.assert.hasClass('[data-id="circuit_feedback"]', 'alert-danger')
.waitForElementContainsText('[data-id="circuit_feedback"]', 'No main specified in the project structure')
},
'Should auto compile circuit #group4': function (browser: NightwatchBrowser) {
browser
.click('[data-id="auto_compile_circuit_checkbox_input"]')
.frameParent()
.setEditorValue(validCircuit)
.frame(0)
.waitForElementNotPresent('[data-id="circuit_feedback"]')
.frameParent()
.clickLaunchIcon('filePanel')
.waitForElementPresent('[data-id="treeViewLitreeViewItemcircuits/.bin/simple.wasm"]')
}
}
const warningCircuit = `
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier2();
`
const errorCircuit = `
pragma circom 2.0.0;
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
`
const validCircuit = `
pragma circom 2.0.0;
template Multiplier2() {
signal input a;
signal input b;
signal output c;
c <== a*b;
}
component main = Multiplier2();
`

@ -345,6 +345,8 @@ export const CompilerApiMixin = (Base) => class extends Base {
if(await this.getAppParameter('hardhat-compilation')) this.compileTabLogic.runCompiler('hardhat')
else if(await this.getAppParameter('truffle-compilation')) this.compileTabLogic.runCompiler('truffle')
else this.compileTabLogic.runCompiler(undefined)
} else if (this.currentFile && this.currentFile.endsWith('.circom')) {
await this.call('circuit-compiler', 'compile', this.currentFile)
}
}
}

Loading…
Cancel
Save