Merge branch 'master' of https://github.com/ethereum/remix-project into newFEdesktop

pull/4401/head
bunsenstraat 11 months ago
commit 408bf81cec
  1. 4
      apps/remix-ide-e2e/src/commands/createContract.ts
  2. 75
      apps/remix-ide-e2e/src/tests/vyper_api.test.ts
  3. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  4. 5
      apps/remix-ide/src/app.js
  5. 1
      apps/remix-ide/src/app/plugins/copilot/suggestion-service/copilot-suggestion.ts
  6. 170
      apps/remix-ide/src/app/plugins/vyper-compilation-details.tsx
  7. 4
      apps/remix-ide/src/remixAppManager.js
  8. 8
      apps/vyper/src/app/app.css
  9. 43
      apps/vyper/src/app/app.tsx
  10. 81
      apps/vyper/src/app/components/CompilerButton.tsx
  11. 2
      apps/vyper/src/app/components/LocalUrl.tsx
  12. 63
      apps/vyper/src/app/components/VyperResult.tsx
  13. 135
      apps/vyper/src/app/utils/compiler.tsx
  14. 9
      apps/vyper/src/app/utils/remix-client.tsx
  15. 540
      apps/vyper/src/app/utils/types.ts
  16. 65
      libs/remix-ui/solidity-compile-details/src/lib/components/solidityCompile.tsx
  17. 70
      libs/remix-ui/solidity-compile-details/src/lib/solidity-compile-details.tsx
  18. 1
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  19. 1
      libs/remix-ui/vyper-compile-details/src/index.ts
  20. 28
      libs/remix-ui/vyper-compile-details/src/lib/vyper-compile-details.tsx
  21. 85
      libs/remix-ui/vyper-compile-details/src/lib/vyperCompile.tsx
  22. 2
      package.json
  23. 3
      tsconfig.paths.json
  24. 50
      yarn.lock

@ -16,7 +16,9 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) { function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) { if (inputParams) {
browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () { browser.setValue('.udapp_contractActionsContainerSingle > input', inputParams, function () {
browser.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() }) browser
.waitForElementVisible('.udapp_contractActionsContainerSingle > div')
.click('.udapp_contractActionsContainerSingle > div').pause(500).perform(function () { callback() })
}) })
} else { } else {
browser browser

@ -9,7 +9,7 @@ declare global {
module.exports = { module.exports = {
'@disabled': true, '@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) init(browser, done, 'http://localhost:8080')
}, },
'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) { 'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) {
@ -47,27 +47,54 @@ module.exports = {
.frame(0) .frame(0)
.click('[data-id="remote-compiler"]') .click('[data-id="remote-compiler"]')
.click('[data-id="compile"]') .click('[data-id="compile"]')
.isVisible({ .waitForElementVisible({
selector: '[data-id="copy-abi"]', selector:'[data-id="compilation-details"]',
timeout: 4000, timeout: 120000
abortOnFailure: false,
suppressNotFoundErrors: true
}, (okVisible) => {
if (okVisible.value === null) {
console.log('retrying compilation...')
browser.click('[data-id="compile"]').waitForElementVisible('[data-id="copy-abi"]')
} else{
browser.assert.ok(okVisible.value === true, 'ABI should be visible')
}
}) })
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.waitForElementVisible({
selector: "//*[@class='variable-value' and contains(.,'highestBidder')]",
locateStrategy: 'xpath',
})
},
'Should copy abi after blind_auction compile #group1': function (browser: NightwatchBrowser) {
if (browser.browserName.indexOf('chrome') > -1) {
const chromeBrowser = (browser as any).chrome
chromeBrowser.setPermission('clipboard-read', 'granted')
chromeBrowser.setPermission('clipboard-write', 'granted')
browser
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.click('[data-id="copy-abi"]')
.executeAsyncScript(function (done) {
navigator.clipboard.readText()
.then(function (clippedText) {
done(clippedText)
}).catch(function (error) {
console.log('Failed to read clipboard contents: ', error)
done()
})
}, [], function (result) {
console.log('clipboard result: ' + result)
browser.assert.ok((result as any).value.length > 1, 'abi copied to clipboard')
})
}
}, },
'Compile test contract and deploy to remix VM #group1': function (browser: NightwatchBrowser) { 'Compile test contract and deploy to remix VM #group1': function (browser: NightwatchBrowser) {
let contractAddress let contractAddress
browser browser
.frameParent()
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace') .switchWorkspace('default_workspace')
.addFile('test.vy', { content: testContract }) .addFile('test.vy', { content: testContract })
@ -75,20 +102,13 @@ module.exports = {
// @ts-ignore // @ts-ignore
.frame(0) .frame(0)
.click('[data-id="compile"]') .click('[data-id="compile"]')
.isVisible({ .waitForElementVisible({
selector: '[data-id="copy-abi"]', selector:'[data-id="compilation-details"]',
timeout: 4000, timeout: 60000
abortOnFailure: false,
suppressNotFoundErrors: true
}, (okVisible) => {
if (okVisible.value === null) {
console.log('retrying compilation...')
browser.click('[data-id="compile"]').waitForElementVisible('[data-id="copy-abi"]')
} else{
browser.assert.ok(okVisible.value === true, 'ABI should be visible')
}
}) })
.click('[data-id="compilation-details"]')
.frameParent() .frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.createContract('') .createContract('')
.clickInstance(0) .clickInstance(0)
@ -105,7 +125,6 @@ module.exports = {
} }
const testContract = ` const testContract = `
# @version >=0.2.4 <0.3.0
DNA_DIGITS: constant(uint256) = 16 DNA_DIGITS: constant(uint256) = 16
DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS

@ -15,6 +15,7 @@ declare module 'nightwatch' {
verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser
selectAccount(account?: string): NightwatchBrowser selectAccount(account?: string): NightwatchBrowser
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser
checkClipboard(): NightwatchBrowser
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser
goToVMTraceStep(step: number, incr?: number): NightwatchBrowser goToVMTraceStep(step: number, incr?: number): NightwatchBrowser
checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser checkVariableDebug(id: string, debugValue: NightwatchCheckVariableDebugValue): NightwatchBrowser

@ -47,6 +47,7 @@ import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format' import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen' import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { CompilationDetailsPlugin } from './app/plugins/compile-details' import { CompilationDetailsPlugin } from './app/plugins/compile-details'
import { VyperCompilationDetailsPlugin } from './app/plugins/vyper-compilation-details'
import { ContractFlattener } from './app/plugins/contractFlattener' import { ContractFlattener } from './app/plugins/contractFlattener'
import { TemplatesPlugin } from './app/plugins/remix-templates' import { TemplatesPlugin } from './app/plugins/remix-templates'
import { fsPlugin } from './app/plugins/electron/fsPlugin' import { fsPlugin } from './app/plugins/electron/fsPlugin'
@ -56,6 +57,7 @@ import { electronTemplates } from './app/plugins/electron/templatesPlugin'
import { xtermPlugin } from './app/plugins/electron/xtermPlugin' import { xtermPlugin } from './app/plugins/electron/xtermPlugin'
import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin' import { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin' import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import {OpenAIGpt} from './app/plugins/openaigpt' import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron') const isElectron = require('is-electron')
@ -225,7 +227,7 @@ class AppComponent {
// ----------------- Compilation Details ---------------------------- // ----------------- Compilation Details ----------------------------
const compilationDetails = new CompilationDetailsPlugin(appManager) const compilationDetails = new CompilationDetailsPlugin(appManager)
const vyperCompilationDetails = new VyperCompilationDetailsPlugin(appManager)
// ----------------- ContractFlattener ---------------------------- // ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener() const contractFlattener = new ContractFlattener()
@ -353,6 +355,7 @@ class AppComponent {
search, search,
solidityumlgen, solidityumlgen,
compilationDetails, compilationDetails,
vyperCompilationDetails,
contractFlattener, contractFlattener,
solidityScript, solidityScript,
templates, templates,

@ -1,6 +1,7 @@
import {Plugin} from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import {SuggestionService, SuggestOptions} from './suggestion-service' import {SuggestionService, SuggestOptions} from './suggestion-service'
import axios, {AxiosResponse} from 'axios' import axios, {AxiosResponse} from 'axios'
//@ts-ignore
const _paq = (window._paq = window._paq || []) //eslint-disable-line const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = { const profile = {

@ -0,0 +1,170 @@
import React from 'react'
import { ViewPlugin } from '@remixproject/engine-web'
import {PluginViewWrapper} from '@remix-ui/helper'
import { RemixAppManager } from '../../remixAppManager'
import { RemixUiVyperCompileDetails } from '@remix-ui/vyper-compile-details'
import { ThemeKeys, ThemeObject } from '@microlink/react-json-view'
//@ts-ignore
const _paq = (window._paq = window._paq || [])
const profile = {
name: 'vyperCompilationDetails',
displayName: 'Vyper Compile Details',
description: 'Displays details from vyper compiler',
location: 'mainPanel',
methods: ['showDetails'],
events: []
}
export class VyperCompilationDetailsPlugin extends ViewPlugin {
dispatch: React.Dispatch<any> = () => {}
appManager: RemixAppManager
element: HTMLDivElement
payload: any
themeStyle: any
theme: ThemeKeys | ThemeObject
constructor(appManager: RemixAppManager) {
super(profile)
this.appManager = appManager
this.element = document.createElement('div')
this.element.setAttribute('id', 'vypercompileDetails')
this.payload = {
contractProperties: {} as any,
selectedContract: '',
help: {} as any,
insertValue: {} as any,
saveAs: {} as any,
}
}
async onActivation() {
this.handleThemeChange()
await this.call('tabs', 'focus', 'vyperCompilationDetails')
this.renderComponent()
_paq.push(['trackEvent', 'plugin', 'activated', 'vyperCompilationDetails'])
}
onDeactivation(): void {
}
async showDetails(sentPayload: any) {
const contractName = Object.entries(sentPayload).find(([key, value]) => key )
await this.call('tabs', 'focus', 'vyperCompilationDetails')
this.profile.displayName = `${contractName}`
this.payload = sentPayload
const active = await this.call('theme', 'currentTheme')
if (active.quality === 'dark') {
switch(active.name) {
case 'HackerOwl':
this.theme = 'harmonic'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Black':
this.theme = 'eighties'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Cyborg':
this.theme = 'shapeshifter'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Dark':
this.theme = 'flat'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
default:
this.theme = 'shapeshifter'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
}
} else {
switch(active.name) {
case 'Candy':
this.theme = 'apathy:inverted'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Midcentury':
this.theme = 'apathy:inverted'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Unicorn':
this.theme = 'apathy:inverted'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
case 'Violet':
this.theme = 'summerfruit:inverted'
this.themeStyle = { backgroundColor: active.backgroundColor }
break
default:
this.theme = 'bright:inverted'
break
}
}
this.renderComponent()
}
private handleThemeChange() {
this.on('theme', 'themeChanged', (theme: any) => {
if (theme.quality === 'dark') {
switch(theme.name) {
case 'HackerOwl':
this.theme = 'solarized'
this.themeStyle = { backgroundColor: theme.backgroundColor }
break
case 'Black':
this.theme = 'shapeshifter'
this.themeStyle = { backgroundColor: theme.backgroundColor }
break
case 'Cyborg':
this.theme = 'shapeshifter'
this.themeStyle = { backgroundColor: theme.backgroundColor }
break
case 'Dark':
this.theme = 'harmonic'
this.themeStyle = { backgroundColor: theme.backgroundColor }
break
default:
this.theme = 'shapeshifter'
this.themeStyle = { backgroundColor: theme.backgroundColor }
break
}
} else {
this.theme = 'bright:inverted'
this.themeStyle = { backgroundColor: theme.backgroundColor }
}
this.renderComponent()
})
}
setDispatch(dispatch: React.Dispatch<any>): void {
this.dispatch = dispatch
this.renderComponent()
}
render() {
return (
<div id="compileDetails">
<PluginViewWrapper plugin={this} />
</div>
)
}
renderComponent() {
this.dispatch({
...this,
...this.payload,
themeStyle: this.themeStyle,
theme: this.theme
})
}
updateComponent(state: any) {
return (
<RemixUiVyperCompileDetails
payload={state.payload}
theme={state.theme}
themeStyle={state.themeStyle}
/>
)
}
}

@ -73,6 +73,7 @@ let requiredModules = [ // services + layout views + system views
'codeFormatter', 'codeFormatter',
'solidityumlgen', 'solidityumlgen',
'compilationDetails', 'compilationDetails',
'vyperCompilationDetails',
'contractflattener', 'contractflattener',
'solidity-script', 'solidity-script',
'openaigpt', 'openaigpt',
@ -121,7 +122,8 @@ export function isNative(name) {
'doc-gen', 'doc-gen',
'doc-viewer', 'doc-viewer',
'circuit-compiler', 'circuit-compiler',
'compilationDetails' 'compilationDetails',
'vyperCompilationDetails'
] ]
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -45,15 +45,7 @@ html, body, #root, main {
} }
#local-url { #local-url {
width: 90%;
}
#compile-btn {
width: 100%;
}
#compile-btn * {
width: 100%;
} }
#result { #result {

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react' import React, {useState, useEffect} from 'react'
import {VyperCompilationOutput, remixClient} from './utils' import {remixClient} from './utils'
import {CompilationResult} from '@remixproject/plugin-api' import {CompilationResult} from '@remixproject/plugin-api'
// Components // Components
@ -22,18 +22,19 @@ interface AppState {
} }
interface OutputMap { interface OutputMap {
[fileName: string]: VyperCompilationOutput [fileName: string]: any
} }
const App = () => { const App = () => {
const [contract, setContract] = useState<string>() const [contract, setContract] = useState<string>()
const [output, setOutput] = useState<OutputMap>({}) const [output, setOutput] = useState<any>({})
const [state, setState] = useState<AppState>({ const [state, setState] = useState<AppState>({
status: 'idle', status: 'idle',
environment: 'local', environment: 'local',
localUrl: 'http://localhost:8000/compile' localUrl: 'http://localhost:8000/'
}) })
useEffect(() => { useEffect(() => {
async function start() { async function start() {
try { try {
@ -61,7 +62,11 @@ const App = () => {
} }
function compilerUrl() { function compilerUrl() {
return state.environment === 'remote' ? 'https://vyper.remixproject.org/compile' : state.localUrl return state.environment === 'remote' ? 'https://vyper2.remixproject.org/' : state.localUrl
}
function resetCompilerResultState() {
setOutput({})
} }
return ( return (
@ -76,14 +81,14 @@ const App = () => {
</a> </a>
</header> </header>
<section> <section>
<div className="px-4 w-100"> <div className="px-3 w-100">
<Button data-id="add-repository" className="w-100 text-dark w-100 bg-light btn-outline-primary " onClick={() => remixClient.cloneVyperRepo()}> <Button data-id="add-repository" className="w-100 text-dark bg-light btn-outline-primary " onClick={() => remixClient.cloneVyperRepo()}>
Clone Vyper examples repository Clone Vyper examples repository
</Button> </Button>
</div> </div>
<ToggleButtonGroup name="remote" onChange={setEnvironment} type="radio" value={state.environment}> <ToggleButtonGroup name="remote" onChange={setEnvironment} type="radio" value={state.environment}>
<ToggleButton id="remote-compiler" data-id="remote-compiler" variant="secondary" name="remote" value="remote"> <ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote">
Remote Compiler v0.2.16 Remote Compiler v0.3.10
</ToggleButton> </ToggleButton>
<ToggleButton id="local-compiler" data-id="local-compiler" variant="secondary" name="local" value="local"> <ToggleButton id="local-compiler" data-id="local-compiler" variant="secondary" name="local" value="local">
Local Compiler Local Compiler
@ -91,11 +96,23 @@ const App = () => {
</ToggleButtonGroup> </ToggleButtonGroup>
<LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} /> <LocalUrlInput url={state.localUrl} setUrl={setLocalUrl} environment={state.environment} />
<WarnRemote environment={state.environment} /> <WarnRemote environment={state.environment} />
<div className="px-4" id="compile-btn"> <div className="px-3 w-100" id="compile-btn">
<CompilerButton compilerUrl={compilerUrl()} contract={contract} setOutput={(name, update) => setOutput({...output, [name]: update})} /> <CompilerButton
compilerUrl={compilerUrl()}
contract={contract}
setOutput={(name, update) => setOutput({...output, [name]: update})}
resetCompilerState={resetCompilerResultState}
/>
</div> </div>
<article id="result" className="px-2">
<VyperResult output={contract ? output[contract] : undefined} /> <article id="result" className="px-2 mx-2 border-top mt-3">
{
output && Object.keys(output).length > 0 && output.status !== 'failed' ? (
<>
<VyperResult output={output} plugin={remixClient} />
</>
) : null
}
</article> </article>
</section> </section>
</main> </main>

@ -1,26 +1,31 @@
import React from 'react' import React, { Fragment, useState } from 'react'
import {isVyper, compile, toStandardOutput, VyperCompilationOutput, isCompilationError, remixClient} from '../utils' import {isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath} from '../utils'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
import _ from 'lodash'
interface Props { interface Props {
compilerUrl: string compilerUrl: string
contract?: string contract?: string
setOutput: (name: string, output: VyperCompilationOutput) => void setOutput: (name: string, output: any) => void
resetCompilerState: () => void
} }
function CompilerButton({contract, setOutput, compilerUrl}: Props) { function CompilerButton({contract, setOutput, compilerUrl, resetCompilerState}: Props) {
const [loadingSpinner, setLoadingSpinnerState] = useState(false)
if (!contract || !contract) { if (!contract || !contract) {
return <Button disabled>No contract selected</Button> return <Button disabled className="w-100">No contract selected</Button>
} }
if (!isVyper(contract)) { if (!isVyper(contract)) {
return <Button disabled>Not a vyper contract</Button> return <Button disabled className="w-100">Not a vyper contract</Button>
} }
/** Compile a Contract */ /** Compile a Contract */
async function compileContract() { async function compileContract() {
resetCompilerState()
setLoadingSpinnerState(true)
try { try {
await remixClient.discardHighlight() // await remixClient.discardHighlight()
let _contract: any let _contract: any
try { try {
_contract = await remixClient.getContract() _contract = await remixClient.getContract()
@ -37,10 +42,34 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
try { try {
output = await compile(compilerUrl, _contract) output = await compile(compilerUrl, _contract)
} catch (e: any) { } catch (e: any) {
setOutput(_contract.name, {status: 'failed', message: e.message}) remixClient.changeStatus({
key: 'failed',
type: 'error',
title: e.message
})
return return
} }
setOutput(_contract.name, output) const compileReturnType = () => {
const t: any = toStandardOutput(contract, output)
const temp = _.merge(t['contracts'][contract])
const normal = normalizeContractPath(contract)[2]
const abi = temp[normal]['abi']
const evm = _.merge(temp[normal]['evm'])
const dpb = evm.deployedBytecode
const runtimeBytecode = evm.bytecode
const methodIdentifiers = evm.methodIdentifiers
const result = {
contractName: normal,
abi: abi,
bytecode: dpb,
runtimeBytecode: runtimeBytecode,
ir: '',
methodIdentifiers: methodIdentifiers
}
return result
}
// ERROR // ERROR
if (isCompilationError(output)) { if (isCompilationError(output)) {
const line = output.line const line = output.line
@ -49,27 +78,27 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
start: {line: line - 1, column: 10}, start: {line: line - 1, column: 10},
end: {line: line - 1, column: 10} end: {line: line - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4') // remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else { } else {
const regex = output.message.match(/line ((\d+):(\d+))+/g) const regex = output?.message?.match(/line ((\d+):(\d+))+/g)
const errors = output.message.split(/line ((\d+):(\d+))+/g) // extract error message const errors = output?.message?.split(/line ((\d+):(\d+))+/g) // extract error message
if (regex) { if (regex) {
let errorIndex = 0 let errorIndex = 0
regex.map((errorLocation) => { regex.map((errorLocation) => {
const location = errorLocation.replace('line ', '').split(':') const location = errorLocation?.replace('line ', '').split(':')
let message = errors[errorIndex] let message = errors[errorIndex]
errorIndex = errorIndex + 4 errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) { if (message && message?.split('\n\n').length > 0) {
try { try {
message = message.split('\n\n')[message.split('\n\n').length - 1] message = message?.split('\n\n')[message.split('\n\n').length - 1]
} catch (e) {} } catch (e) {}
} }
if (location.length > 0) { if (location?.length > 0) {
const lineColumnPos = { const lineColumnPos = {
start: {line: parseInt(location[0]) - 1, column: 10}, start: {line: parseInt(location[0]) - 1, column: 10},
end: {line: parseInt(location[0]) - 1, column: 10} end: {line: parseInt(location[0]) - 1, column: 10}
} }
remixClient.highlight(lineColumnPos as any, _contract.name, message) // remixClient.highlight(lineColumnPos as any, _contract.name, message)
} }
}) })
} }
@ -77,14 +106,15 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
throw new Error(output.message) throw new Error(output.message)
} }
// SUCCESS // SUCCESS
remixClient.discardHighlight() // remixClient.discardHighlight()
remixClient.changeStatus({ remixClient.changeStatus({
key: 'succeed', key: 'succeed',
type: 'success', type: 'success',
title: 'succeed' title: 'success'
}) })
const data = toStandardOutput(_contract.name, output) const data = toStandardOutput(_contract.name, output)
remixClient.compilationFinish(_contract.name, _contract.content, data) remixClient.compilationFinish(_contract.name, _contract.content, data)
setOutput(_contract.name, compileReturnType())
} catch (err: any) { } catch (err: any) {
remixClient.changeStatus({ remixClient.changeStatus({
key: 'failed', key: 'failed',
@ -95,10 +125,17 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
} }
return ( return (
<Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column"> <Fragment>
<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">
<div className="d-flex align-items-center justify-content-center fa-1x">
<span className="fas fa-sync fa-pulse mr-1" />
<div className="text-truncate overflow-hidden text-nowrap">
<span>Compile</span> <span>Compile</span>
<span className="overflow-hidden text-truncate text-nowrap">{contract}</span> <span className="ml-1 text-nowrap">{contract}</span>
</Button> </div>
</div>
</button>
</Fragment>
) )
} }

@ -17,7 +17,7 @@ function LocalUrlInput({url, setUrl, environment}: Props) {
} }
return ( return (
<Form id="local-url"> <Form id="local-url" className="w-100 px-3">
<Form.Group controlId="localUrl"> <Form.Group controlId="localUrl">
<Form.Text className="text-warning pb-2">{'Currently we support vyper version > 0.2.16'}</Form.Text> <Form.Text className="text-warning pb-2">{'Currently we support vyper version > 0.2.16'}</Form.Text>
<Form.Label>Local Compiler Url</Form.Label> <Form.Label>Local Compiler Url</Form.Label>

@ -1,13 +1,14 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {VyperCompilationResult, VyperCompilationOutput, isCompilationError} from '../utils' import {VyperCompilationOutput, isCompilationError} from '../utils'
import Tabs from 'react-bootstrap/Tabs' import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab' import Tab from 'react-bootstrap/Tab'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
import JSONTree from 'react-json-view'
import {CopyToClipboard} from '@remix-ui/clipboard' import {CopyToClipboard} from '@remix-ui/clipboard'
import { VyperCompilationResult } from '../utils/types'
interface VyperResultProps { interface VyperResultProps {
output?: VyperCompilationOutput output?: any
plugin?: any
} }
export type ExampleContract = { export type ExampleContract = {
@ -15,8 +16,15 @@ export type ExampleContract = {
address: string address: string
} }
function VyperResult({output}: VyperResultProps) { type TabContentMembers = {
const [active, setActive] = useState<keyof VyperCompilationResult>('abi') tabText: string
tabPayload: any
tabMemberType: 'abi' | 'bytecode' | 'bytecode_runtime' | 'ir'
className: string
}
function VyperResult({ output, plugin }: VyperResultProps) {
// const [active, setActive] = useState<keyof VyperCompilationResult>('abi')
if (!output) if (!output)
return ( return (
@ -43,42 +51,17 @@ function VyperResult({output}: VyperResultProps) {
</div> </div>
) )
} }
return ( return (
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}> <>
<Tab eventKey="abi" title="ABI"> <div className="border border-top"></div>
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}> <div className="d-flex justify-content-center px-2 w-100">
<Button variant="info" className="copy" data-id="copy-abi"> <button data-id="compilation-details" className="btn btn-secondary w-100" onClick={async () => {
Copy ABI await plugin?.call('vyperCompilationDetails', 'showDetails', output)
</Button> }}>
</CopyToClipboard> <span>Compilation Details</span>
<JSONTree src={output.abi} /> </button>
</Tab> </div>
<Tab eventKey="bytecode" title="Bytecode"> </>
<CopyToClipboard getContent={() => output.bytecode}>
<Button variant="info" className="copy">
Copy Bytecode
</Button>
</CopyToClipboard>
<textarea defaultValue={output.bytecode}></textarea>
</Tab>
<Tab eventKey="bytecode_runtime" title="Runtime Bytecode">
<CopyToClipboard getContent={() => output.bytecode_runtime}>
<Button variant="info" className="copy">
Copy Runtime Bytecode
</Button>
</CopyToClipboard>
<textarea defaultValue={output.bytecode_runtime}></textarea>
</Tab>
<Tab eventKey="ir" title="LLL">
<CopyToClipboard getContent={() => output.ir}>
<Button variant="info" className="copy">
Copy LLL Code
</Button>
</CopyToClipboard>
<textarea defaultValue={output.ir}></textarea>
</Tab>
</Tabs>
) )
} }

@ -1,4 +1,5 @@
import {CompilationResult, ABIDescription} from '@remixproject/plugin-api' import { ABIDescription} from '@remixproject/plugin-api'
import axios from 'axios'
export interface Contract { export interface Contract {
name: string name: string
@ -8,6 +9,7 @@ export interface Contract {
export interface VyperCompilationResult { export interface VyperCompilationResult {
status: 'success' status: 'success'
bytecode: string bytecode: string
contractName?: string
bytecode_runtime: string bytecode_runtime: string
abi: ABIDescription[] abi: ABIDescription[]
ir: string ir: string
@ -30,12 +32,45 @@ export function isCompilationError(output: VyperCompilationOutput): output is Vy
return output.status === 'failed' return output.status === 'failed'
} }
export function normalizeContractPath(contractPath: string): string[] {
const paths = contractPath.split('/')
const filename = paths[paths.length - 1].split('.')[0]
let folders = ''
for (let i = 0; i < paths.length - 1; i++) {
if (i !== paths.length -1) {
folders += `${paths[i]}/`
}
}
const resultingPath = `${folders}${filename}`
return [folders,resultingPath, filename]
}
function parseErrorString(errorString) {
// Split the string into lines
let lines = errorString.trim().split('\n')
// Extract the line number and message
let message = lines[1].trim()
let targetLine = lines[2].split(',')
let lineColumn = targetLine[targetLine.length - 1].split(' ')[2].split(':')
const errorObject = {
status: 'failed',
message: message,
column: parseInt(lineColumn[1]),
line: parseInt(lineColumn[0])
}
message = null
targetLine = null
lineColumn = null
lines = null
return errorObject
}
/** /**
* Compile the a contract * Compile the a contract
* @param url The url of the compiler * @param url The url of the compiler
* @param contract The name and content of the contract * @param contract The name and content of the contract
*/ */
export async function compile(url: string, contract: Contract): Promise<VyperCompilationOutput> { export async function compile(url: string, contract: Contract): Promise<any> {
if (!contract.name) { if (!contract.name) {
throw new Error('Set your Vyper contract file.') throw new Error('Set your Vyper contract file.')
} }
@ -43,19 +78,45 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
if (extension !== 'vy') { if (extension !== 'vy') {
throw new Error('Use extension .vy for Vyper.') throw new Error('Use extension .vy for Vyper.')
} }
const response = await fetch(url, {
method: 'POST', let contractName = contract['name']
headers: {'Content-Type': 'application/json'}, const compilePackage = {
body: JSON.stringify({code: contract.content}) manifest: 'ethpm/3',
}) sources: {
[contractName] : {content : contract.content}
}
}
let response = await axios.post(`${url}compile`, compilePackage )
if (response.status === 404) { if (response.status === 404) {
throw new Error(`Vyper compiler not found at "${url}".`) throw new Error(`Vyper compiler not found at "${url}".`)
} }
/*if (response.status === 400) { if (response.status === 400) {
throw new Error(`Vyper compilation failed: ${response.statusText}`) throw new Error(`Vyper compilation failed: ${response.statusText}`)
}*/ }
return response.json()
const compileCode = response.data
contractName = null
response = null
let result: any
const status = await (await axios.get(url + 'status/' + compileCode , {
method: 'Get'
})).data
if (status === 'SUCCESS') {
result = await(await axios.get(url + 'artifacts/' + compileCode , {
method: 'Get'
})).data
return result
} else if (status === 'FAILED') {
const intermediate = await(await axios.get(url + 'exceptions/' + compileCode , {
method: 'Get'
})).data
result = parseErrorString(intermediate[0])
return result
}
await new Promise((resolve) => setTimeout(() => resolve({}), 3000))
} }
/** /**
@ -63,15 +124,21 @@ export async function compile(url: string, contract: Contract): Promise<VyperCom
* @param name Name of the contract file * @param name Name of the contract file
* @param compilationResult Result returned by the compiler * @param compilationResult Result returned by the compiler
*/ */
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult { export function toStandardOutput(fileName: string, compilationResult: any): any {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0] const contractName = normalizeContractPath(fileName)[2]
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g, '')) const compiledAbi = compilationResult['contractTypes'][contractName].abi
const deployedBytecode = compilationResult['contractTypes'][contractName].deploymentBytecode.bytecode.replace('0x', '')
const bytecode = compilationResult['contractTypes'][contractName].runtimeBytecode.bytecode.replace('0x', '')
const compiledAst = compilationResult['contractTypes'][contractName].ast
const methodIds = compilationResult['contractTypes'][contractName].methodIdentifiers
const methodIdentifiers = Object.entries(methodIds as Record<any,string>).map(([key, value]) => {
return { [key]: value.replace('0x', '') }
})
return { return {
sources: { sources: {
[fileName]: { [fileName]: {
id: 1, id: 1,
ast: {} as any, ast: compiledAst
legacyAST: {} as any
} }
}, },
contracts: { contracts: {
@ -80,16 +147,17 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
[contractName]: { [contractName]: {
// The Ethereum Contract ABI. If empty, it is represented as an empty array. // The Ethereum Contract ABI. If empty, it is represented as an empty array.
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi: compilationResult['abi'], abi: compiledAbi,
contractName: contractName,
evm: { evm: {
bytecode: { bytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode'].replace('0x', ''), object: deployedBytecode,
opcodes: '' opcodes: ''
}, },
deployedBytecode: { deployedBytecode: {
linkReferences: {}, linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x', ''), object: bytecode,
opcodes: '' opcodes: ''
}, },
methodIdentifiers: methodIdentifiers methodIdentifiers: methodIdentifiers
@ -99,6 +167,37 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
} }
} }
} }
export type StandardOutput = {
sources: {
[fileName: string]: {
id: number,
ast: AST
}
},
contracts: {
[fileName: string]: {
[contractName: string]: {
abi: ABI,
contractName: string,
evm: {
bytecode: BytecodeObject,
deployedBytecode: BytecodeObject,
methodIdentifiers: {
[method: string]: string
}
}
}
}
}
}
type AST = any // Replace with the actual AST type
type ABI = ABIDescription[] // Replace with the actual ABI type
type BytecodeObject = {
linkReferences: Record<string, any>,
object: string,
opcodes: string
}
/* /*
export function createCompilationResultMessage(name: string, result: any) { export function createCompilationResultMessage(name: string, result: any) {

@ -45,7 +45,7 @@ export class RemixClient extends PluginClient {
await this.call( await this.call(
'dGitProvider', 'dGitProvider',
'clone', 'clone',
{url: 'https://github.com/vyperlang/vyper', token: null}, {url: 'https://github.com/vyperlang/vyper', token: null, branch: 'v0.3.10'},
// @ts-ignore // @ts-ignore
'vyper-lang' 'vyper-lang'
) )
@ -66,6 +66,13 @@ export class RemixClient extends PluginClient {
this.client.emit('statusChanged', status) this.client.emit('statusChanged', status)
} }
checkActiveTheme() {
const active = this.client.call('theme', 'currentTheme')
if (active === 'dark') {
return 'monokai' as any
}
}
/** Highlight a part of the editor */ /** Highlight a part of the editor */
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) { async highlight(lineColumnPos: HighlightPosition, name: string, message: string) {
await this.client.call('editor', 'highlight', lineColumnPos, name) await this.client.call('editor', 'highlight', lineColumnPos, name)

@ -0,0 +1,540 @@
import {CompilationResult, ABIDescription} from '@remixproject/plugin-api'
export interface VyperCompilationResult {
status: 'success'
bytecode: string
bytecode_runtime: string
abi: ABIDescription[]
ir: string
method_identifiers: {
[method: string]: string
}
}
export interface VyperCompilationError {
status: 'failed'
column?: number
line?: number
message: string
}
export type VyperCompilationResultType = {
buildDependencies: any
compilers: [
contractTypes: [],
name: string,
settings: {
optimize: boolean
outputSelection: {
['fileName']: {
['contractName']: string[]
}
}
version: string
}
]
contractTypes: {
['fileName']: {
abi: any[]
ast: {
name: string,
children: any[]
classification: number
col_offset: number
end_col_offset: number
end_lineno: number
lineno: number
src: {
length: number
jump_code: string
}
}
contractName: string
deploymentBytecode: {
bytecode: string
}
dev_messages: any
devdoc: {
methods: any
}
pcmap: any
runtimeBytecode: {
bytecode: string
}
sourceId: string
sourcemap: string
userdoc: {
methods: any
}
}
}
deployments: any
manifest: string
meta: any
sources: {
['fileName'] : {
checksum: any
content: string
imports: string[]
references: []
urls: []
}
}
}
export interface PackageManifest {
title: string;
description: string;
type: TypeEnum;
required: string[];
version: string;
properties: PackageManifestProperties;
definitions: Definitions;
}
export interface Definitions {
packageMeta: ByteString;
contractType: ByteString;
contractInstance: ContractInstance;
byteString: ByteString;
bytecodeObject: BytecodeObject;
linkReference: LinkReference;
linkValue: LinkValue;
identifier: ByteString;
contractInstanceName: ByteString;
deployment: Deployment;
packageContractInstanceName: ByteString;
compilerInformation: CompilerInformation;
address: Address;
transactionHash: Address;
blockHash: Address;
contentURI: ByteString;
}
export interface Address {
title: string;
description: string;
allOf: AllOf[];
}
export interface AllOf {
ref?: string;
minLength?: number;
maxLength?: number;
}
export interface ByteStringProperties {
contractName?: ByteString;
deploymentBytecode?: Meta;
runtimeBytecode?: Meta;
abi?: ByteString;
natspec?: ByteString;
compiler?: Meta;
authors?: ByteString;
license?: ByteString;
description?: ByteString;
keywords?: ByteString;
links?: Links;
}
export interface ByteString {
title: string;
description?: string;
type: TypeEnum;
pattern?: string;
format?: string;
items?: Items;
properties?: ByteStringProperties;
patternProperties?: { [key: string]: Meta };
}
export interface Meta {
ref: string;
}
export interface Links {
title: string;
descriptions: string;
type: TypeEnum;
additionalProperties: AdditionalProperties;
}
export interface AdditionalProperties {
type: TypeEnum;
format: string;
}
export type TypeEnum = "string" | "array" | "object";
export interface Items {
ref?: string;
type?: TypeEnum;
}
export interface BytecodeObject {
title: string
type: TypeEnum
offsets: number[]
anyOf: BytecodeObjectAnyOf[]
properties: BytecodeObjectProperties
bytecode: string
linkReferences?: { offset?: any; length?: number; name?: string}[]
}
export interface BytecodeObjectAnyOf {
required: string[];
}
export interface BytecodeObjectProperties {
bytecode: Meta;
linkReferences: Link;
linkDependencies: Link;
}
export interface Link {
type: TypeEnum;
items: Meta;
}
export interface CompilerInformation {
title: string;
description: string;
type: TypeEnum;
required: string[];
properties: CompilerInformationProperties;
}
export interface CompilerInformationProperties {
name: Name;
version: Name;
settings: Name;
}
export interface Name {
description: string;
type: TypeEnum;
}
export interface ContractInstance {
title: string;
description: string;
type: TypeEnum;
required: string[];
properties: ContractInstanceProperties;
}
export interface ContractInstanceProperties {
contractType: ByteString;
address: Meta;
transaction: Meta;
block: Meta;
runtimeBytecode: Meta;
compiler: Meta;
linkDependencies: ByteString;
}
export interface Deployment {
title: string;
type: TypeEnum;
patternProperties: DeploymentPatternProperties;
}
export interface DeploymentPatternProperties {
aZAZAZAZ09_0254$: Meta;
}
export interface LinkReference {
title: string;
description: string;
type: TypeEnum;
required: string[];
properties: LinkReferenceProperties;
}
export interface LinkReferenceProperties {
offsets: Offsets;
length: Length;
name: Meta;
}
export interface Length {
type: string;
minimum: number;
}
export interface Offsets {
type: TypeEnum;
items: Length;
}
export interface LinkValue {
title: string;
description: string;
type: TypeEnum;
required: string[];
properties: LinkValueProperties;
oneOf: OneOf[];
}
export interface OneOf {
properties: OneOfProperties;
}
export interface OneOfProperties {
type: TypeClass;
value: PurpleValue;
}
export interface TypeClass {
enum: string[];
}
export interface PurpleValue {
ref?: string;
anyOf?: Meta[];
}
export interface LinkValueProperties {
offsets: Offsets;
type: Name;
value: FluffyValue;
}
export interface FluffyValue {
description: string;
}
export interface PackageManifestProperties {
manifestVersion: ManifestVersion;
packageName: ByteString;
meta: Meta;
version: Version;
sources: Sources;
contractTypes: ByteString;
deployments: ByteString;
buildDependencies: BuildDependencies;
}
export interface BuildDependencies {
title: string;
type: TypeEnum;
patternProperties: BuildDependenciesPatternProperties;
}
export interface BuildDependenciesPatternProperties {
aZAZ090254$: Meta;
}
export interface ManifestVersion {
type: TypeEnum;
title: string;
description: string;
default: string;
enum: string[];
}
export interface Sources {
title: string;
type: TypeEnum;
patternProperties: SourcesPatternProperties;
}
export interface SourcesPatternProperties {
empty: Empty;
}
export interface Empty {
anyOf: AnyOf[];
}
export interface AnyOf {
title?: string;
type?: TypeEnum;
ref?: string;
}
export interface Version {
title: string;
type: TypeEnum;
}
export type CompileFormat = {
contractTypes: {
abi?: ABI[]
ast?: AST
contractName?: string
depolymentBytecode?: BytecodeObject
devMessages?: { [key: string]: string }
devdoc?: Devdoc
methodIdentifiers?: { [key: string]: string }
pcmap?: any
runtimeBytecode?: BytecodeObject
sourceId?: string
sourcemap?: string
userdoc?: { [key: string]: string }
}
manifest?: string
sources?: {
[fileName: string]: {
content: string
urls?: string[]
references?: string[]
imports?: string[]
checksum?: { [key: string]: string }
}
}
}
export type Devdoc = {
methods: any
}
export type ETHPM3Format = {
manifest: 'ethpm/3'
name: string
version: string
meta: Record<string, any>
buildDependencies: Record<string, any>
sources: {
[fileName: string]: {
content: string
checksum?: {
keccak256: string
hash: string
}
type?: any
license?: string
}
}
compilers: CompilerInformationObject[]
contractTypes: {
contractName: string
sourceId?: string
depolymentBytecode: {
bytecode: string
linkReferences: {
offset: any
length: number
name?: string
}
linkDependencies?: { offsets: number[]}
}
runtimeBytecode: {
bytecode: string
linkReferences: {
offset: any
length: number
name?: string
}
linkDependencies?: LinkValueObject
}
abi: ABI[]
ast: AST
userDoc?: {
methods: any
notice: string
}
devDoc?: {
methods: any,
author: string
details: string
title: string
license: string
}
}
deployments: {
[contractName: string]: ContractInstanceObject
}
}
export type CompilerInformationObject = {
name: string
version: string
settings?: {
optimizer: {
enabled: boolean
runs: number
}
outputSelection: {
[fileName: string]: {
[contractName: string]: string[]
}
}
},
contractTypes?: string[]
}
export type LinkValueObject = {
offsets: number[]
type: string
value: string
}
export type PackageMetaDataObject = {
authors?: string[]
description?: string
keywords?: string[]
license?: string
links?: {
[key: string]: string
}
}
export type ContractInstanceObject = {
contractType: string
address: string
transaction?: string
block?: string
runtimeBytecode?: BytecodeObject
compiler?: string
linkDependencies?: LinkValueObject
}
export type ASTSrc = {
jumpCode: string;
length: number;
}
export type Child = {
astType: string;
children: Child[];
classification: number;
colOffset: number;
endColOffset: number;
endLineno: number;
lineno: number;
name?: string;
src: ChildSrc;
docStr?: Child;
}
export type ChildSrc = {
jumpCode: string;
length: number;
start: number;
}
export type AST = {
astType: string;
children: Child[];
classification: number;
colOffset: number;
endColOffset: number;
endLineno: number;
lineno: number;
name: string;
src: ASTSrc;
}
export type ABI = {
anonymous?: boolean;
inputs: any[];
name?: string;
type: any
stateMutability?: any;
outputs?: any[];
}

@ -0,0 +1,65 @@
import { CopyToClipboard } from '@remix-ui/clipboard'
import { CustomTooltip } from '@remix-ui/helper'
import { ContractPropertyName } from '@remix-ui/solidity-compiler'
import React from 'react'
import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { useIntl } from 'react-intl'
const _paq = (window._paq = window._paq || [])
export default function SolidityCompile({ contractProperties, selectedContract, help, insertValue, saveAs, plugin }: any) {
const intl = useIntl()
const downloadFn = () => {
_paq.push(['trackEvent', 'compiler', 'compilerDetails', 'download'])
saveAs(new Blob([JSON.stringify(contractProperties, null, '\t')]), `${selectedContract}_compData.json`)
}
return (
<>
<div className="d-flex justify-content-between align-items-center mr-1">
<span className="lead">{selectedContract}</span>
<CustomTooltip tooltipText={intl.formatMessage({id: 'solidity.compileDetails'})}>
<span className="btn btn-outline-success border-success mr-1" onClick={downloadFn}>Download</span>
</CustomTooltip>
</div>
<div className="remixui_detailsJSON">
{<TreeView>
{Object.keys(contractProperties).map((propertyName: ContractPropertyName, index) => {
const copyDetails = (
<span className="remixui_copyDetails">
<CopyToClipboard tip={intl.formatMessage({id: 'solidity.copy'})} content={contractProperties[propertyName]} direction="top" />
</span>
)
const questionMark = (
<span className="remixui_questionMark">
<i
title={intl.formatMessage({
id: `solidity.${propertyName}`,
defaultMessage: help[propertyName]
})}
className="fas fa-question-circle"
aria-hidden="true"
></i>
</span>
)
return (
<div className="remixui_log" key={index}>
<TreeViewItem
label={
<div data-id={`remixui_treeviewitem_${propertyName}`} className="remixui_key">
{propertyName} {copyDetails} {questionMark}
</div>
}
expand={propertyName === 'metadata' || propertyName === 'bytecode' ? true : false}
iconY='fas fa-caret-down'
>
{insertValue(contractProperties, propertyName)}
</TreeViewItem>
</div>
)
})}
</TreeView>}
</div>
</>
)
}

@ -4,14 +4,14 @@ import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { ContractPropertyName } from '@remix-ui/solidity-compiler' import { ContractPropertyName } from '@remix-ui/solidity-compiler'
import React from 'react' import React from 'react'
import { useIntl } from 'react-intl' import SolidityCompile from './components/solidityCompile'
export interface RemixUiCompileDetailsProps { export interface RemixUiCompileDetailsProps {
plugin: any plugin?: any
contractProperties: any contractProperties?: any
selectedContract: string selectedContract?: string
help: any help?: any
insertValue: any insertValue?: any
saveAs: any saveAs: any
} }
@ -19,58 +19,16 @@ const _paq = (window._paq = window._paq || [])
export function RemixUiCompileDetails({ plugin, contractProperties, selectedContract, saveAs, help, insertValue }: RemixUiCompileDetailsProps) { export function RemixUiCompileDetails({ plugin, contractProperties, selectedContract, saveAs, help, insertValue }: RemixUiCompileDetailsProps) {
const intl = useIntl()
const downloadFn = () => {
_paq.push(['trackEvent', 'compiler', 'compilerDetails', 'download'])
saveAs(new Blob([JSON.stringify(contractProperties, null, '\t')]), `${selectedContract}_compData.json`)
}
return ( return (
<> <>
<div className="d-flex justify-content-between align-items-center mr-1"> <SolidityCompile
<span className="lead">{selectedContract}</span> contractProperties={contractProperties}
<CustomTooltip tooltipText={intl.formatMessage({id: 'solidity.compileDetails'})}> plugin={plugin}
<span className="btn btn-outline-success border-success mr-1" onClick={downloadFn}>Download</span> selectedContract={selectedContract}
</CustomTooltip> help={help}
</div> insertValue={insertValue}
<div className="remixui_detailsJSON"> saveAs={saveAs}
<TreeView> />
{Object.keys(contractProperties).map((propertyName: ContractPropertyName, index) => {
const copyDetails = (
<span className="remixui_copyDetails">
<CopyToClipboard tip={intl.formatMessage({id: 'solidity.copy'})} content={contractProperties[propertyName]} direction="top" />
</span>
)
const questionMark = (
<span className="remixui_questionMark">
<i
title={intl.formatMessage({
id: `solidity.${propertyName}`,
defaultMessage: help[propertyName]
})}
className="fas fa-question-circle"
aria-hidden="true"
></i>
</span>
)
return (
<div className="remixui_log" key={index}>
<TreeViewItem
label={
<div data-id={`remixui_treeviewitem_${propertyName}`} className="remixui_key">
{propertyName} {copyDetails} {questionMark}
</div>
}
expand={propertyName === 'metadata' || propertyName === 'bytecode' ? true : false}
iconY='fas fa-caret-down'
>
{insertValue(contractProperties, propertyName)}
</TreeViewItem>
</div>
)
})}
</TreeView>
</div>
</> </>
) )
} }

@ -310,6 +310,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
</span> </span>
</CustomTooltip> </CustomTooltip>
</button> </button>
<button <button
data-id="compilation-details" data-id="compilation-details"
className="btn btn-secondary btn-block" className="btn btn-secondary btn-block"

@ -0,0 +1 @@
export * from './lib/vyper-compile-details'

@ -0,0 +1,28 @@
import React from 'react'
import VyperCompile from './vyperCompile'
import { ThemeKeys, ThemeObject } from '@microlink/react-json-view'
interface RemixUiVyperCompileDetailsProps {
payload: any
theme?: ThemeKeys | ThemeObject
themeStyle?: any
}
export function RemixUiVyperCompileDetails({ payload, theme, themeStyle }: RemixUiVyperCompileDetailsProps) {
const dpayload = Object.values(payload) as any ?? {}
const bcode = dpayload[0].bytecode ? dpayload[0].bytecode.object : ''
const runtimeBcode = dpayload[0].runtimeBytecode ? dpayload[0].runtimeBytecode.object : ''
const ir = dpayload[0].ir
const methodIdentifiers= dpayload[0].methodIdentifiers
const abi= dpayload[0].abi
return (
<>
<VyperCompile
result={{bytecode: bcode, bytecodeRuntime: runtimeBcode, ir: ir, methodIdentifiers: methodIdentifiers, abi: abi}}
theme={theme}
themeStyle={themeStyle}
/>
</>
)
}

@ -0,0 +1,85 @@
import { CopyToClipboard } from '@remix-ui/clipboard'
import JSONTree, { ThemeKeys, ThemeObject } from '@microlink/react-json-view'
import React, { useState } from 'react'
import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab'
import Button from 'react-bootstrap/Button'
import { ABIDescription } from '@remixproject/plugin-api'
const _paq = (window._paq = window._paq || [])
export interface VyperCompilationResult {
status?: 'success'
bytecode: string
bytecodeRuntime: string
abi: ABIDescription[]
ir: string
methodIdentifiers: {
[method: string]: string
}
}
export interface VyperCompileProps {
result: VyperCompilationResult
theme?: ThemeKeys | ThemeObject
themeStyle?: any
}
export default function VyperCompile({result, theme, themeStyle}: VyperCompileProps) {
const [active, setActive] = useState<keyof VyperCompilationResult>('abi')
const tabContent = [
{
tabHeadingText: 'ABI',
tabPayload: result.abi,
tabMemberType: 'abi',
tabButtonText: () => 'Copy ABI',
eventKey: 'abi'
},
{
tabHeadingText: 'Bytecode',
tabPayload: result.bytecode,
tabMemberType: 'bytecode',
tabButtonText: () => 'Copy Bytecode',
eventKey: 'bytecode'
},
{
tabHeadingText: 'Runtime Bytecode',
tabPayload: result.bytecodeRuntime,
tabMemberType: 'bytecode_runtime',
tabButtonText: () => 'Copy Runtime Bytecode',
eventKey: 'bytecode_runtime'
}
]
return (
<>
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)} justify>
{tabContent.map((content, index) => (
<Tab eventKey={content.eventKey} title={content.tabHeadingText} as={'span'} key={`${index}-${content.eventKey}`}>
<div className="d-flex flex-column w-75 justify-content-center mx-auto rounded-2">
<CopyToClipboard getContent={() => (content.eventKey !== 'abi' ? content.tabPayload : JSON.stringify(result['abi']))}>
<Button variant="info" className="copy mt-3 ml-2" data-id={content.eventKey === 'abi' ? 'copy-abi' : ''}>
<span className="far fa-copy mr-2"></span>
{content.tabButtonText()}
</Button>
</CopyToClipboard>
{content.eventKey === 'abi' ? (
<div className="my-3">
<JSONTree
src={content.tabPayload as ABIDescription[]}
theme={theme}
style={themeStyle}
/>
</div>
) : (
<div className="w-100 mt-2 p-2 mx-auto">
<textarea className="form-control rounded-2" defaultValue={content.tabPayload as string} rows={15}></textarea>
</div>
)}
</div>
</Tab>
))}
</Tabs>
</>
)
}

@ -136,6 +136,7 @@
"@ethereumjs/vm": "^6.4.1", "@ethereumjs/vm": "^6.4.1",
"@ethersphere/bee-js": "^3.2.0", "@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1", "@isomorphic-git/lightning-fs": "^4.4.1",
"@microlink/react-json-view": "^1.23.0",
"@openzeppelin/contracts": "^5.0.0", "@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/upgrades-core": "^1.30.0", "@openzeppelin/upgrades-core": "^1.30.0",
"@openzeppelin/wizard": "0.4.0", "@openzeppelin/wizard": "0.4.0",
@ -210,7 +211,6 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.4", "react-draggable": "^4.4.4",
"react-intl": "^6.0.4", "react-intl": "^6.0.4",
"react-json-view": "^1.21.3",
"react-markdown": "^8.0.5", "react-markdown": "^8.0.5",
"react-multi-carousel": "^2.8.2", "react-multi-carousel": "^2.8.2",
"react-router-dom": "^6.16.0", "react-router-dom": "^6.16.0",

@ -99,6 +99,9 @@
], ],
"@remix-ui/solidity-compile-details": [ "@remix-ui/solidity-compile-details": [
"libs/remix-ui/solidity-compile-details/src/index.ts" "libs/remix-ui/solidity-compile-details/src/index.ts"
],
"@remix-ui/vyper-compile-details": [
"libs/remix-ui/vyper-compile-details/src/index.ts"
], ],
"@remix-ui/solidity-compiler": [ "@remix-ui/solidity-compiler": [
"libs/remix-ui/solidity-compiler/src/index.ts" "libs/remix-ui/solidity-compiler/src/index.ts"

@ -4025,6 +4025,16 @@
semver "^7.3.8" semver "^7.3.8"
superstruct "^1.0.3" superstruct "^1.0.3"
"@microlink/react-json-view@^1.23.0":
version "1.23.0"
resolved "https://registry.yarnpkg.com/@microlink/react-json-view/-/react-json-view-1.23.0.tgz#641c2483b1a0014818303d4e9cce634d5dacc7e9"
integrity sha512-HYJ1nsfO4/qn8afnAMhuk7+5a1vcjEaS8Gm5Vpr1SqdHDY0yLBJGpA+9DvKyxyVKaUkXzKXt3Mif9RcmFSdtYg==
dependencies:
flux "~4.0.1"
react-base16-styling "~0.6.0"
react-lifecycles-compat "~3.0.4"
react-textarea-autosize "~8.3.2"
"@monaco-editor/loader@^1.4.0": "@monaco-editor/loader@^1.4.0":
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558"
@ -14264,9 +14274,9 @@ fbjs-css-vars@^1.0.0:
integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==
fbjs@^3.0.0, fbjs@^3.0.1: fbjs@^3.0.0, fbjs@^3.0.1:
version "3.0.4" version "3.0.5"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d"
integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==
dependencies: dependencies:
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
fbjs-css-vars "^1.0.0" fbjs-css-vars "^1.0.0"
@ -14274,7 +14284,7 @@ fbjs@^3.0.0, fbjs@^3.0.1:
object-assign "^4.1.0" object-assign "^4.1.0"
promise "^7.1.1" promise "^7.1.1"
setimmediate "^1.0.5" setimmediate "^1.0.5"
ua-parser-js "^0.7.30" ua-parser-js "^1.0.35"
fd-slicer@~1.1.0: fd-slicer@~1.1.0:
version "1.1.0" version "1.1.0"
@ -14600,10 +14610,10 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
inherits "^2.0.3" inherits "^2.0.3"
readable-stream "^2.3.6" readable-stream "^2.3.6"
flux@^4.0.1: flux@~4.0.1:
version "4.0.3" version "4.0.4"
resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572"
integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==
dependencies: dependencies:
fbemitter "^3.0.0" fbemitter "^3.0.0"
fbjs "^3.0.1" fbjs "^3.0.1"
@ -24176,7 +24186,7 @@ re-emitter@1.1.3:
resolved "https://registry.yarnpkg.com/re-emitter/-/re-emitter-1.1.3.tgz#fa9e319ffdeeeb35b27296ef0f3d374dac2f52a7" resolved "https://registry.yarnpkg.com/re-emitter/-/re-emitter-1.1.3.tgz#fa9e319ffdeeeb35b27296ef0f3d374dac2f52a7"
integrity sha1-+p4xn/3u6zWycpbvDz03TawvUqc= integrity sha1-+p4xn/3u6zWycpbvDz03TawvUqc=
react-base16-styling@^0.6.0: react-base16-styling@~0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ== integrity sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==
@ -24281,17 +24291,7 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-json-view@^1.21.3: react-lifecycles-compat@^3.0.4, react-lifecycles-compat@~3.0.4:
version "1.21.3"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475"
integrity sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==
dependencies:
flux "^4.0.1"
react-base16-styling "^0.6.0"
react-lifecycles-compat "^3.0.4"
react-textarea-autosize "^8.3.2"
react-lifecycles-compat@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
@ -24381,7 +24381,7 @@ react-tabs@^6.0.2:
clsx "^2.0.0" clsx "^2.0.0"
prop-types "^15.5.0" prop-types "^15.5.0"
react-textarea-autosize@^8.3.2: react-textarea-autosize@~8.3.2:
version "8.3.4" version "8.3.4"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524"
integrity sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ== integrity sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==
@ -28159,10 +28159,10 @@ typescript@^4.8.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
ua-parser-js@^0.7.30: ua-parser-js@^1.0.35:
version "0.7.33" version "1.0.37"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
uglify-js@^2.8.16: uglify-js@^2.8.16:
version "2.8.29" version "2.8.29"

Loading…
Cancel
Save