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

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

@ -4,12 +4,12 @@ import EventEmitter from 'events'
class ClickLaunchIcon extends EventEmitter {
command (this: NightwatchBrowser, icon: string): NightwatchBrowser {
this.api
.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]')
.click('#icon-panel div[plugin="' + icon + '"]')
.perform((done) => {
done()
this.emit('complete')
})
.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]')
.click('#icon-panel div[plugin="' + icon + '"]')
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}

@ -2,40 +2,40 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ConnectToExternalHttpProvider extends EventEmitter {
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('basic-http-provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('basic-http-provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
}
module.exports = ConnectToExternalHttpProvider

@ -16,7 +16,9 @@ class CreateContract extends EventEmitter {
function createContract (browser: NightwatchBrowser, inputParams: string, callback: VoidFunction) {
if (inputParams) {
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 {
browser

@ -9,7 +9,7 @@ declare global {
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
init(browser, done, 'http://localhost:8080')
},
'Should connect to vyper plugin #group1': function (browser: NightwatchBrowser) {
@ -47,27 +47,54 @@ module.exports = {
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.isVisible({
selector: '[data-id="copy-abi"]',
timeout: 4000,
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')
}
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 120000
})
.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) {
let contractAddress
browser
.frameParent()
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.addFile('test.vy', { content: testContract })
@ -75,20 +102,13 @@ module.exports = {
// @ts-ignore
.frame(0)
.click('[data-id="compile"]')
.isVisible({
selector: '[data-id="copy-abi"]',
timeout: 4000,
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')
}
.waitForElementVisible({
selector:'[data-id="compilation-details"]',
timeout: 60000
})
.click('[data-id="compilation-details"]')
.frameParent()
.waitForElementVisible('[data-id="copy-abi"]')
.clickLaunchIcon('udapp')
.createContract('')
.clickInstance(0)
@ -105,7 +125,6 @@ module.exports = {
}
const testContract = `
# @version >=0.2.4 <0.3.0
DNA_DIGITS: constant(uint256) = 16
DNA_MODULUS: constant(uint256) = 10 ** DNA_DIGITS
@ -136,4 +155,4 @@ def _createPokemon(_name: String[32], _dna: uint256, _HP: uint256):
matches: 0,
wins: 0
})
self.totalPokemonCount += 1`
self.totalPokemonCount += 1`

@ -15,6 +15,7 @@ declare module 'nightwatch' {
verifyContracts(compiledContractNames: string[], opts?: {wait: number; version?: string; runs?: string}): NightwatchBrowser
selectAccount(account?: string): NightwatchBrowser
clickFunction(fnFullName: string, expectedInput?: NightwatchClickFunctionExpectedInput): NightwatchBrowser
checkClipboard(): NightwatchBrowser
testFunction(txHash: string, expectedInput: NightwatchTestFunctionExpectedInput): NightwatchBrowser
goToVMTraceStep(step: number, incr?: number): 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 { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { CompilationDetailsPlugin } from './app/plugins/compile-details'
import { VyperCompilationDetailsPlugin } from './app/plugins/vyper-compilation-details'
import { ContractFlattener } from './app/plugins/contractFlattener'
import { TemplatesPlugin } from './app/plugins/remix-templates'
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 { ripgrepPlugin } from './app/plugins/electron/ripgrepPlugin'
import { compilerLoaderPlugin, compilerLoaderPluginDesktop } from './app/plugins/electron/compilerLoaderPlugin'
import {OpenAIGpt} from './app/plugins/openaigpt'
const isElectron = require('is-electron')
@ -163,11 +165,11 @@ class AppComponent {
'remix.ethereum.org': 23,
'6fd22d6fe5549ad4c4d8fd3ca0b7816b.mod': 35 // remix desktop
}
this.matomoConfAlreadySet = Registry.getInstance().get('config').api.exists('settings/matomo-analytics')
this.matomoCurrentSetting = Registry.getInstance().get('config').api.get('settings/matomo-analytics')
this.showMatamo = matomoDomains[window.location.hostname] && !this.matomoConfAlreadySet
this.walkthroughService = new WalkthroughService(appManager)
this.platform = isElectron() ? 'desktop' : 'web'
@ -225,7 +227,7 @@ class AppComponent {
// ----------------- Compilation Details ----------------------------
const compilationDetails = new CompilationDetailsPlugin(appManager)
const vyperCompilationDetails = new VyperCompilationDetailsPlugin(appManager)
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
@ -353,6 +355,7 @@ class AppComponent {
search,
solidityumlgen,
compilationDetails,
vyperCompilationDetails,
contractFlattener,
solidityScript,
templates,

@ -1,6 +1,7 @@
import {Plugin} from '@remixproject/engine'
import {SuggestionService, SuggestOptions} from './suggestion-service'
import axios, {AxiosResponse} from 'axios'
//@ts-ignore
const _paq = (window._paq = window._paq || []) //eslint-disable-line
const profile = {
@ -28,7 +29,7 @@ export class CopilotSuggestion extends Plugin {
})
this.service.events.on('ready', (data) => {
this.ready = true
})
})
}
useRemoteService(service: string) {
@ -81,7 +82,7 @@ export class CopilotSuggestion extends Plugin {
importsContent += '\n\n' + (await this.call('contentImport', 'resolve', imp)).content
} catch (e) {
console.log(e)
}
}
}
return importsContent
}

@ -58,11 +58,11 @@ export class SuggestionService {
this.responses[e.data.id](null, e.data)
} else {
this.responses[e.data.id]('aborted')
}
}
delete this.responses[e.data.id]
this.current = null
}
// Generation complete: re-enable the "Generate" button
break;
}
@ -95,6 +95,6 @@ export class SuggestionService {
if (error) return reject(error)
resolve(result)
}
})
})
}
}
}

@ -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',
'solidityumlgen',
'compilationDetails',
'vyperCompilationDetails',
'contractflattener',
'solidity-script',
'openaigpt',
@ -121,7 +122,8 @@ export function isNative(name) {
'doc-gen',
'doc-viewer',
'circuit-compiler',
'compilationDetails'
'compilationDetails',
'vyperCompilationDetails'
]
return nativePlugins.includes(name) || requiredModules.includes(name)
}

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

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

@ -1,26 +1,31 @@
import React from 'react'
import {isVyper, compile, toStandardOutput, VyperCompilationOutput, isCompilationError, remixClient} from '../utils'
import React, { Fragment, useState } from 'react'
import {isVyper, compile, toStandardOutput, isCompilationError, remixClient, normalizeContractPath} from '../utils'
import Button from 'react-bootstrap/Button'
import _ from 'lodash'
interface Props {
compilerUrl: 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) {
return <Button disabled>No contract selected</Button>
return <Button disabled className="w-100">No contract selected</Button>
}
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 */
async function compileContract() {
resetCompilerState()
setLoadingSpinnerState(true)
try {
await remixClient.discardHighlight()
// await remixClient.discardHighlight()
let _contract: any
try {
_contract = await remixClient.getContract()
@ -37,10 +42,34 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
try {
output = await compile(compilerUrl, _contract)
} catch (e: any) {
setOutput(_contract.name, {status: 'failed', message: e.message})
remixClient.changeStatus({
key: 'failed',
type: 'error',
title: e.message
})
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
if (isCompilationError(output)) {
const line = output.line
@ -49,27 +78,27 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
start: {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 {
const regex = output.message.match(/line ((\d+):(\d+))+/g)
const errors = output.message.split(/line ((\d+):(\d+))+/g) // extract error message
const regex = output?.message?.match(/line ((\d+):(\d+))+/g)
const errors = output?.message?.split(/line ((\d+):(\d+))+/g) // extract error message
if (regex) {
let errorIndex = 0
regex.map((errorLocation) => {
const location = errorLocation.replace('line ', '').split(':')
const location = errorLocation?.replace('line ', '').split(':')
let message = errors[errorIndex]
errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) {
if (message && message?.split('\n\n').length > 0) {
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) {}
}
if (location.length > 0) {
if (location?.length > 0) {
const lineColumnPos = {
start: {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)
}
// SUCCESS
remixClient.discardHighlight()
// remixClient.discardHighlight()
remixClient.changeStatus({
key: 'succeed',
type: 'success',
title: 'succeed'
title: 'success'
})
const data = toStandardOutput(_contract.name, output)
remixClient.compilationFinish(_contract.name, _contract.content, data)
setOutput(_contract.name, compileReturnType())
} catch (err: any) {
remixClient.changeStatus({
key: 'failed',
@ -95,10 +125,17 @@ function CompilerButton({contract, setOutput, compilerUrl}: Props) {
}
return (
<Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column">
<span>Compile</span>
<span className="overflow-hidden text-truncate text-nowrap">{contract}</span>
</Button>
<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 className="ml-1 text-nowrap">{contract}</span>
</div>
</div>
</button>
</Fragment>
)
}

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

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

@ -1,4 +1,5 @@
import {CompilationResult, ABIDescription} from '@remixproject/plugin-api'
import { ABIDescription} from '@remixproject/plugin-api'
import axios from 'axios'
export interface Contract {
name: string
@ -8,6 +9,7 @@ export interface Contract {
export interface VyperCompilationResult {
status: 'success'
bytecode: string
contractName?: string
bytecode_runtime: string
abi: ABIDescription[]
ir: string
@ -30,12 +32,45 @@ export function isCompilationError(output: VyperCompilationOutput): output is Vy
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
* @param url The url of the compiler
* @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) {
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') {
throw new Error('Use extension .vy for Vyper.')
}
const response = await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code: contract.content})
})
let contractName = contract['name']
const compilePackage = {
manifest: 'ethpm/3',
sources: {
[contractName] : {content : contract.content}
}
}
let response = await axios.post(`${url}compile`, compilePackage )
if (response.status === 404) {
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}`)
}*/
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 compilationResult Result returned by the compiler
*/
export function toStandardOutput(fileName: string, compilationResult: VyperCompilationResult): CompilationResult {
const contractName = fileName.split('/').slice(-1)[0].split('.')[0]
const methodIdentifiers = JSON.parse(JSON.stringify(compilationResult['method_identifiers']).replace(/0x/g, ''))
export function toStandardOutput(fileName: string, compilationResult: any): any {
const contractName = normalizeContractPath(fileName)[2]
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 {
sources: {
[fileName]: {
id: 1,
ast: {} as any,
legacyAST: {} as any
ast: compiledAst
}
},
contracts: {
@ -80,16 +147,17 @@ export function toStandardOutput(fileName: string, compilationResult: VyperCompi
[contractName]: {
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi: compilationResult['abi'],
abi: compiledAbi,
contractName: contractName,
evm: {
bytecode: {
linkReferences: {},
object: compilationResult['bytecode'].replace('0x', ''),
object: deployedBytecode,
opcodes: ''
},
deployedBytecode: {
linkReferences: {},
object: compilationResult['bytecode_runtime'].replace('0x', ''),
object: bytecode,
opcodes: ''
},
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) {

@ -45,7 +45,7 @@ export class RemixClient extends PluginClient {
await this.call(
'dGitProvider',
'clone',
{url: 'https://github.com/vyperlang/vyper', token: null},
{url: 'https://github.com/vyperlang/vyper', token: null, branch: 'v0.3.10'},
// @ts-ignore
'vyper-lang'
)
@ -66,6 +66,13 @@ export class RemixClient extends PluginClient {
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 */
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) {
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 React from 'react'
import { useIntl } from 'react-intl'
import SolidityCompile from './components/solidityCompile'
export interface RemixUiCompileDetailsProps {
plugin: any
contractProperties: any
selectedContract: string
help: any
insertValue: any
plugin?: any
contractProperties?: any
selectedContract?: string
help?: any
insertValue?: any
saveAs: any
}
@ -19,58 +19,16 @@ const _paq = (window._paq = window._paq || [])
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 (
<>
<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>
<SolidityCompile
contractProperties={contractProperties}
plugin={plugin}
selectedContract={selectedContract}
help={help}
insertValue={insertValue}
saveAs={saveAs}
/>
</>
)
}

@ -310,6 +310,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
</span>
</CustomTooltip>
</button>
<button
data-id="compilation-details"
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",
"@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1",
"@microlink/react-json-view": "^1.23.0",
"@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/upgrades-core": "^1.30.0",
"@openzeppelin/wizard": "0.4.0",
@ -210,7 +211,6 @@
"react-dom": "^18.2.0",
"react-draggable": "^4.4.4",
"react-intl": "^6.0.4",
"react-json-view": "^1.21.3",
"react-markdown": "^8.0.5",
"react-multi-carousel": "^2.8.2",
"react-router-dom": "^6.16.0",

@ -100,6 +100,9 @@
"@remix-ui/solidity-compile-details": [
"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": [
"libs/remix-ui/solidity-compiler/src/index.ts"
],

@ -4025,6 +4025,16 @@
semver "^7.3.8"
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":
version "1.4.0"
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==
fbjs@^3.0.0, fbjs@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6"
integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==
version "3.0.5"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.5.tgz#aa0edb7d5caa6340011790bd9249dbef8a81128d"
integrity sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==
dependencies:
cross-fetch "^3.1.5"
fbjs-css-vars "^1.0.0"
@ -14274,7 +14284,7 @@ fbjs@^3.0.0, fbjs@^3.0.1:
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.30"
ua-parser-js "^1.0.35"
fd-slicer@~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"
readable-stream "^2.3.6"
flux@^4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7"
integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw==
flux@~4.0.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.4.tgz#9661182ea81d161ee1a6a6af10d20485ef2ac572"
integrity sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==
dependencies:
fbemitter "^3.0.0"
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"
integrity sha1-+p4xn/3u6zWycpbvDz03TawvUqc=
react-base16-styling@^0.6.0:
react-base16-styling@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
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"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-json-view@^1.21.3:
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:
react-lifecycles-compat@^3.0.4, react-lifecycles-compat@~3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
@ -24381,7 +24381,7 @@ react-tabs@^6.0.2:
clsx "^2.0.0"
prop-types "^15.5.0"
react-textarea-autosize@^8.3.2:
react-textarea-autosize@~8.3.2:
version "8.3.4"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524"
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"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
ua-parser-js@^0.7.30:
version "0.7.33"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
ua-parser-js@^1.0.35:
version "1.0.37"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f"
integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==
uglify-js@^2.8.16:
version "2.8.29"

Loading…
Cancel
Save