Skip to content

Commit 5988c71

Browse files
authored
Merge pull request #4525 from ethereum/vypercompile-contextmenu
Vyper Contract compile FE
2 parents a309272 + ef73a56 commit 5988c71

File tree

9 files changed

+227
-119
lines changed

9 files changed

+227
-119
lines changed

apps/remix-ide-e2e/src/tests/vyper_api.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,30 @@ module.exports = {
4141
.openFile('examples/auctions/blind_auction.vy')
4242
},
4343

44+
'Context menu click to compile blind_auction should succeed #group1': function (browser: NightwatchBrowser) {
45+
browser
46+
.click('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
47+
.rightClick('*[data-id="treeViewLitreeViewItemexamples/auctions/blind_auction.vy"]')
48+
.waitForElementPresent('[data-id="contextMenuItemvyper"]')
49+
.click('[data-id="contextMenuItemvyper"]')
50+
.clickLaunchIcon('vyper')
51+
// @ts-ignore
52+
.frame(0)
53+
.waitForElementVisible({
54+
selector:'[data-id="compilation-details"]',
55+
timeout: 120000
56+
})
57+
.click('[data-id="compilation-details"]')
58+
.frameParent()
59+
.waitForElementVisible('[data-id="copy-abi"]')
60+
.waitForElementVisible({
61+
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
62+
locateStrategy: 'xpath',
63+
})
64+
},
65+
4466
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
45-
browser.clickLaunchIcon('vyper')
67+
browser
4668
// @ts-ignore
4769
.frame(0)
4870
.click('[data-id="remote-compiler"]')

apps/remix-ide/src/remixAppManager.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export class RemixAppManager extends PluginManager {
153153
if (Registry.getInstance().get('platform').api.isDesktop()) {
154154
requiredModules = [...requiredModules, 'fs', 'electronTemplates', 'isogit', 'remix-templates', 'electronconfig', 'xterm', 'compilerloader', 'ripgrep']
155155
}
156-
156+
157157
}
158158

159159
async canActivatePlugin(from, to) {
@@ -331,6 +331,17 @@ export class RemixAppManager extends PluginManager {
331331
sticky: true,
332332
group: 7
333333
})
334+
await this.call('filePanel', 'registerContextMenuItem', {
335+
id: 'vyper',
336+
name: 'vyperCompileCustomAction',
337+
label: 'Compile for Vyper',
338+
type: [],
339+
extension: ['.vy'],
340+
path: [],
341+
pattern: [],
342+
sticky: true,
343+
group: 7
344+
})
334345
if (Registry.getInstance().get('platform').api.isDesktop()) {
335346
await this.call('filePanel', 'registerContextMenuItem', {
336347
id: 'fs',

apps/vyper/src/app/app.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface OutputMap {
2828

2929
const App = () => {
3030
const [contract, setContract] = useState<string>()
31-
const [output, setOutput] = useState<any>({})
31+
const [output, setOutput] = useState<any>(remixClient.compilerOutput)
3232
const [state, setState] = useState<AppState>({
3333
status: 'idle',
3434
environment: 'remote',
@@ -53,6 +53,30 @@ const App = () => {
5353
start()
5454
}, [])
5555

56+
useEffect(() => {
57+
remixClient.eventEmitter.on('resetCompilerState', () => {
58+
resetCompilerResultState()
59+
})
60+
61+
return () => {
62+
remixClient.eventEmitter.off('resetCompilerState', () => {
63+
resetCompilerResultState()
64+
})
65+
}
66+
}, [])
67+
68+
useEffect(() => {
69+
remixClient.eventEmitter.on('setOutput', (payload) => {
70+
setOutput(payload)
71+
})
72+
73+
return () => {
74+
remixClient.eventEmitter.off('setOutput', (payload) => {
75+
setOutput(payload)
76+
})
77+
}
78+
}, [])
79+
5680
/** Update the environment state value */
5781
function setEnvironment(environment: 'local' | 'remote') {
5882
setState({...state, environment})
@@ -67,7 +91,7 @@ const App = () => {
6791
}
6892

6993
function resetCompilerResultState() {
70-
setOutput({})
94+
setOutput(remixClient.compilerOutput)
7195
}
7296

7397
return (

apps/vyper/src/app/components/CompilerButton.tsx

Lines changed: 6 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React, { Fragment, useState } from 'react'
2-
import {isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath} from '../utils'
2+
import {isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath, compileContract} from '../utils'
33
import Button from 'react-bootstrap/Button'
4-
import _ from 'lodash'
54

65
interface Props {
76
compilerUrl: string
@@ -21,112 +20,14 @@ function CompilerButton({contract, setOutput, compilerUrl, resetCompilerState}:
2120
}
2221

2322
/** Compile a Contract */
24-
async function compileContract() {
25-
resetCompilerState()
26-
setLoadingSpinnerState(true)
27-
try {
28-
// await remixClient.discardHighlight()
29-
let _contract: any
30-
try {
31-
_contract = await remixClient.getContract()
32-
} catch (e: any) {
33-
setOutput('', {status: 'failed', message: e.message})
34-
return
35-
}
36-
remixClient.changeStatus({
37-
key: 'loading',
38-
type: 'info',
39-
title: 'Compiling'
40-
})
41-
let output
42-
try {
43-
output = await compile(compilerUrl, _contract)
44-
} catch (e: any) {
45-
remixClient.changeStatus({
46-
key: 'failed',
47-
type: 'error',
48-
title: e.message
49-
})
50-
return
51-
}
52-
const compileReturnType = () => {
53-
const t: any = toStandardOutput(contract, output)
54-
const temp = _.merge(t['contracts'][contract])
55-
const normal = normalizeContractPath(contract)[2]
56-
const abi = temp[normal]['abi']
57-
const evm = _.merge(temp[normal]['evm'])
58-
const dpb = evm.deployedBytecode
59-
const runtimeBytecode = evm.bytecode
60-
const methodIdentifiers = evm.methodIdentifiers
61-
62-
const result = {
63-
contractName: normal,
64-
abi: abi,
65-
bytecode: dpb,
66-
runtimeBytecode: runtimeBytecode,
67-
ir: '',
68-
methodIdentifiers: methodIdentifiers
69-
}
70-
return result
71-
}
72-
73-
// ERROR
74-
if (isCompilationError(output)) {
75-
const line = output.line
76-
if (line) {
77-
const lineColumnPos = {
78-
start: {line: line - 1, column: 10},
79-
end: {line: line - 1, column: 10}
80-
}
81-
// remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
82-
} else {
83-
const regex = output?.message?.match(/line ((\d+):(\d+))+/g)
84-
const errors = output?.message?.split(/line ((\d+):(\d+))+/g) // extract error message
85-
if (regex) {
86-
let errorIndex = 0
87-
regex.map((errorLocation) => {
88-
const location = errorLocation?.replace('line ', '').split(':')
89-
let message = errors[errorIndex]
90-
errorIndex = errorIndex + 4
91-
if (message && message?.split('\n\n').length > 0) {
92-
try {
93-
message = message?.split('\n\n')[message.split('\n\n').length - 1]
94-
} catch (e) {}
95-
}
96-
if (location?.length > 0) {
97-
const lineColumnPos = {
98-
start: {line: parseInt(location[0]) - 1, column: 10},
99-
end: {line: parseInt(location[0]) - 1, column: 10}
100-
}
101-
// remixClient.highlight(lineColumnPos as any, _contract.name, message)
102-
}
103-
})
104-
}
105-
}
106-
throw new Error(output.message)
107-
}
108-
// SUCCESS
109-
// remixClient.discardHighlight()
110-
remixClient.changeStatus({
111-
key: 'succeed',
112-
type: 'success',
113-
title: 'success'
114-
})
115-
const data = toStandardOutput(_contract.name, output)
116-
remixClient.compilationFinish(_contract.name, _contract.content, data)
117-
setOutput(_contract.name, compileReturnType())
118-
} catch (err: any) {
119-
remixClient.changeStatus({
120-
key: 'failed',
121-
type: 'error',
122-
title: err.message
123-
})
124-
}
125-
}
12623

12724
return (
12825
<Fragment>
129-
<button data-id="compile" onClick={compileContract} title={contract} className="btn btn-primary w-100 d-block btn-block text-break remixui_disabled mb-1 mt-3">
26+
<button data-id="compile"
27+
onClick={() => compileContract(contract, compilerUrl, setOutput)}
28+
title={contract}
29+
className="btn btn-primary w-100 d-block btn-block text-break remixui_disabled mb-1 mt-3"
30+
>
13031
<div className="d-flex align-items-center justify-content-center fa-1x">
13132
<span className="fas fa-sync fa-pulse mr-1" />
13233
<div className="text-truncate overflow-hidden text-nowrap">

apps/vyper/src/app/utils/compiler.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { ABIDescription} from '@remixproject/plugin-api'
22
import axios from 'axios'
3+
import { remixClient } from './remix-client'
4+
import _ from 'lodash'
5+
36

47
export interface Contract {
58
name: string
@@ -167,6 +170,130 @@ export function toStandardOutput(fileName: string, compilationResult: any): any
167170
}
168171
}
169172
}
173+
174+
175+
export async function compileContract(contract: string, compilerUrl: string, setOutput?: any) {
176+
remixClient.eventEmitter.emit('resetCompilerState', {})
177+
178+
try {
179+
// await remixClient.discardHighlight()
180+
let _contract: any
181+
try {
182+
_contract = await remixClient.getContract()
183+
} catch (e: any) {
184+
if (setOutput === null || setOutput === undefined) {
185+
const compileResult = {
186+
status: 'failed',
187+
message: e.message
188+
}
189+
const compileResultKey = ''
190+
remixClient.eventEmitter.emit('setOutput', { compileResultKey, compileResult })
191+
} else {
192+
setOutput('', {status: 'failed', message: e.message})
193+
}
194+
return
195+
}
196+
remixClient.changeStatus({
197+
key: 'loading',
198+
type: 'info',
199+
title: 'Compiling'
200+
})
201+
let output
202+
try {
203+
output = await compile(compilerUrl, _contract)
204+
} catch (e: any) {
205+
remixClient.changeStatus({
206+
key: 'failed',
207+
type: 'error',
208+
title: e.message
209+
})
210+
return
211+
}
212+
const compileReturnType = () => {
213+
const t: any = toStandardOutput(contract, output)
214+
const temp = _.merge(t['contracts'][contract])
215+
const normal = normalizeContractPath(contract)[2]
216+
const abi = temp[normal]['abi']
217+
const evm = _.merge(temp[normal]['evm'])
218+
const dpb = evm.deployedBytecode
219+
const runtimeBytecode = evm.bytecode
220+
const methodIdentifiers = evm.methodIdentifiers
221+
222+
const result = {
223+
contractName: normal,
224+
abi: abi,
225+
bytecode: dpb,
226+
runtimeBytecode: runtimeBytecode,
227+
ir: '',
228+
methodIdentifiers: methodIdentifiers
229+
}
230+
return result
231+
}
232+
233+
// ERROR
234+
if (isCompilationError(output)) {
235+
const line = output.line
236+
if (line) {
237+
const lineColumnPos = {
238+
start: {line: line - 1, column: 10},
239+
end: {line: line - 1, column: 10}
240+
}
241+
// remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
242+
} else {
243+
const regex = output?.message?.match(/line ((\d+):(\d+))+/g)
244+
const errors = output?.message?.split(/line ((\d+):(\d+))+/g) // extract error message
245+
if (regex) {
246+
let errorIndex = 0
247+
regex.map((errorLocation) => {
248+
const location = errorLocation?.replace('line ', '').split(':')
249+
let message = errors[errorIndex]
250+
errorIndex = errorIndex + 4
251+
if (message && message?.split('\n\n').length > 0) {
252+
try {
253+
message = message?.split('\n\n')[message.split('\n\n').length - 1]
254+
} catch (e) {}
255+
}
256+
if (location?.length > 0) {
257+
const lineColumnPos = {
258+
start: {line: parseInt(location[0]) - 1, column: 10},
259+
end: {line: parseInt(location[0]) - 1, column: 10}
260+
}
261+
// remixClient.highlight(lineColumnPos as any, _contract.name, message)
262+
}
263+
})
264+
}
265+
}
266+
throw new Error(output.message)
267+
}
268+
// SUCCESS
269+
// remixClient.discardHighlight()
270+
remixClient.changeStatus({
271+
key: 'succeed',
272+
type: 'success',
273+
title: 'success'
274+
})
275+
276+
const data = toStandardOutput(_contract.name, output)
277+
remixClient.compilationFinish(_contract.name, _contract.content, data)
278+
if (setOutput === null || setOutput === undefined) {
279+
const contractName = _contract['name']
280+
const compileResult = compileReturnType()
281+
remixClient.eventEmitter.emit('setOutput', { contractName, compileResult })
282+
} else {
283+
setOutput(_contract.name, compileReturnType())
284+
}
285+
} catch (err: any) {
286+
remixClient.changeStatus({
287+
key: 'failed',
288+
type: 'error',
289+
title: err.message
290+
})
291+
}
292+
}
293+
294+
295+
296+
170297
export type StandardOutput = {
171298
sources: {
172299
[fileName: string]: {

0 commit comments

Comments
 (0)