Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2E: Circom Plugin #4184

Merged
merged 8 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions apps/circuit-compiler/src/app/components/compileBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'}>
Expand Down
2 changes: 1 addition & 1 deletion apps/circuit-compiler/src/app/components/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
6 changes: 3 additions & 3 deletions apps/circuit-compiler/src/app/components/feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions apps/circuit-compiler/src/app/components/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>
Expand Down
1 change: 1 addition & 0 deletions apps/circuit-compiler/src/app/components/r1csBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions apps/circuit-compiler/src/app/components/witness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
18 changes: 10 additions & 8 deletions apps/circuit-compiler/src/app/services/circomPluginClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -101,18 +101,16 @@ 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)
}
}

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') {
Expand All @@ -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
Expand Down Expand Up @@ -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') {
Expand All @@ -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
Expand Down
178 changes: 178 additions & 0 deletions apps/remix-ide-e2e/src/tests/circom.test.ts
Original file line number Diff line number Diff line change
@@ -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();
`
2 changes: 2 additions & 0 deletions libs/remix-ui/solidity-compiler/src/lib/api/compiler-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
Loading