Merge branch 'master' into spacelab-theme-fix

pull/2747/head
Aniket 2 years ago committed by GitHub
commit 739fdb018a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  2. 84
      apps/remix-ide-e2e/src/tests/editor_error_marker.test.ts
  3. 12
      apps/remix-ide-e2e/src/tests/gist.test.ts
  4. 37
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  5. 17
      apps/remix-ide-e2e/src/tests/vyper_api.ts
  6. 11
      apps/remix-ide/src/app/editor/editor.js
  7. 6
      apps/remix-ide/src/app/tabs/debugger-tab.js
  8. 3
      apps/remix-ide/src/app/tabs/styles/debugger-tab-styles.js
  9. 1
      apps/remix-ide/src/assets/css/themes/bootstrap-cerulean.min.css
  10. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  11. 75
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  12. 4
      apps/solidity-compiler/src/app/compiler-api.ts
  13. 7
      apps/vyper/src/app/app.tsx
  14. 47
      apps/vyper/src/app/components/CompilerButton.tsx
  15. 1
      apps/vyper/src/app/components/LocalUrl.tsx
  16. 17
      apps/vyper/src/app/components/VyperResult.tsx
  17. 161
      apps/vyper/src/app/examples/ballot.tsx
  18. 4
      apps/vyper/src/app/utils/compiler.tsx
  19. 53
      apps/vyper/src/app/utils/remix-client.tsx
  20. 94
      libs/remix-lib/src/execution/txListener.ts
  21. 4
      libs/remix-lib/src/helpers/hhconsoleSigs.ts
  22. 3
      libs/remix-solidity/src/compiler/compiler-worker.ts
  23. 14
      libs/remix-solidity/src/compiler/compiler.ts
  24. 2
      libs/remix-solidity/src/compiler/types.ts
  25. 4
      libs/remix-tests/src/compiler.ts
  26. 4
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.css
  27. 38
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  28. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
  29. 83
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  30. 1
      libs/remix-ui/helper/src/index.ts
  31. 15
      libs/remix-ui/helper/src/lib/bleach.ts
  32. 1
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.css
  33. 4
      libs/remix-ui/panel/src/lib/plugins/panel-header.tsx
  34. 2
      libs/remix-ui/panel/src/lib/plugins/remix-ui-panel.tsx
  35. 6
      libs/remix-ui/run-tab/src/lib/actions/actions.ts
  36. 3
      libs/remix-ui/run-tab/src/lib/actions/index.ts
  37. 9
      libs/remix-ui/run-tab/src/lib/actions/payload.ts
  38. 5
      libs/remix-ui/run-tab/src/lib/components/contractDropdownUI.tsx
  39. 17
      libs/remix-ui/run-tab/src/lib/components/mainnet.tsx
  40. 1
      libs/remix-ui/run-tab/src/lib/constants/index.ts
  41. 13
      libs/remix-ui/run-tab/src/lib/reducers/runTab.ts
  42. 4
      libs/remix-ui/run-tab/src/lib/run-tab.tsx
  43. 2
      libs/remix-ui/run-tab/src/lib/types/index.ts
  44. 6
      libs/remix-ui/settings/src/lib/github-settings.tsx
  45. 2
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  46. 4
      libs/remix-ui/solidity-compiler/src/lib/actions/compiler.ts
  47. 22
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  48. 2
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  49. 4
      libs/remix-ui/static-analyser/src/lib/remix-ui-static-analyser.tsx
  50. 2
      libs/remix-ui/terminal/src/lib/actions/terminalAction.ts
  51. 19
      libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx
  52. 8
      libs/remix-ui/vertical-icons-panel/src/lib/reducers/iconBadgeReducer.ts
  53. 2
      libs/remix-ui/workspace/src/lib/actions/events.ts

@ -205,6 +205,7 @@ module.exports = {
.clickLaunchIcon('debugger')
.waitForElementVisible('*[data-id="slider"]')
.goToVMTraceStep(154)
.scrollInto('*[data-id="stepdetail"]')
.waitForElementContainsText('*[data-id="stepdetail"]', 'vm trace step:\n154', 60000)
},

@ -0,0 +1,84 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should add error marker': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/adderror.ts', {content: addErrorMarker})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='cdr squiggly-error']")
.waitForElementVisible("//*[@class='cdr squiggly-warning']")
},
'Should clear error marker': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clear.ts', {content: clearMarkers})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementNotPresent("//*[@class='cdr squiggly-error']")
.waitForElementNotPresent("//*[@class='cdr squiggly-warning']")
}
}
const clearMarkers =`
(async () => {
await remix.call('editor', 'clearErrorMarkers' as any, ['contracts/1_Storage.sol'])
})()`
const addErrorMarker = `
(async () => {
let errors = [
{
position: {
start: {
line: 10,
column: 1,
},
end: {
line: 10,
column: 10
}
},
message: 'testing',
severity: 'error',
file: 'contracts/1_Storage.sol'
},
{
position: {
start: {
line: 18,
column: 1,
},
end: {
line: 18,
column: 10
}
},
message: 'testing2',
severity: 'warning',
file: 'contracts/1_Storage.sol'
},
]
await remix.call('editor', 'addErrorMarker' as any, errors)
})()`

@ -9,10 +9,11 @@ const testData = {
// 99266d6da54cc12f37f11586e8171546c7700d67
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
UploadToGists: function (browser: NightwatchBrowser) {
'UploadToGists #group1': function (browser: NightwatchBrowser) {
/*
- set the access token
- publish to gist
@ -68,7 +69,7 @@ module.exports = {
*/
},
'Load Gist Modal': function (browser: NightwatchBrowser) {
'Load Gist Modal #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
@ -84,7 +85,7 @@ module.exports = {
.modalFooterCancelClick('gisthandler')
},
'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) {
'Display Error Message For Invalid Gist ID #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -101,7 +102,7 @@ module.exports = {
.modalFooterOKClick('gisthandler')
},
'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) {
'Display Error Message For Missing Gist Token When Publishing #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -125,7 +126,7 @@ module.exports = {
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
},
'Import From Gist For Valid Gist ID': function (browser: NightwatchBrowser) {
'Import From Gist For Valid Gist ID #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.clickLaunchIcon('settings')
@ -140,6 +141,7 @@ module.exports = {
})
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')

@ -217,13 +217,42 @@ module.exports = {
.addFile('scripts/log_tx_block.js', { content: scriptBlockAndTransaction } )
.pause(1000)
.executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')')
.pause(10000)
// check if the input of the transaction is being logged (web3 call)
.journalChildIncludes('0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 120000)
// check if the logsBloom is being logged (web3 call)
.journalChildIncludes('0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001', 120000)
// check if the logsBloom is being logged (ethers.js call)
.journalChildIncludes('"hex":"0x025cd8"')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"hex":"0x025cd8"', 120000)
},
'Should listen on all transactions #group8': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('udapp') // connect to mainnet
.switchEnvironment('External 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"]', 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym')
.modalFooterOKClick('basic-http-provider')
.click('[data-id="terminalClearConsole"]') // clear the console
.click('[data-id="listenNetworkCheckInput"]') // start to listen
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from:', 200000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'to:', 200000)
.click('[data-id="terminalClearConsole"]') // clear the console
.waitForElementContainsText('*[data-id="terminalJournal"]', 'from:', 200000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'to:', 200000)
.click('[data-id="listenNetworkCheckInput"]') // stop to listen
.pause(30000)
.click('[data-id="terminalClearConsole"]') // clear the console
.pause(5000)
.click('[data-id="terminalClearConsole"]') // clear the console
.pause(20000)
.execute(function () {
return (document.querySelector('[data-id="terminalJournal"]') as any).innerText
}, [], function (result) {
browser.assert.equal(result.value, '', 'terminal log should be empty')
})
}
}

@ -21,25 +21,30 @@ module.exports = {
.frame(0)
},
'Should add the Ballot.vy #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-ballot"]')
'Should clone the Vyper repo #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-repository"]')
.frameParent()
.openFile('ballot.vy')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'Vyper repository cloned', 30000)
.openFile('examples')
.openFile('examples/auctions')
.openFile('examples/auctions/blind_auction.vy')
},
'Compile ballot.vy should error #group1': function (browser: NightwatchBrowser) {
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.assert.containsText('[data-id="error-message"]', 'unexpected indent')
.waitForElementVisible('[data-id="copy-abi"]')
},
'Compile test contract should success #group1': function (browser: NightwatchBrowser) {
'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 })
.clickLaunchIcon('vyper')
// @ts-ignore

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'addErrorMarker', 'clearErrorMarkers']
}
class Editor extends Plugin {
@ -504,6 +504,15 @@ class Editor extends Plugin {
}
}
// error markers
async addErrorMarker (error){
this.api.addErrorMarker(error)
}
async clearErrorMarkers(sources){
this.api.clearErrorMarkers(sources)
}
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* An annotation has the following shape:

@ -3,7 +3,7 @@ import { DebuggerApiMixin } from '@remixproject/debugger-plugin' // eslint-disab
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import * as remixBleach from '../../lib/remixBleach'
import { bleach } from '@remix-ui/helper'
import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMsg, notFoundToastMsg, sourceVerificationNotAvailableToastMsg } from '@remix-ui/helper'
const css = require('./styles/debugger-tab-styles')
@ -51,7 +51,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
return <div className={css.debuggerTabView} id='debugView'><DebuggerUI debuggerAPI={this} /></div>
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} /></div>
}
showMessage (title, message) {
@ -59,7 +59,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: remixBleach.sanitize(message)
message: bleach.sanitize(message)
})
} catch (e) {
console.log(e)

@ -1,9 +1,6 @@
var csjs = require('csjs-inject')
const css = csjs`
.debuggerTabView {
padding: 2%;
}
.debugger {
margin-bottom: 1%;
}

@ -34,6 +34,7 @@
--dark:#343a40;
--body-bg: #fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -9,8 +9,7 @@
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;
700&display=swap);
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap);
:root {
--blue:#2a9fd6;
--indigo:#6610f2;
@ -36,6 +35,7 @@
--dark:#adafae;
--body-bg: #060606;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -1,75 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../package.json'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = {
name: 'offsetToLineColumnConverter',
methods: ['offsetToLineColumn'],
events: [],
version: packageJson.version
}
export class OffsetToLineColumnConverter extends Plugin {
constructor () {
super(profile)
this.lineBreakPositionsByContent = {}
this.sourceMappingDecoder = sourceMappingDecoder
}
/**
* Convert offset representation with line/column representation.
* This is also used to resolve the content:
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index.
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {Object.<string, {content}>} sources - Map of content sources
* @param {Object.<string, {ast, id}>} asts - Map of content sources
*/
offsetToLineColumn (rawLocation, file, sources, asts) {
if (!this.lineBreakPositionsByContent[file]) {
const sourcesArray = Object.keys(sources)
if (!asts || (file === 0 && sourcesArray.length === 1)) {
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content)
} else {
for (var filename in asts) {
const source = asts[filename]
if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break
}
}
}
}
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Convert offset representation with line/column representation.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {string} content - source
*/
offsetToLineColumnWithContent (rawLocation, file, content) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content)
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Clear the cache
*/
clear () {
this.lineBreakPositionsByContent = {}
}
/**
* called by plugin API
*/
activate () {
this.on('solidity', 'compilationFinished', () => {
this.clear()
})
}
}

@ -222,10 +222,10 @@ export const CompilerApiMixin = (Base) => class extends Base {
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)
this.data.eventHandlers.onCompilerLoaded = (version) => {
this.data.eventHandlers.onCompilerLoaded = (version, license) => {
this.data.loading = false
this.statusChanged({ key: 'none' })
this.emit('compilerLoaded', version)
this.emit('compilerLoaded', version, license)
}
this.compiler.event.register('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)

@ -39,11 +39,14 @@ const App: React.FC = () => {
try {
await remixClient.loaded()
remixClient.onFileChange(name => setContract(name))
const name = await remixClient.getContractName()
setContract(name)
remixClient.onNoFileSelected(() => setContract(''))
} catch (err) {
console.log(err)
}
try {
const name = await remixClient.getContractName() // throw if no file are selected
setContract(name)
} catch (e) {}
}
start()
}, [])

@ -28,22 +28,60 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
/** Compile a Contract */
async function compileContract() {
try {
const _contract = await remixClient.getContract()
await remixClient.discardHighlight()
let _contract: any
try {
_contract = await remixClient.getContract()
} catch (e: any) {
setOutput('', { status: 'failed', message: e.message})
return
}
remixClient.changeStatus({
key: 'loading',
type: 'info',
title: 'Compiling'
})
const output = await compile(compilerUrl, _contract)
let output
try {
output = await compile(compilerUrl, _contract)
} catch (e: any) {
setOutput(_contract.name, { status: 'failed', message: e.message})
return
}
setOutput(_contract.name, output)
// ERROR
if (isCompilationError(output)) {
const line = output.line
if (line) {
const lineColumnPos = {
start: { line: line - 1 },
end: { line: line - 1 }
start: { line: line - 1, column: 10 },
end: { line: line - 1, column: 10 }
}
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
if (regex) {
let errorIndex = 0
regex.map((errorLocation) => {
const location = errorLocation.replace('line ', '').split(':')
let message = errors[errorIndex]
errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) {
try {
message = message.split('\n\n')[1]
} catch (e) {}
}
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)
}
})
}
}
throw new Error(output.message)
}
// SUCCESS
@ -61,7 +99,6 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
type: 'error',
title: err.message
})
console.error(err)
}
}

@ -26,7 +26,6 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
The url to your local compiler
</Form.Text>
</Form.Group>
</Form>

@ -7,7 +7,6 @@ import {
} from '../utils';
import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab'
import { Ballot } from '../examples/ballot';
import Button from 'react-bootstrap/Button';
import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard'
@ -17,14 +16,20 @@ interface VyperResultProps {
output?: VyperCompilationOutput;
}
export type ExampleContract = {
name: string,
address: string
}
function VyperResult({ output }: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi');
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi')
if (!output) return (
<div id="result">
<p>No contract compiled yet.</p>
<Button data-id="add-ballot" variant="info" onClick={() => remixClient.loadContract(Ballot)}>
Create Ballot.vy example
<Button data-id="add-repository" variant="info" onClick={() => remixClient.cloneVyperRepo()}>
Clone Vyper repository and play with the contract examples
</Button>
</div>
)
@ -33,7 +38,7 @@ function VyperResult({ output }: VyperResultProps) {
return (
<div id="result" className="error">
<i className="fas fa-exclamation-circle text-danger"></i>
<p data-id="error-message" className="alert alert-danger">{output.message}</p>
<pre data-id="error-message" className="alert alert-danger">{output.message}</pre>
</div>)
}
@ -41,7 +46,7 @@ function VyperResult({ output }: VyperResultProps) {
<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">Copy ABI</Button>
<Button variant="info" className="copy" data-id="copy-abi">Copy ABI</Button>
</CopyToClipboard>
<JSONTree src={output.abi} />
</Tab>

@ -1,161 +0,0 @@
export const Ballot = {
name: 'browser/ballot.vy',
content: `# Voting with delegation.
# Information about voters
struct Voter:
# weight is accumulated by delegation
weight: int128
# if true, that person already voted (which includes voting by delegating)
voted: bool
# person delegated to
delegate: address
# index of the voted proposal, which is not meaningful unless 'voted' is True.
vote: int128
# Users can create proposals
struct Proposal:
# short name (up to 32 bytes)
name: bytes32
# number of accumulated votes
voteCount: int128
voters: public(map(address, Voter))
proposals: public(map(int128, Proposal))
voterCount: public(int128)
chairperson: public(address)
int128Proposals: public(int128)
@public
@constant
def delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS
@public
@constant
def directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)
# Setup global variables
@public
def __init__(_proposalNames: bytes32[2]):
self.chairperson = msg.sender
self.voterCount = 0
for i in range(2):
self.proposals[i] = Proposal({
name: _proposalNames[i],
voteCount: 0
})
self.int128Proposals += 1
# Give a 'voter' the right to vote on this ballot.
# This may only be called by the 'chairperson'.
@public
def giveRightToVote(voter: address):
# Throws if the sender is not the chairperson.
assert msg.sender == self.chairperson
# Throws if the voter has already voted.
assert not self.voters[voter].voted
# Throws if the voter's voting weight isn't 0.
assert self.voters[voter].weight == 0
self.voters[voter].weight = 1
self.voterCount += 1
# Used by 'delegate' below, and can be called by anyone.
@public
def forwardWeight(delegate_with_weight_to_forward: address):
assert self.delegated(delegate_with_weight_to_forward)
# Throw if there is nothing to do:
assert self.voters[delegate_with_weight_to_forward].weight > 0
target: address = self.voters[delegate_with_weight_to_forward].delegate
for i in range(4):
if self.delegated(target):
target = self.voters[target].delegate
# The following effectively detects cycles of length <= 5,
# in which the delegation is given back to the delegator.
# This could be done for any int128ber of loops,
# or even infinitely with a while loop.
# However, cycles aren't actually problematic for correctness;
# they just result in spoiled votes.
# So, in the production version, this should instead be
# the responsibility of the contract's client, and this
# check should be removed.
assert target != delegate_with_weight_to_forward
else:
# Weight will be moved to someone who directly voted or
# hasn't voted.
break
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
self.voters[delegate_with_weight_to_forward].weight = 0
self.voters[target].weight += weight_to_forward
if self.directlyVoted(target):
self.proposals[self.voters[target].vote].voteCount += weight_to_forward
self.voters[target].weight = 0
# To reiterate: if target is also a delegate, this function will need
# to be called again, similarly to as above.
# Delegate your vote to the voter 'to'.
@public
def delegate(to: address):
# Throws if the sender has already voted
assert not self.voters[msg.sender].voted
# Throws if the sender tries to delegate their vote to themselves or to
# the default address value of 0x0000000000000000000000000000000000000000
# (the latter might not be problematic, but I don't want to think about it).
assert to != msg.sender
assert to != ZERO_ADDRESS
self.voters[msg.sender].voted = True
self.voters[msg.sender].delegate = to
# This call will throw if and only if this delegation would cause a loop
# of length <= 5 that ends up delegating back to the delegator.
self.forwardWeight(msg.sender)
# Give your vote (including votes delegated to you)
# to proposal 'proposals[proposal].name'.
@public
def vote(proposal: int128):
# can't vote twice
assert not self.voters[msg.sender].voted
# can only vote on legitimate proposals
assert proposal < self.int128Proposals
self.voters[msg.sender].vote = proposal
self.voters[msg.sender].voted = True
# transfer msg.sender's weight to proposal
self.proposals[proposal].voteCount += self.voters[msg.sender].weight
self.voters[msg.sender].weight = 0
# Computes the winning proposal taking all
# previous votes into account.
@public
@constant
def winningProposal() -> int128:
winning_vote_count: int128 = 0
winning_proposal: int128 = 0
for i in range(2):
if self.proposals[i].voteCount > winning_vote_count:
winning_vote_count = self.proposals[i].voteCount
winning_proposal = i
return winning_proposal
# Calls winningProposal() function to get the index
# of the winner contained in the proposals array and then
# returns the name of the winner
@public
@constant
def winnerName() -> bytes32:
return self.proposals[self.winningProposal()].name
`
}

@ -18,8 +18,8 @@ export interface VyperCompilationResult {
export interface VyperCompilationError {
status: 'failed'
column: number
line: number
column?: number
line?: number
message: string
}

@ -3,6 +3,7 @@ import { Api, Status } from '@remixproject/plugin-utils';
import { createClient } from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin';
import { Contract } from './compiler';
import { ExampleContract } from '../components/VyperResult';
export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this);
@ -14,34 +15,70 @@ export class RemixClient extends PluginClient {
/** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => {
if (!name) return
cb(name)
})
}
/** Emit an event when file changed */
async onNoFileSelected(cb: () => any) {
this.client.on('fileManager', 'noFileSelected', async () => {
cb()
})
}
/** Load Ballot contract example into the file manager */
async loadContract({name, content}: Contract) {
async loadContract({name, address}: ExampleContract) {
try {
await this.client.call('fileManager', 'setFile', name, content)
await this.client.call('fileManager', 'switchFile', name)
const content = await this.client.call('contentImport', 'resolve', address)
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content)
await this.client.call('fileManager', 'switchFile', content.cleanUrl)
} catch (err) {
console.log(err)
}
}
async cloneVyperRepo() {
try {
// @ts-ignore
this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider')
// @ts-ignore
await this.call('dGitProvider', 'clone', { url: 'https://github.com/vyperlang/vyper', token: null }, 'vyper-lang')
// @ts-ignore
this.call('notification', 'toast', 'Vyper repository cloned, the workspace Vyper has been created.')
} catch (e) {
// @ts-ignore
this.call('notification', 'toast', e.message)
}
}
/** Update the status of the plugin in remix */
changeStatus(status: Status) {
this.client.emit('statusChanged', status);
}
/** Highlight a part of the editor */
highlight(lineColumnPos: HighlightPosition, name: string, color: string) {
return this.client.call('editor', 'highlight', lineColumnPos, name, color)
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) {
await this.client.call('editor', 'highlight', lineColumnPos, name)
/*
column: -1
row: -1
text: "browser/Untitled1.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.↵"
type: "warning"
*/
const annotation = {
column: 0,
row: lineColumnPos.start.line,
type: 'error',
text: message
}
await this.client.call('editor', 'addAnnotation', annotation, name)
}
/** Remove current Hightlight */
discardHighlight() {
return this.client.call('editor', 'discardHighlight')
async discardHighlight() {
await this.client.call('editor', 'discardHighlight')
await this.client.call('editor', 'clearAnnotations')
}
/** Get the name of the current contract */

@ -1,5 +1,4 @@
'use strict'
import { each } from 'async'
import { ethers } from 'ethers'
import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
@ -34,7 +33,6 @@ export class TxListener {
_listenOnNetwork:boolean
_loopId
blocks
lastBlock
constructor (opt, executionContext) {
this.event = new EventManager()
@ -107,8 +105,7 @@ export class TxListener {
addExecutionCosts(txResult, tx, execResult)
tx.envMode = this.executionContext.getProvider()
tx.status = txResult.receipt.status // 0x0 or 0x1
this._resolve([tx], () => {
})
this._resolve([tx])
})
})
}
@ -123,9 +120,7 @@ export class TxListener {
if (this._loopId) {
clearInterval(this._loopId)
}
if (this._listenOnNetwork) {
this._startListenOnNetwork()
}
this._listenOnNetwork ? this.startListening() : this.stopListening()
}
/**
@ -133,7 +128,6 @@ export class TxListener {
*/
init () {
this.blocks = []
this.lastBlock = -1
}
/**
@ -164,34 +158,54 @@ export class TxListener {
this._isListening = false
}
_startListenOnNetwork () {
this._loopId = setInterval(() => {
async _startListenOnNetwork () {
let lastSeenBlock = this.executionContext.lastBlock?.number - 1
let processingBlock = false
const processBlocks = async () => {
if (!this._isListening) return
if (processingBlock) return
processingBlock = true
const currentLoopId = this._loopId
this.executionContext.web3().eth.getBlockNumber((error, blockNumber) => {
if (this._loopId === null) return
if (error) return console.log(error)
if (currentLoopId === this._loopId && blockNumber > this.lastBlock) {
let current = this.lastBlock + 1
this.lastBlock = blockNumber
while (blockNumber >= current) {
if (this._loopId === null) {
processingBlock = false
return
}
if (!lastSeenBlock) {
lastSeenBlock = this.executionContext.lastBlock?.number // trying to resynchronize
console.log('listen on blocks, resynchronising')
processingBlock = false
return
}
const current = this.executionContext.lastBlock?.number
if (!current) {
console.log(new Error('no last block found'))
processingBlock = false
return
}
if (currentLoopId === this._loopId && lastSeenBlock < current) {
while (lastSeenBlock <= current) {
try {
this._manageBlock(current)
if (!this._isListening) break
await this._manageBlock(lastSeenBlock)
} catch (e) {
console.log(e)
}
current++
lastSeenBlock++
}
lastSeenBlock = current
}
})
}, 2000)
processingBlock = false
}
_manageBlock (blockNumber) {
this.executionContext.web3().eth.getBlock(blockNumber, true, (error, result) => {
if (!error) {
this._newBlock(Object.assign({ type: 'web3' }, result))
this._loopId = setInterval(processBlocks, 20000)
processBlocks()
}
})
async _manageBlock (blockNumber) {
try {
const result = await this.executionContext.web3().eth.getBlock(blockNumber, true)
return await this._newBlock(Object.assign({ type: 'web3' }, result))
} catch (e) {}
}
/**
@ -215,31 +229,37 @@ export class TxListener {
return this._resolvedTransactions[txHash]
}
_newBlock (block) {
async _newBlock (block) {
this.blocks.push(block)
this._resolve(block.transactions, () => {
await this._resolve(block.transactions)
this.event.trigger('newBlock', [block])
})
}
_resolve (transactions, callback) {
each(transactions, (tx, cb) => {
_resolveAsync (tx) {
return new Promise((resolve, reject) => {
this._api.resolveReceipt(tx, (error, receipt) => {
if (error) return cb(error)
if (error) return reject(error)
this._resolveTx(tx, receipt, (error, resolvedData) => {
if (error) cb(error)
if (error) return reject(error)
if (resolvedData) {
this.event.trigger('txResolved', [tx, receipt, resolvedData])
}
this.event.trigger('newTransaction', [tx, receipt])
cb()
resolve({})
})
})
}, () => {
callback()
})
}
async _resolve (transactions) {
for (const tx of transactions) {
try {
if (!this._isListening) break
await this._resolveAsync(tx)
} catch (e) {}
}
}
_resolveTx (tx, receipt, cb) {
const contracts = this._api.contracts()
if (!contracts) return cb()

@ -375,5 +375,7 @@ export const ConsoleLogs = {
3982404743: '(address,address,address,uint)',
4161329696: '(address,address,address,string)',
238520724: '(address,address,address,bool)',
1717301556: '(address,address,address,address)'
1717301556: '(address,address,address,address)',
4133908826: '(uint,uint)',
3054400204: '(string,uint)'
}

@ -33,7 +33,8 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
}
self.postMessage({
cmd: 'versionLoaded',
data: compiler.version()
data: compiler.version(),
license: compiler.license()
})
break
}

@ -25,6 +25,7 @@ export class Compiler {
compileJSON: null,
worker: null,
currentVersion: null,
compilerLicense: null,
optimize: false,
runs: 200,
evmVersion: null,
@ -94,9 +95,10 @@ export class Compiler {
* @param version compiler version
*/
onCompilerLoaded (version: string): void {
onCompilerLoaded (version: string, license: string): void {
this.state.currentVersion = version
this.event.trigger('compilerLoaded', [version])
this.state.compilerLicense = license
this.event.trigger('compilerLoaded', [version, license])
}
/**
@ -131,7 +133,7 @@ export class Compiler {
}
this.onCompilationFinished(result, missingInputs, source, input, this.state.currentVersion)
}
this.onCompilerLoaded(compiler.version())
this.onCompilerLoaded(compiler.version(), compiler.license())
}
}
@ -184,6 +186,7 @@ export class Compiler {
if (err) {
console.error('Error in loading remote solc compiler: ', err)
} else {
let license
this.state.compileJSON = (source: SourceWithTarget) => {
const missingInputs: string[] = []
const missingInputsCallback = (path: string) => {
@ -203,13 +206,14 @@ export class Compiler {
}
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
license = remoteCompiler.license()
}
} catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
}
this.onCompilationFinished(result, missingInputs, source, input, version)
}
this.onCompilerLoaded(version)
this.onCompilerLoaded(version, license)
}
})
}
@ -273,7 +277,7 @@ export class Compiler {
const data: MessageFromWorker = msg.data
switch (data.cmd) {
case 'versionLoaded':
if (data.data) this.onCompilerLoaded(data.data)
if (data.data && data.license) this.onCompilerLoaded(data.data, data.license)
break
case 'compiled':
{

@ -158,6 +158,7 @@ export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
currentVersion: string| null| undefined,
compilerLicense: string| null
optimize: boolean,
runs: number
evmVersion: EVMVersion| null,
@ -186,6 +187,7 @@ export interface MessageToWorker {
export interface MessageFromWorker {
cmd: string,
license?: string,
job?: number,
missingInputs?: string[],
input?: any,

@ -134,7 +134,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
if (runs) compiler.set('runs', runs)
if (currentCompilerUrl) {
compiler.loadRemoteVersion(currentCompilerUrl)
compiler.event.register('compilerLoaded', this, function (version) {
compiler.event.register('compilerLoaded', this, function (version, license) {
next()
})
} else {
@ -198,7 +198,7 @@ export function compileContractSources (sources: SrcIfc, newCompConfig: any, imp
compiler.set('runs', runs)
compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
compiler.event.register('compilerLoaded', this, (version) => {
compiler.event.register('compilerLoaded', this, (version, license) => {
next()
})
} else {

@ -17,3 +17,7 @@
.validationError {
overflow-wrap: break-word;
}
.debuggerPanels {
overflow-y: scroll;
height: fit-content;
}

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' // eslint-disable-line
import React, { useState, useEffect, useRef } from 'react' // eslint-disable-line
import TxBrowser from './tx-browser/tx-browser' // eslint-disable-line
import StepManager from './step-manager/step-manager' // eslint-disable-line
import VmDebugger from './vm-debugger/vm-debugger' // eslint-disable-line
@ -36,6 +36,28 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
sourceLocationStatus: ''
})
const panelsRef = useRef<HTMLDivElement>(null)
const debuggerTopRef = useRef(null)
const handleResize = () => {
if (panelsRef.current && debuggerTopRef.current) {
panelsRef.current.style.height = (window.innerHeight - debuggerTopRef.current.clientHeight) - debuggerTopRef.current.offsetTop - 7 +'px'
}
}
useEffect(() => {
handleResize()
}, [])
useEffect(() => {
window.addEventListener('resize', handleResize)
// TODO: not a good way to wait on the ref doms element to be rendered of course
setTimeout(() =>
handleResize(), 2000)
return () => window.removeEventListener('resize', handleResize)
}, [state.debugging, state.isActive])
useEffect(() => {
return unLoad()
}, [])
@ -288,6 +310,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
}
}, 300)
handleResize()
}
const debug = (txHash, web3?) => {
@ -315,17 +338,18 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
traceLength: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.traceLength : null,
registerEvent: state.debugger && state.debugger.step_manager ? state.debugger.step_manager.event.register.bind(state.debugger.step_manager.event) : null
}
const vmDebugger = {
registerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.register.bind(state.debugger.vmDebuggerLogic.event) : null,
triggerEvent: state.debugger && state.debugger.vmDebuggerLogic ? state.debugger.vmDebuggerLogic.event.trigger.bind(state.debugger.vmDebuggerLogic.event) : null
}
return (
<div>
<Toaster message={state.toastMessage} />
<div className="px-2">
<div className="px-2" ref={debuggerTopRef}>
<div>
<p className="my-2 debuggerLabel">Debugger Configuration</p>
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<div className="mb-2 debuggerConfig custom-control custom-checkbox" title="Using Generated Sources lets you step into compiler outputs while debugging.">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithGeneratedSources: checked } }
@ -333,7 +357,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}} type="checkbox" title="Debug with generated sources" />
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (Solidity {'>='} v0.7.2)</label>
</div>
{ state.isLocalNodeUsed && <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugWithLocalNodeInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } }
@ -356,10 +380,12 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
</span>
</div> }
{ state.debugging && <StepManager stepManager={ stepManager } /> }
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> }
</div>
<div className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> }
{ state.debugging && <VmDebugger vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> }
</div>
</div>
)
}

@ -98,7 +98,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } })
}, [registerEvent])
return (
<div id='vmheadView' className="mt-1 px-0">
<div id='vmheadView' className="mt-1 px-2">
<div className='d-flex flex-column'>
<div className='w-100'>
<FunctionPanel data={functionPanel} />

@ -9,6 +9,8 @@ import { IMarkdownString } from 'monaco-editor'
import './remix-ui-editor.css'
import { loadTypes } from './web-types'
import monaco from '../types/monaco'
import { MarkerSeverity } from 'monaco-editor'
type cursorPosition = {
startLineNumber: number,
@ -60,6 +62,22 @@ export type lineText = {
hoverMessage: IMarkdownString | IMarkdownString[]
}
type errorMarker = {
message: string
severity: MarkerSeverity
position: {
start: {
line: number
column: number
},
end: {
line: number
column: number
}
},
file: string
}
loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
export type DecorationsReturn = {
@ -91,6 +109,8 @@ export interface EditorUIProps {
addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
addErrorMarker: (errors: []) => void
clearErrorMarkers: (sources: string[] | {[fileName: string]: any}) => void
}
}
@ -381,6 +401,57 @@ export const EditorUI = (props: EditorUIProps) => {
return addDecoration(marker, filePath, typeOfDecoration)
}
props.editorAPI.addErrorMarker = async (errors: errorMarker[]) => {
const allMarkersPerfile: Record<string, Array<monaco.editor.IMarkerData>> = {}
for (const error of errors) {
let filePath = error.file
if (!filePath) return
const fileFromUrl = await props.plugin.call('fileManager', 'getPathFromUrl', filePath)
filePath = fileFromUrl.file
const model = editorModelsState[filePath]?.model
const errorServerityMap = {
'error': MarkerSeverity.Error,
'warning': MarkerSeverity.Warning,
'info': MarkerSeverity.Info
}
if (model) {
const markerData: monaco.editor.IMarkerData = {
severity: errorServerityMap[error.severity],
startLineNumber: ((error.position.start && error.position.start.line) || 0),
startColumn: ((error.position.start && error.position.start.column) || 0),
endLineNumber: ((error.position.end && error.position.end.line) || 0),
endColumn: ((error.position.end && error.position.end.column) || 0),
message: error.message,
}
if (!allMarkersPerfile[filePath]) {
allMarkersPerfile[filePath] = []
}
allMarkersPerfile[filePath].push(markerData)
}
}
for (const filePath in allMarkersPerfile) {
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', allMarkersPerfile[filePath])
}
}
}
props.editorAPI.clearErrorMarkers = async (sources: string[] | {[fileName: string]: any}) => {
if (sources) {
for (const source of (Array.isArray(sources) ? sources : Object.keys(sources))) {
const filePath = source
const model = editorModelsState[filePath]?.model
if (model) {
monacoRef.current.editor.setModelMarkers(model, 'remix-solidity', [])
}
}
}
}
props.editorAPI.findMatches = (uri: string, value: string) => {
if (!editorRef.current) return
const model = editorModelsState[uri]?.model
@ -435,7 +506,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
function handleEditorDidMount (editor) {
function handleEditorDidMount(editor) {
editorRef.current = editor
defineAndSetTheme(monacoRef.current)
reducerListener(props.plugin, dispatch, monacoRef.current, editorRef.current, props.events)
@ -495,7 +566,7 @@ export const EditorUI = (props: EditorUIProps) => {
}
}
function handleEditorWillMount (monaco) {
function handleEditorWillMount(monaco) {
monacoRef.current = monaco
// Register a new language
monacoRef.current.languages.register({ id: 'remix-solidity' })
@ -523,7 +594,7 @@ export const EditorUI = (props: EditorUIProps) => {
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
options={{ glyphMargin: true, readOnly: true}}
options={{ glyphMargin: true, readOnly: true }}
defaultValue={defaultEditorValue}
/>
<div className="contextview">
@ -531,9 +602,9 @@ export const EditorUI = (props: EditorUIProps) => {
hide={false}
gotoLine={(line, column) => props.plugin.call('editor', 'gotoLine', line, column)}
openFile={(file) => props.plugin.call('fileManager', 'switchFile', file)}
getLastCompilationResult={() => { return props.plugin.call('compilerArtefacts', 'getLastCompilationResult') } }
offsetToLineColumn={(position, file, sources, asts) => { return props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) } }
getCurrentFileName={() => { return props.plugin.call('fileManager', 'file') } }
getLastCompilationResult={() => { return props.plugin.call('compilerArtefacts', 'getLastCompilationResult') }}
offsetToLineColumn={(position, file, sources, asts) => { return props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) }}
getCurrentFileName={() => { return props.plugin.call('fileManager', 'file') }}
onContextListenerChanged={(listener) => { props.plugin.on('contextualListener', 'contextChanged', listener) }}
onCurrentFileChanged={(listener) => { props.plugin.on('fileManager', 'currentFileChanged', listener) }}
referencesOf={(node: astNode) => { return props.plugin.call('contextualListener', 'referencesOf', node) }}

@ -1,4 +1,5 @@
export * from './lib/remix-ui-helper'
export * from './lib/bleach'
export * from './lib/helper-components'
export * from './lib/components/PluginViewWrapper'
export * from './lib/components/custom-dropdown'

@ -5,7 +5,7 @@
*/
import * as he from 'he'
const remixBleach = {
export const bleach = {
matcher: /<\/?([a-zA-Z0-9]+)*(.*?)\/?>/igm,
@ -24,7 +24,7 @@ const remixBleach = {
let match
// extract all tags
while ((match = remixBleach.matcher.exec(html)) != null) {
while ((match = bleach.matcher.exec(html)) != null) {
const attrr = match[2].split(' ')
const attrs = []
@ -45,7 +45,7 @@ const remixBleach = {
if (attr.name) attrs.push(attr)
})
var tag = {
const tag = {
full: match[0],
name: match[1],
attr: attrs
@ -57,14 +57,13 @@ const remixBleach = {
return matches
},
sanitize: function (html, options) {
sanitize: function (html, options = { mode: 'white', list: bleach.whitelist, encode_entities: false}) {
html = String(html) || ''
options = options || {}
const mode = options.mode || 'white'
const list = options.list || remixBleach.whitelist
const list = options.list || bleach.whitelist
var matches = remixBleach.analyze(html)
const matches = bleach.analyze(html)
if ((mode === 'white' && list.indexOf('script') === -1) ||
(mode === 'black' && list.indexOf('script') !== -1)) {
@ -95,5 +94,3 @@ const remixBleach = {
return html
}
}
module.exports = remixBleach

@ -8,6 +8,7 @@
.remixModalBody {
overflow-y: auto;
max-height: 600px;
white-space: pre-line;
}
@-webkit-keyframes animatetop {
from {top: -300px; opacity: 0}

@ -27,8 +27,8 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => {
return (
<header className='d-flex flex-column'>
<div className="swapitHeader px-3 pt-2 pb-0 d-flex flex-row">
<h6 className="mb-2" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="mt-2 d-flex flex-row">
<h6 className="mb-3" data-id='sidePanelSwapitTitle'>{plugin?.profile.displayName || plugin?.profile.name}</h6>
<div className="d-flex flex-row">
<div className="d-flex flex-row">
{plugin?.profile?.maintainedBy?.toLowerCase() === "remix" && (<i aria-hidden="true" className="text-success mt-1 px-1 fas fa-check" title="Maintained by Remix"></i>)}
</div>

@ -15,7 +15,7 @@ export function RemixPluginPanel (props: RemixPanelProps) {
<>
{props.header}
<div className="pluginsContainer">
<div className='plugins' id='plugins'>
<div className='plugins pb-1' id='plugins'>
{Object.values(props.plugins).map((pluginRecord) => {
return <RemixUIPanelPlugin key={pluginRecord.profile.name} pluginRecord={pluginRecord} />
})}

@ -1,5 +1,5 @@
import { ContractData } from "@remix-project/core-plugin"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue, setTxFeeContent } from "./payload"
import { addNewInstance, addProvider, clearAllInstances, clearRecorderCount, hidePopUp, removeExistingInstance, removeProvider, setBaseFeePerGas, setConfirmSettings, setCurrentContract, setExecutionEnvironment, setExternalEndpoint, setGasLimit, setGasPrice, setGasPriceStatus, setMatchPassphrase, setMaxFee, setMaxPriorityFee, setNetworkName, setPassphrase, setPathToScenario, setSelectedAccount, setSendUnit, setSendValue } from "./payload"
export const setAccount = (dispatch: React.Dispatch<any>, account: string) => {
dispatch(setSelectedAccount(account))
@ -65,10 +65,6 @@ export const updateGasPrice = (dispatch: React.Dispatch<any>, price: string) =>
dispatch(setGasPrice(price))
}
export const updateTxFeeContent = (dispatch: React.Dispatch<any>, content: string) => {
dispatch(setTxFeeContent(content))
}
export const addInstance = (dispatch: React.Dispatch<any>, instance: { contractData?: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> }) => {
instance.decodedResponse = {}
dispatch(addNewInstance(instance))

@ -5,7 +5,7 @@ import { resetAndInit, setupEvents } from './events'
import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account'
import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt,
setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit,
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions'
updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath } from './actions'
import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance } from './deploy'
import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts'
import { ContractData, FuncABI } from "@remix-project/core-plugin"
@ -50,7 +50,6 @@ export const setGasPrice = (price: string) => updateGasPrice(dispatch, price)
export const setGasPriceStatus = (status: boolean) => updateGasPriceStatus(dispatch, status)
export const setMaxFee = (fee: string) => updateMaxFee(dispatch, fee)
export const setMaxPriorityFee = (fee: string) => updateMaxPriorityFee(dispatch, fee)
export const setTxFeeContent = (content: string) => updateTxFeeContent(dispatch, content)
export const removeInstances = () => clearInstances(dispatch)
export const removeSingleInstance = (index: number) => removeInstance(dispatch, index)
export const getExecutionContext = () => getContext(plugin)

@ -1,6 +1,6 @@
import { ContractList } from '../reducers/runTab'
import { ContractData } from '@remix-project/core-plugin'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT } from '../constants'
import { ADD_DEPLOY_OPTION, ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_DEPLOY_OPTION, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_PROXY_ENV_ADDRESS, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE } from '../constants'
import { DeployMode, DeployOptions } from '../types'
export const fetchAccountsListRequest = () => {
@ -216,13 +216,6 @@ export const setGasPrice = (price: string) => {
}
}
export const setTxFeeContent = (content: string) => {
return {
type: SET_TX_FEE_CONTENT,
payload: content
}
}
export const addNewInstance = (instance: { contractData?: ContractData, address: string, name: string, abi?: any }) => {
return {
type: ADD_INSTANCE,

@ -28,6 +28,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const [constructorInterface, setConstructorInterface] = useState<FuncABI>(null)
const [constructorInputs, setConstructorInputs] = useState(null)
const contractsRef = useRef<HTMLSelectElement>(null)
const atAddressValue = useRef<HTMLInputElement>(null)
const { contractList, loadType, currentFile, currentContract, compilationCount, deployOptions, proxyKey } = props.contracts
useEffect(() => {
@ -52,7 +53,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
}, [loadedAddress])
useEffect(() => {
if (/.(.abi)$/.exec(currentFile)) {
if (/.(.abi)$/.exec(currentFile) && "" !== atAddressValue.current.value) {
setAbiLabel({
display: 'block',
content: currentFile
@ -175,7 +176,6 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
const atAddressChanged = (event) => {
const value = event.target.value
if (!value) {
enableAtAddress(false)
} else {
@ -282,6 +282,7 @@ export function ContractDropdownUI (props: ContractDropdownProps) {
<div className="udapp_button udapp_atAddressSect">
<button className="udapp_atAddress btn btn-sm btn-info" id="runAndDeployAtAdressButton" disabled={atAddressOptions.disabled} title={atAddressOptions.title} onClick={loadFromAddress}>At Address</button>
<input
ref={atAddressValue}
className="udapp_input udapp_ataddressinput ataddressinput form-control"
placeholder="Load contract from Address"
title="address of contract"

@ -6,13 +6,14 @@ import { MainnetProps } from '../types'
export function MainnetPrompt (props: MainnetProps) {
const [baseFee, setBaseFee] = useState<string>('')
const [transactionFee, setTransactionFee] = useState<string>('')
useEffect(() => {
props.init((txFeeText, gasPriceValue, gasPriceStatus) => {
if (txFeeText) props.setTxFeeContent(txFeeText)
if (txFeeText) setTransactionFee(txFeeText)
if (gasPriceValue) onGasPriceChange(gasPriceValue)
if (props.network && props.network.lastBlock && props.network.lastBlock.baseFeePerGas) {
const baseFee = Web3.utils.fromWei(Web3.utils.toBN(parseInt(props.network.lastBlock.baseFeePerGas, 16)), 'Gwei')
const baseFee = Web3.utils.fromWei(Web3.utils.toBN(props.network.lastBlock.baseFeePerGas), 'Gwei')
setBaseFee(baseFee)
onMaxFeeChange(baseFee)
@ -24,8 +25,8 @@ export function MainnetPrompt (props: MainnetProps) {
const onMaxFeeChange = (value: string) => {
const maxFee = value
// @ts-ignore
if (parseInt(props.network.lastBlock.baseFeePerGas, 16) > Web3.utils.toWei(maxFee, 'Gwei')) {
props.setTxFeeContent('Transaction is invalid. Max fee should not be less than Base fee')
if (Web3.utils.toBN(props.network.lastBlock.baseFeePerGas).gt(Web3.utils.toBN(Web3.utils.toWei(maxFee, 'Gwei')))) {
setTransactionFee('Transaction is invalid. Max fee should not be less than Base fee')
props.updateGasPriceStatus(false)
props.updateConfirmSettings(true)
return
@ -35,7 +36,7 @@ export function MainnetPrompt (props: MainnetProps) {
}
props.setNewGasPrice(maxFee, (txFeeText, priceStatus) => {
props.setTxFeeContent(txFeeText)
setTransactionFee(txFeeText)
if (priceStatus) {
props.updateConfirmSettings(false)
} else {
@ -51,7 +52,7 @@ export function MainnetPrompt (props: MainnetProps) {
const gasPrice = value
props.setNewGasPrice(gasPrice, (txFeeText, priceStatus) => {
props.setTxFeeContent(txFeeText)
setTransactionFee(txFeeText)
props.updateGasPriceStatus(priceStatus)
props.updateGasPrice(gasPrice)
})
@ -105,7 +106,7 @@ export function MainnetPrompt (props: MainnetProps) {
</div>
<div className="align-items-center my-1" title="Represents the maximum amount of fee that you will pay for this transaction. The minimun needs to be set to base fee.">
<div className='d-flex'>
<span className="text-dark mr-2 text-nowrap">Max fee (Not less than base fee {Web3.utils.fromWei(Web3.utils.toBN(parseInt(props.network.lastBlock.baseFeePerGas, 16)), 'Gwei')} Gwei):</span>
<span className="text-dark mr-2 text-nowrap">Max fee (Not less than base fee {Web3.utils.fromWei(Web3.utils.toBN(props.network.lastBlock.baseFeePerGas), 'Gwei')} Gwei):</span>
<input className="form-control mr-1 text-right" style={{ height: '1.2rem', width: '6rem' }} id='maxfee' onInput={(e: any) => onMaxFeeChange(e.target.value)} defaultValue={baseFee} />
<span>Gwei</span>
<span className="text-dark ml-2"></span>
@ -120,7 +121,7 @@ export function MainnetPrompt (props: MainnetProps) {
}
<div className="mb-3">
<span className="text-dark mr-2">Max transaction fee:</span>
<span className="text-warning" id='txfee'>{ props.txFeeContent }</span>
<span className="text-warning" id='txfee'>{ transactionFee }</span>
</div>
</div>
<div className="d-flex py-1 align-items-center custom-control custom-checkbox remixui_checkbox">

@ -32,7 +32,6 @@ export const SET_MAX_FEE = 'SET_MAX_FEE'
export const SET_MAX_PRIORITY_FEE = 'SET_MAX_PRIORITY_FEE'
export const SET_BASE_FEE_PER_GAS = 'SET_BASE_FEE_PER_GAS'
export const SET_GAS_PRICE = 'SET_GAS_PRICE'
export const SET_TX_FEE_CONTENT = 'SET_TX_FEE_CONTENT'
export const ADD_INSTANCE = 'ADD_INSTANCE'
export const REMOVE_INSTANCE = 'REMOVE_INSTANCE'
export const CLEAR_INSTANCES = 'CLEAR_INSTANCES'

@ -1,7 +1,7 @@
import { CompilerAbstract } from '@remix-project/remix-solidity-ts'
import { ContractData } from '@remix-project/core-plugin'
import { DeployOptions } from '../types'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_TX_FEE_CONTENT, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION } from '../constants'
import { ADD_INSTANCE, ADD_PROVIDER, CLEAR_INSTANCES, CLEAR_RECORDER_COUNT, DISPLAY_NOTIFICATION, DISPLAY_POPUP_MESSAGE, FETCH_ACCOUNTS_LIST_FAILED, FETCH_ACCOUNTS_LIST_REQUEST, FETCH_ACCOUNTS_LIST_SUCCESS, FETCH_CONTRACT_LIST_FAILED, FETCH_CONTRACT_LIST_REQUEST, FETCH_CONTRACT_LIST_SUCCESS, FETCH_PROVIDER_LIST_FAILED, FETCH_PROVIDER_LIST_REQUEST, FETCH_PROVIDER_LIST_SUCCESS, HIDE_NOTIFICATION, HIDE_POPUP_MESSAGE, REMOVE_INSTANCE, REMOVE_PROVIDER, RESET_STATE, SET_BASE_FEE_PER_GAS, SET_CONFIRM_SETTINGS, SET_CURRENT_CONTRACT, SET_CURRENT_FILE, SET_DECODED_RESPONSE, SET_DEPLOY_OPTIONS, SET_EXECUTION_ENVIRONMENT, SET_EXTERNAL_WEB3_ENDPOINT, SET_GAS_LIMIT, SET_GAS_PRICE, SET_GAS_PRICE_STATUS, SET_IPFS_CHECKED_STATE, SET_LOAD_TYPE, SET_MATCH_PASSPHRASE, SET_MAX_FEE, SET_MAX_PRIORITY_FEE, SET_NETWORK_NAME, SET_PASSPHRASE, SET_PATH_TO_SCENARIO, SET_PERSONAL_MODE, SET_RECORDER_COUNT, SET_SELECTED_ACCOUNT, SET_SEND_UNIT, SET_SEND_VALUE, SET_PROXY_ENV_ADDRESS, ADD_DEPLOY_OPTION, REMOVE_DEPLOY_OPTION } from '../constants'
declare const window: any
interface Action {
@ -82,7 +82,6 @@ export interface RunTabState {
maxFee: string,
maxPriorityFee: string,
baseFeePerGas: string,
txFeeContent: string,
gasPrice: string,
instances: {
instanceList: {
@ -171,7 +170,6 @@ export const runTabInitialState: RunTabState = {
maxFee: '',
maxPriorityFee: '1',
baseFeePerGas: '',
txFeeContent: '',
gasPrice: '',
instances: {
instanceList: [],
@ -582,15 +580,6 @@ export const runTabReducer = (state: RunTabState = runTabInitialState, action: A
}
}
case SET_TX_FEE_CONTENT: {
const payload: string = action.payload
return {
...state,
txFeeContent: payload
}
}
case ADD_INSTANCE: {
const payload: { contractData: ContractData, address: string, name: string, abi?: any, decodedResponse?: Record<number, any> } = action.payload

@ -21,7 +21,7 @@ import {
setBaseFeePerGas, setConfirmSettings,
setGasPrice, setGasPriceStatus,
setMaxFee, setMaxPriorityFee,
setTxFeeContent, removeInstances,
removeInstances,
removeSingleInstance, getExecutionContext,
executeTransactions, loadFromAddress,
storeNewScenario, runScenario,
@ -189,8 +189,6 @@ export function RunTabUI (props: RunTabProps) {
updateGasPriceStatus={setGasPriceStatus}
updateMaxFee={setMaxFee}
updateMaxPriorityFee={setMaxPriorityFee}
setTxFeeContent={setTxFeeContent}
txFeeContent={runTab.txFeeContent}
maxFee={runTab.maxFee}
maxPriorityFee={runTab.maxPriorityFee}
/>

@ -270,10 +270,8 @@ export interface MainnetProps {
updateMaxFee: (fee: string) => void,
updateBaseFeePerGas: (fee: string) => void,
init: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void,
setTxFeeContent: (content: string) => void,
updateGasPrice: (price: string) => void,
updateMaxPriorityFee: (fee: string) => void
txFeeContent: string,
maxFee: string,
maxPriorityFee: string
}

@ -9,9 +9,9 @@ export function GithubSettings (props: GithubSettingsProps) {
useEffect(() => {
if (props.config) {
const githubToken = props.config.get('settings/gist-access-token')
const githubUserName = props.config.get('settings/github-user-name')
const githubEmail = props.config.get('settings/github-email')
const githubToken = props.config.get('settings/gist-access-token') || ''
const githubUserName = props.config.get('settings/github-user-name') || ''
const githubEmail = props.config.get('settings/github-email') || ''
setGithubToken(githubToken)
setGithubUsername(githubUserName)

@ -214,7 +214,7 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{ labels[type].link }</a></p>
<div className=""><label>TOKEN:</label>
<div className="text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] } />
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] || '' } />
<div className="d-flex justify-content-end pt-2">
<CopyToClipboard content={tokenValue[type]} data-id='copyToClipboardCopyIcon' />
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken(type)} value="Save" type="button" disabled={tokenValue === ''}></input>

@ -46,8 +46,8 @@ export const listenToEvents = (compileTabLogic: CompileTabLogic, api) => (dispat
dispatch(setCompilerMode('loadingCompiler'))
})
compileTabLogic.compiler.event.register('compilerLoaded', () => {
dispatch(setCompilerMode('compilerLoaded'))
compileTabLogic.compiler.event.register('compilerLoaded', (version, license) => {
dispatch(setCompilerMode('compilerLoaded', version, license))
})
compileTabLogic.compiler.event.register('compilationFinished', (success, data, source, input, version) => {

@ -48,6 +48,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
timeout: 300,
allversions: [],
customVersions: [],
compilerLicense: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.8.7+commit.e28d00a7.js', // this default version is defined: in makeMockCompiler (for browser test)
runs: '',
@ -185,7 +186,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
loadingCompiler()
break
case 'compilerLoaded':
compilerLoaded()
compilerLoaded(compilerContainer.compiler.args[1])
break
case 'compilationFinished':
compilationFinished()
@ -432,14 +433,20 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
if (!compileIcon.current) return
compileIcon.current.setAttribute('title', 'compiler is loading, please wait a few moments.')
compileIcon.current.classList.add('remixui_spinningIcon')
setState(prevState => {
return { ...prevState, compilerLicense: 'Compiler is loading. License will be displayed once compiler is loaded'}
})
_updateLanguageSelector()
setDisableCompileButton(true)
}
const compilerLoaded = () => {
const compilerLoaded = (license) => {
if (!compileIcon.current) return
compileIcon.current.setAttribute('title', '')
compileIcon.current.classList.remove('remixui_spinningIcon')
setState(prevState => {
return { ...prevState, compilerLicense: license ? license : 'Could not retreive license for selected compiler version' }
})
if (state.autoCompile) compile()
const isDisabled = !compiledFileName || (compiledFileName && !isSolFileSelected(compiledFileName))
@ -554,6 +561,10 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
modal('Add a custom compiler', promptMessage('URL'), 'OK', addCustomCompiler, 'Cancel', () => {})
}
const showCompilerLicense = () => {
modal('Compiler License', state.compilerLicense ? state.compilerLicense : 'License not available', 'OK', () => {})
}
const promptMessage = (message) => {
return (
<>
@ -701,10 +712,9 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
<article>
<div className='pt-0 remixui_compilerSection'>
<div className="mb-1">
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">
Compiler
<button className="far fa-plus btn-light border-0 p-0 mx-2 btn-sm" onClick={promptCompiler} title="Add a custom compiler with URL"></button>
</label>
<label className="remixui_compilerLabel form-check-label" htmlFor="versionSelector">Compiler</label>
<span className="far fa-plus border-0 p-0 ml-3" onClick={() => promptCompiler()} title="Add a custom compiler with URL"></span>
<span className="fa fa-file-text-o border-0 p-0 ml-2" onClick={() => showCompilerLicense()} title="See compiler license"></span>
<select value={ state.selectedVersion || state.defaultVersion } onChange={(e) => handleLoadVersion(e.target.value) } className="custom-select" id="versionSelector" disabled={state.allversions.length <= 0}>
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === state.defaultVersion ? 'selected' : ''}>{ state.defaultVersion }</option> }
{ state.allversions.length <= 0 && <option disabled data-id={state.selectedVersion === 'builtin' ? 'selected' : ''}>builtin</option> }

@ -164,7 +164,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
testTab.fileManager.events.on('currentFileChanged', async (file: string) => {
await updateForNewCurrent(file)
})
testTab.on('solidity', 'compilerLoaded', async (version: string) => {
testTab.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
const { currentVersion } = testTab.compileTab.getCurrentCompilerConfig()
if (!semver.gt(truncateVersion(currentVersion), '0.4.12')) {

@ -140,8 +140,8 @@ export const RemixUiStaticAnalyser = (props: RemixUiStaticAnalyserProps) => {
setSlitherEnabled(false)
}
})
props.analysisModule.on('solidity', 'compilerLoaded', async (version: string) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props.analysisModule.on('solidity', 'compilerLoaded', async (version: string, license: string) => {
setDisableForRun(version)
})
return () => { }

@ -115,7 +115,7 @@ export const initListeningOnNetwork = (plugins, dispatch: React.Dispatch<any>) =
plugins.txListener.event.register(NEW_BLOCK, (block) => {
if (!block.transactions || (block.transactions && !block.transactions.length)) {
dispatch({ type: EMPTY_BLOCK, payload: { message: 0, provider } })
dispatch({ type: EMPTY_BLOCK, payload: { message: block.number, provider } })
}
})
plugins.txListener.event.register(KNOWN_TRANSACTION, () => {

@ -86,8 +86,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
scriptRunnerDispatch({ type: 'html', payload: { message: [html ? html.innerText ? html.innerText : html : null] } })
},
log: (message, type) => {
scriptRunnerDispatch({ type: type ? type : 'log', payload: { message: [message] } })
log: (message) => {
scriptRunnerDispatch({ type: 'log', payload: { message: [message] } })
}
})
}, [])
@ -461,6 +461,7 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
className="pt-1 form-check-label custom-control-label text-nowrap"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
htmlFor="listenNetworkCheck"
data-id="listenNetworkCheckInput"
>
listen on all transactions
</label>
@ -549,6 +550,11 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
<div className="px-4 block" data-id="block" key={i}><span className={x.style}>{ msg }</span></div>
)
} else if (typeof msg === 'object') {
if (msg.value && isHtml(msg.value)) {
return (
<div className={classNameBlock} data-id="block" key={i}><span className={x.style}>{ parse(msg.value) } </span></div>
)
}
let stringified
try {
stringified = JSON.stringify(msg)
@ -609,4 +615,13 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => {
)
}
function isHtml (value) {
if (!value.indexOf) return false
return value.indexOf('<div') !== -1
|| value.indexOf('<span') !== -1
|| value.indexOf('<p') !== -1
|| value.indexOf('<label') !== -1
|| value.indexOf('<b') !== -1
}
export default RemixUiTerminal

@ -1,5 +1,7 @@
import { checkSpecialChars } from '@remix-ui/helper'
import { BadgeStatus, IconStatus } from '../components/Icon'
import { bleach } from '@remix-ui/helper'
export type IconBadgeReducerAction = {
readonly type: string
@ -20,13 +22,13 @@ function setIconStatus(name: string, status: IconStatus) {
if (typeof status.key === 'number') {
key = status.key
text = key
} else key = checkSpecialChars(status.key) ? '' : status.key
} else key = checkSpecialChars(status.key) ? bleach.sanitize(status.key) : status.key
let thisType = ''
if (status.type === 'error') {
thisType = 'danger' // to use with bootstrap
} else thisType = checkSpecialChars(status.type) ? '' : status.type!
const title = checkSpecialChars(status.title) ? '' : status.title
} else thisType = checkSpecialChars(status.type) ? bleach.sanitize(status.type) : status.type
const title = checkSpecialChars(status.title) ? bleach.sanitize(status.title) : status.title
const pluginName = status.pluginName
return { title, type: thisType, key, text, pluginName }
}

@ -209,6 +209,6 @@ const rootFolderChanged = async (path) => {
}
const setFileDecorators = async (items: fileDecoration[], cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
await dispatch(setFileDecorationSuccess(items))
dispatch && await dispatch(setFileDecorationSuccess(items))
cb && cb(null, true)
}

Loading…
Cancel
Save