diff --git a/apps/debugger/src/app/debugger-api.ts b/apps/debugger/src/app/debugger-api.ts index e1b66a3d48..0f99518e78 100644 --- a/apps/debugger/src/app/debugger-api.ts +++ b/apps/debugger/src/app/debugger-api.ts @@ -111,8 +111,7 @@ export const DebuggerApiMixin = (Base) => class extends Base { } return null }, - debugWithGeneratedSources: false, - fork: 'berlin' + debugWithGeneratedSources: false }) return await debug.debugger.traceManager.getTrace(hash) } diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index b44b17746e..aa6d507120 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -121,7 +121,8 @@ const stateCheck = { chairperson: { value: '0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C', type: 'address', - constant: false + constant: false, + immutable: false }, voters: { value: { @@ -148,7 +149,8 @@ const stateCheck = { } }, type: 'mapping(address => struct Ballot.Voter)', - constant: false + constant: false, + immutable: false }, proposals: { value: [ @@ -168,7 +170,8 @@ const stateCheck = { ], length: '0x1', type: 'struct Ballot.Proposal[]', - constant: false + constant: false, + immutable: false } } diff --git a/apps/remix-ide-e2e/src/tests/debugger.spec.ts b/apps/remix-ide-e2e/src/tests/debugger.spec.ts index 78406a7b8d..0baeee65b3 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.spec.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.spec.ts @@ -228,7 +228,7 @@ module.exports = { .waitForElementVisible('*[data-id="solidityLocals"]', 60000) .pause(10000) .checkVariableDebug('soliditylocals', { num: { value: '2', type: 'uint256' } }) - .checkVariableDebug('soliditystate', { number: { value: '0', type: 'uint256', constant: false } }) + .checkVariableDebug('soliditystate', { number: { value: '0', type: 'uint256', constant: false, immutable: false } }) .end() } } diff --git a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts index 9c605afec0..b79d2e5054 100644 --- a/apps/remix-ide-e2e/src/tests/generalSettings.test.ts +++ b/apps/remix-ide-e2e/src/tests/generalSettings.test.ts @@ -18,6 +18,7 @@ module.exports = { 'Should activate `generate contract metadata`': function (browser) { browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) .waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000) + .verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked') .click('*[data-id="verticalIconsFileExplorerIcons"]') .click('[data-id="treeViewLitreeViewItemcontracts"]') .openFile('contracts/3_Ballot.sol') @@ -39,22 +40,26 @@ module.exports = { .click('*[data-id="verticalIconsKindsettings"]') .setValue('*[data-id="settingsTabGistAccessToken"]', '**********') .click('*[data-id="settingsTabSaveGistToken"]') - .waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) - .assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Access token has been saved') + .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) + .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token has been saved') + .pause(3000) }, 'Should copy github access token to clipboard': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000) .click('*[data-id="copyToClipboardCopyIcon"]') - .waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) - .assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Copied value to clipboard.') + .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) + // .waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1) , 5000) + // .assert.containsText('*[data-shared="tooltipPopup"]', 'Copied value to clipboard.') + // .assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Copied value to clipboard.') }, 'Should remove github access token': function (browser: NightwatchBrowser) { browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000) + .pause(1000) .click('*[data-id="settingsTabRemoveGistToken"]') - .waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) - .assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Access token removed') + .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000) + .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token removed') .assert.containsText('*[data-id="settingsTabGistAccessToken"]', '') }, diff --git a/apps/remix-ide-e2e/src/tests/pluginManager.spec.ts b/apps/remix-ide-e2e/src/tests/pluginManager.spec.ts index bd2884e4e5..f87bf1269e 100644 --- a/apps/remix-ide-e2e/src/tests/pluginManager.spec.ts +++ b/apps/remix-ide-e2e/src/tests/pluginManager.spec.ts @@ -5,7 +5,7 @@ import init from '../helpers/init' const testData = { pluginName: 'remixIde', pluginDisplayName: 'Remix IDE', - pluginUrl: 'https://zokrates-remix-plugin.netlify.app/' + pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/' } module.exports = { diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.ts index 20455df8b3..a138bc77d1 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.ts @@ -32,6 +32,8 @@ module.exports = { 'Should sign message using account key': function (browser: NightwatchBrowser) { browser.waitForElementPresent('*[data-id="settingsRemixRunSignMsg"]') + .click('select[id="selectExEnvOptions"] option[value="vm-berlin"]') + .pause(2000) .click('*[data-id="settingsRemixRunSignMsg"]') .pause(2000) .waitForElementPresent('*[data-id="modalDialogCustomPromptText"]') diff --git a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts index e1d5ebb82f..d06c1c1c9d 100644 --- a/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts +++ b/apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts @@ -148,9 +148,14 @@ module.exports = { .click('.instance:nth-of-type(3) > div > button') .clickFunction('g - transact (not payable)') .journalLastChildIncludes('Error provided by the contract:') - .journalLastChildIncludes('CustomError') + .journalLastChildIncludes('CustomError : error description') .journalLastChildIncludes('Parameters:') - .journalLastChildIncludes('2,3,error_string_2') + .journalLastChildIncludes('"value": "2",') + .journalLastChildIncludes('"value": "3",') + .journalLastChildIncludes('"value": "error_string_2",') + .journalLastChildIncludes('"documentation": "param1"') + .journalLastChildIncludes('"documentation": "param2"') + .journalLastChildIncludes('"documentation": "param3"') .journalLastChildIncludes('Debug the transaction to get more information.') }, @@ -163,9 +168,14 @@ module.exports = { .click('.instance:nth-of-type(2) > div > button') .clickFunction('g - transact (not payable)') .journalLastChildIncludes('Error provided by the contract:') - .journalLastChildIncludes('CustomError') + .journalLastChildIncludes('CustomError : error description') .journalLastChildIncludes('Parameters:') - .journalLastChildIncludes('2,3,error_string_2') + .journalLastChildIncludes('"value": "2",') + .journalLastChildIncludes('"value": "3",') + .journalLastChildIncludes('"value": "error_string_2",') + .journalLastChildIncludes('"documentation": "param1"') + .journalLastChildIncludes('"documentation": "param2"') + .journalLastChildIncludes('"documentation": "param3"') .journalLastChildIncludes('Debug the transaction to get more information.') .end() } @@ -256,6 +266,10 @@ contract C { pragma solidity ^0.8.4; + /// error description + /// @param a param1 + /// @param b param2 + /// @param c param3 error CustomError(uint a, uint b, string c); contract C { function f() public pure { diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 82177860d3..4374ab3a0d 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -34,7 +34,7 @@ const modalDialogCustom = require('../ui/modal-dialog-custom') const profile = { name: 'filePanel', displayName: 'File explorers', - methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace'], + methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem'], events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'], icon: 'assets/img/fileManager.webp', description: ' - ', @@ -60,9 +60,11 @@ module.exports = class Filepanel extends ViewPlugin { this.gitHandle = new GitHandle() this.hardhatHandle = new HardhatHandle() this.registeredMenuItems = [] + this.removedMenuItems = [] this.request = {} this.workspaces = [] this.initialWorkspace = null + this.appManager = appManager } render () { @@ -88,6 +90,7 @@ module.exports = class Filepanel extends ViewPlugin { request={this.request} workspaces={this.workspaces} registeredMenuItems={this.registeredMenuItems} + removedMenuItems={this.removedMenuItems} initialWorkspace={this.initialWorkspace} /> , this.el) @@ -101,8 +104,22 @@ module.exports = class Filepanel extends ViewPlugin { if (!item) throw new Error('Invalid register context menu argument') if (!item.name || !item.id) throw new Error('Item name and id is mandatory') if (!item.type && !item.path && !item.extension && !item.pattern) throw new Error('Invalid file matching criteria provided') - + if (this.registeredMenuItems.filter((o) => { + return o.id === item.id && o.name === item.name + }).length) throw new Error(`Action ${item.name} already exists on ${item.id}`) this.registeredMenuItems = [...this.registeredMenuItems, item] + this.removedMenuItems = this.removedMenuItems.filter(menuItem => item.id !== menuItem.id) + this.renderComponent() + } + + removePluginActions (plugin) { + this.registeredMenuItems = this.registeredMenuItems.filter((item) => { + if (item.id !== plugin.name || item.sticky === true) return true + else { + this.removedMenuItems.push(item) + return false + } + }) this.renderComponent() } @@ -163,6 +180,9 @@ module.exports = class Filepanel extends ViewPlugin { } return } + + const self = this + this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this)) // insert example contracts if there are no files to show return new Promise((resolve, reject) => { this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { diff --git a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js b/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js index dfb0cfc5a4..00cf79233d 100644 --- a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js +++ b/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js @@ -7,6 +7,7 @@ const addTooltip = require('../../ui/tooltip') const semver = require('semver') const modalDialogCustom = require('../../ui/modal-dialog-custom') const css = require('../styles/compile-tab-styles') +const _paq = window._paq = window._paq || [] class CompilerContainer { constructor (compileTabLogic, editor, config, queryParams) { @@ -111,6 +112,7 @@ class CompilerContainer { this._view.compileIcon.setAttribute('title', 'idle') this._view.compileIcon.classList.remove(`${css.spinningIcon}`) this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) + _paq.push(['trackEvent', 'compiler', `compiled_with_v_${this._retrieveVersion()}`]) }) } diff --git a/apps/remix-ide/src/app/tabs/hardhat-provider.js b/apps/remix-ide/src/app/tabs/hardhat-provider.js index 037117fa85..805bc37e08 100644 --- a/apps/remix-ide/src/app/tabs/hardhat-provider.js +++ b/apps/remix-ide/src/app/tabs/hardhat-provider.js @@ -17,11 +17,13 @@ export default class HardhatProvider extends Plugin { constructor (blockchain) { super(profile) this.provider = null + this.blocked = false // used to block any call when trying to recover after a failed connection. this.blockchain = blockchain } onDeactivation () { this.provider = null + this.blocked = false } hardhatProviderDialogBody () { @@ -39,6 +41,8 @@ export default class HardhatProvider extends Plugin { sendAsync (data) { return new Promise((resolve, reject) => { + if (this.blocked) return reject(new Error('provider unable to connect')) + // If provider is not set, allow to open modal only when provider is trying to connect if (!this.provider) { modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { this.provider = new Web3.providers.HttpProvider(target) @@ -54,9 +58,16 @@ export default class HardhatProvider extends Plugin { sendAsyncInternal (data, resolve, reject) { if (this.provider) { - this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, (error, message) => { + // Check the case where current environment is VM on UI and it still sends RPC requests + // This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!' + if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!')) + this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, async (error, message) => { if (error) { + this.blocked = true + modalDialogCustom.alert('Hardhat Provider', `Error while connecting to the hardhat provider: ${error.message}`) + await this.call('udapp', 'setEnvironmentMode', 'vm') this.provider = null + setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm return reject(error) } resolve(message) diff --git a/apps/remix-ide/src/app/tabs/settings-tab.js b/apps/remix-ide/src/app/tabs/settings-tab.js index 3ef4d2953d..8c3889a02b 100644 --- a/apps/remix-ide/src/app/tabs/settings-tab.js +++ b/apps/remix-ide/src/app/tabs/settings-tab.js @@ -1,12 +1,10 @@ +import React from 'react' // eslint-disable-line import { ViewPlugin } from '@remixproject/engine-web' +import ReactDOM from 'react-dom' import * as packageJson from '../../../../../package.json' -const yo = require('yo-yo') +import { RemixUiSettings } from '@remix-ui/settings' //eslint-disable-line const globalRegistry = require('../../global/registry') -const tooltip = require('../ui/tooltip') -const copyToClipboard = require('../ui/copy-to-clipboard') const EventManager = require('../../lib/events') -const css = require('./styles/settings-tab-styles') -const _paq = window._paq = window._paq || [] const profile = { name: 'settings', @@ -51,210 +49,27 @@ module.exports = class SettingsTab extends ViewPlugin { textWrapLabel: null } /* eslint-enable */ this.event = new EventManager() + this.element = document.createElement('div') + this.element.setAttribute('id', 'settingsTab') } - createThemeCheckies () { - const themes = this._deps.themeModule.getThemes() - const onswitchTheme = (event, name) => { - this._deps.themeModule.switchTheme(name) - } - if (themes) { - return yo`
- ${themes.map((aTheme) => { - const el = yo`
- { onswitchTheme(event, aTheme.name) }} class="align-middle custom-control-input" name="theme" id="${aTheme.name}" data-id="settingsTabTheme${aTheme.name}"> - -
` - if (this._deps.themeModule.active === aTheme.name) el.querySelector('input').setAttribute('checked', 'checked') - return el - })} -
` - } + onActivation () { + this.renderComponent() } render () { - const self = this - if (self._view.el) return self._view.el - - // Gist settings - const token = this.config.get('settings/gist-access-token') - const gistAccessToken = yo`` - if (token) gistAccessToken.value = token - const removeToken = () => { self.config.set('settings/gist-access-token', ''); gistAccessToken.value = ''; tooltip('Access token removed') } - const saveToken = () => { - this.config.set('settings/gist-access-token', gistAccessToken.value) - tooltip('Access token has been saved. RELOAD the page to apply it.') - } - const gistAddToken = yo` saveToken()} value="Save" type="button">` - const gistRemoveToken = yo`` - this._view.gistToken = yo` -
- ${gistAccessToken} -
- ${copyToClipboard(() => gistAccessToken.value)}${gistAddToken}${gistRemoveToken} -
-

- - Please reload Remix after having saved the token. -

-
- ` - this._view.optionVM = yo`` - this._view.optionVMLabel = yo`` - if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true) - if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '') - elementStateChanged(self._view.optionVMLabel, !this.config.get('settings/always-use-vm')) - - this._view.textWrap = yo`` - this._view.textWrapLabel = yo`` - if (this.config.get('settings/text-wrap')) this._view.textWrap.setAttribute('checked', '') - elementStateChanged(self._view.textWrapLabel, !this.config.get('settings/text-wrap')) - - const warnText = `Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it. - This mode allows to provide the passphrase in the Remix interface without having to unlock the account. - Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). - Remix never persist any passphrase.`.split('\n').map(s => s.trim()).join(' ') - - this._view.personal = yo`` - this._view.warnPersonalMode = yo`` - this._view.personalLabel = yo`` - if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '') - elementStateChanged(self._view.personalLabel, !this.config.get('settings/personal-mode')) - - this._view.useMatomoAnalytics = yo`` - this._view.useMatomoAnalyticsLabel = yo` - - ` - if (this.config.get('settings/matomo-analytics')) { - this._view.useMatomoAnalytics.setAttribute('checked', '') - _paq.push(['forgetUserOptOut']) - // @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used - document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' - } else { - _paq.push(['optUserOut']) - } - elementStateChanged(self._view.useMatomoAnalyticsLabel, !this.config.get('settings/matomo-analytics')) - - this._view.generateContractMetadata = yo`` - this._view.generateContractMetadataLabel = yo`` - if (this.config.get('settings/generate-contract-metadata') === undefined) this.config.set('settings/generate-contract-metadata', true) - if (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '') - elementStateChanged(self._view.generateContractMetadataLabel, !this.config.get('settings/generate-contract-metadata')) - - this._view.pluginInput = yo`` - - this._view.themes = this._deps.themeModule.getThemes() - this._view.themesCheckBoxes = this.createThemeCheckies() - - this._view.config.general = yo` -
-
-
General settings
-
- ${this._view.generateContractMetadata} - ${this._view.generateContractMetadataLabel} -
-
- ${this._view.optionVM} - ${this._view.optionVMLabel} -
-
- ${this._view.textWrap} - ${this._view.textWrapLabel} -
-
- ${this._view.personal} - ${this._view.personalLabel} -
-
- ${this._view.useMatomoAnalytics} - ${this._view.useMatomoAnalyticsLabel} -
-
-
- ` - this._view.gistToken = yo` -
-
-
Github Access Token
-

Manage the access token used to publish to Gist and retrieve Github contents.

-

Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only 'create gist' permission.

-

https://github.com/settings/tokens

-
${this._view.gistToken}
-
-
` - this._view.config.themes = yo` -
-
-
Themes
- ${this._view.themesCheckBoxes} -
-
` - this._view.el = yo` -
- ${this._view.config.general} - ${this._view.gistToken} - ${this._view.config.themes} -
` - - function onchangeGenerateContractMetadata (event) { - const isChecked = self.config.get('settings/generate-contract-metadata') - - self.config.set('settings/generate-contract-metadata', !isChecked) - elementStateChanged(self._view.generateContractMetadataLabel, isChecked) - } - - function onchangeOption (event) { - const isChecked = self.config.get('settings/always-use-vm') - - self.config.set('settings/always-use-vm', !isChecked) - elementStateChanged(self._view.optionVMLabel, isChecked) - } - - function textWrapEvent (event) { - const isChecked = self.config.get('settings/text-wrap') - - self.config.set('settings/text-wrap', !isChecked) - elementStateChanged(self._view.textWrapLabel, isChecked) - self.editor.resize(!isChecked) - } - - function onchangePersonal (event) { - const isChecked = self.config.get('settings/personal-mode') - - self.config.set('settings/personal-mode', !isChecked) - elementStateChanged(self._view.personalLabel, isChecked) - } - - function onchangeMatomoAnalytics (event) { - const isChecked = self.config.get('settings/matomo-analytics') - - self.config.set('settings/matomo-analytics', !isChecked) - elementStateChanged(self._view.useMatomoAnalyticsLabel, isChecked) - if (event.target.checked) { - _paq.push(['forgetUserOptOut']) - // @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used - document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;' - } else { - _paq.push(['optUserOut']) - } - } - - function elementStateChanged (el, isChanged) { - if (isChanged) { - el.classList.remove('text-dark') - el.classList.add('text-secondary') - } else { - el.classList.add('text-dark') - el.classList.remove('text-secondary') - } - } + return this.element + } - this._deps.themeModule.switchTheme() - return this._view.el + renderComponent () { + ReactDOM.render( + , + this.element + ) } getGithubAccessToken () { diff --git a/apps/remix-ide/src/app/ui/universal-dapp-ui.js b/apps/remix-ide/src/app/ui/universal-dapp-ui.js index 17fe7058c8..217217503f 100644 --- a/apps/remix-ide/src/app/ui/universal-dapp-ui.js +++ b/apps/remix-ide/src/app/ui/universal-dapp-ui.js @@ -44,14 +44,14 @@ UniversalDAppUI.prototype.renderInstance = function (contract, address, contract noInstances.parentNode.removeChild(noInstances) } const abi = txHelper.sortAbiFunction(contract.abi) - return this.renderInstanceFromABI(abi, address, contractName) + return this.renderInstanceFromABI(abi, address, contractName, contract) } // TODO this function was named before "appendChild". // this will render an instance: contract name, contract address, and all the public functions // basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed // this returns a DOM element -UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) { +UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName, contract) { const self = this address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') address = ethJSUtil.toChecksumAddress(address) @@ -117,7 +117,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address funABI: funABI, address: address, contractABI: contractABI, - contractName: contractName + contractName: contractName, + contract })) }) @@ -255,6 +256,7 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i args.contractName, args.contractABI, args.funABI, + args.contract, inputsValues, args.address, params, diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index f3dc841646..2b156e2a9b 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -249,7 +249,7 @@ class Blockchain { return txlistener } - runOrCallContractMethod (contractName, contractAbi, funABI, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { + runOrCallContractMethod (contractName, contractAbi, funABI, contract, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) { // contractsDetails is used to resolve libraries txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => { if (error) { @@ -265,6 +265,7 @@ class Blockchain { if (data) { data.contractName = contractName data.contractABI = contractAbi + data.contract = contract } const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure' this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => { @@ -490,7 +491,7 @@ class Blockchain { if (execResult) { // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value. returnValue = execResult ? execResult.returnValue : toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000') - const vmError = txExecution.checkVMError(execResult, args.data.contractABI) + const vmError = txExecution.checkVMError(execResult, args.data.contractABI, args.data.contract) if (vmError.error) { return cb(vmError.message) } diff --git a/apps/remix-ide/src/blockchain/execution-context.js b/apps/remix-ide/src/blockchain/execution-context.js index 352a689df3..f29d0ec0d4 100644 --- a/apps/remix-ide/src/blockchain/execution-context.js +++ b/apps/remix-ide/src/blockchain/execution-context.js @@ -1,6 +1,7 @@ /* global ethereum */ 'use strict' import Web3 from 'web3' +import { execution } from '@remix-project/remix-lib' import EventManager from '../lib/events' let web3 @@ -21,8 +22,7 @@ export class ExecutionContext { this.executionContext = null this.blockGasLimitDefault = 4300000 this.blockGasLimit = this.blockGasLimitDefault - this.defaultFork = 'berlin' - this.currentFork = this.defaultFork + this.currentFork = 'berlin' this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3' this.customNetWorks = {} this.blocks = {} @@ -123,21 +123,18 @@ export class ExecutionContext { this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null) } - executionContextChange (value, endPointUrl, confirmCb, infoCb, cb) { + async executionContextChange (value, endPointUrl, confirmCb, infoCb, cb) { const context = value.context - const fork = value.fork || this.defaultFork if (!cb) cb = () => {} if (!confirmCb) confirmCb = () => {} if (!infoCb) infoCb = () => {} if (context === 'vm') { this.executionContext = context - this.currentFork = fork + this.currentFork = value.fork this.event.trigger('contextChanged', ['vm']) return cb() } - this.currentFork = this.defaultFork // in the case of injected and web3, we default to the last fork. - if (context === 'injected') { if (injectedProvider === undefined) { infoCb('No injected Web3 provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).') @@ -146,7 +143,7 @@ export class ExecutionContext { this.askPermission() this.executionContext = context web3.setProvider(injectedProvider) - this._updateBlockGasLimit() + await this._updateChainContext() this.event.trigger('contextChanged', ['injected']) return cb() } @@ -173,22 +170,29 @@ export class ExecutionContext { this.listenOnLastBlockId = null } - _updateBlockGasLimit () { + async _updateChainContext () { if (this.getProvider() !== 'vm') { - web3.eth.getBlock('latest', (err, block) => { - if (!err) { - // we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506 - this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault - } else { - this.blockGasLimit = this.blockGasLimitDefault + try { + const block = await web3.eth.getBlock('latest') + // we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506 + this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault + try { + this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number) + } catch (e) { + this.currentFork = 'berlin' + console.log(`unable to detect fork, defaulting to ${this.currentFork}..`) + console.error(e) } - }) + } catch (e) { + console.error(e) + this.blockGasLimit = this.blockGasLimitDefault + } } } listenOnLastBlock () { this.listenOnLastBlockId = setInterval(() => { - this._updateBlockGasLimit() + this._updateChainContext() }, 15000) } @@ -202,7 +206,7 @@ export class ExecutionContext { web3.eth.net.isListening((err, isConnected) => { if (!err && isConnected === true) { this.executionContext = context - this._updateBlockGasLimit() + this._updateChainContext() this.event.trigger('contextChanged', [context]) this.event.trigger('web3EndpointChanged') cb() diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 26f684e432..45bc5a7421 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -14,7 +14,7 @@ const requiredModules = [ // services + layout views + system views const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd) export function isNative (name) { - const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity'] + const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider'] return nativePlugins.includes(name) || requiredModules.includes(name) } diff --git a/libs/remix-debug/src/Ethdebugger.ts b/libs/remix-debug/src/Ethdebugger.ts index 4c023c6028..3c492e1812 100644 --- a/libs/remix-debug/src/Ethdebugger.ts +++ b/libs/remix-debug/src/Ethdebugger.ts @@ -41,7 +41,7 @@ export class Ethdebugger { this.opts = opts this.event = new EventManager() - this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork }) + this.traceManager = new TraceManager({ web3: this.web3 }) this.codeManager = new CodeManager(this.traceManager) this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) }) this.storageResolver = null @@ -55,7 +55,7 @@ export class Ethdebugger { } setManagers () { - this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork }) + this.traceManager = new TraceManager({ web3: this.web3 }) this.codeManager = new CodeManager(this.traceManager) this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) }) this.storageResolver = null diff --git a/libs/remix-debug/src/code/codeManager.ts b/libs/remix-debug/src/code/codeManager.ts index fff30cba15..f0b38ea3d9 100644 --- a/libs/remix-debug/src/code/codeManager.ts +++ b/libs/remix-debug/src/code/codeManager.ts @@ -145,15 +145,38 @@ export class CodeManager { }) } - private retrieveIndexAndTrigger (codeMananger, address, step, code) { + private async retrieveIndexAndTrigger (codeMananger, address, step, code) { let result + let next + const returnInstructionIndexes = [] + const outOfGasInstructionIndexes = [] + try { result = codeMananger.getInstructionIndex(address, step) + next = codeMananger.getInstructionIndex(address, step + 1) + + let values = this.traceManager.getAllStopIndexes() + if (values) { + for (const value of values) { + if (value.address === address) { + returnInstructionIndexes.push({ instructionIndex: this.getInstructionIndex(address, value.index), address }) + } + } + } + + values = this.traceManager.getAllOutofGasIndexes() + if (values) { + for (const value of values) { + if (value.address === address) { + outOfGasInstructionIndexes.push({ instructionIndex: this.getInstructionIndex(address, value.index), address }) + } + } + } } catch (error) { return console.log(error) } try { - codeMananger.event.trigger('changed', [code, address, result]) + codeMananger.event.trigger('changed', [code, address, result, next, returnInstructionIndexes, outOfGasInstructionIndexes]) } catch (e) { console.log('dispatching event failed', e) } diff --git a/libs/remix-debug/src/debugger/VmDebugger.ts b/libs/remix-debug/src/debugger/VmDebugger.ts index 2a90d0ed2c..72a131cfd6 100644 --- a/libs/remix-debug/src/debugger/VmDebugger.ts +++ b/libs/remix-debug/src/debugger/VmDebugger.ts @@ -59,8 +59,8 @@ export class VmDebuggerLogic { } listenToCodeManagerEvents () { - this._codeManager.event.register('changed', (code, address, index) => { - this.event.trigger('codeManagerChanged', [code, address, index]) + this._codeManager.event.register('changed', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => { + this.event.trigger('codeManagerChanged', [code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes]) }) } diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 76d28746bd..11395026b3 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -26,8 +26,7 @@ export class Debugger { this.debugger = new Ethdebugger({ web3: options.web3, debugWithGeneratedSources: options.debugWithGeneratedSources, - compilationResult: this.compilationResult, - fork: options.fork + compilationResult: this.compilationResult }) const { traceManager, callTree, solidityProxy } = this.debugger diff --git a/libs/remix-debug/src/solidity-decoder/decodeInfo.ts b/libs/remix-debug/src/solidity-decoder/decodeInfo.ts index 5384b1af86..d506e5ca3e 100644 --- a/libs/remix-debug/src/solidity-decoder/decodeInfo.ts +++ b/libs/remix-debug/src/solidity-decoder/decodeInfo.ts @@ -336,7 +336,9 @@ function computeOffsets (types, stateDefinitions, contractName, location) { console.log('unable to retrieve decode info of ' + variable.typeDescriptions.typeString) return null } - if (!variable.constant && storagelocation.offset + type.storageBytes > 32) { + const immutable = variable.mutability === 'immutable' + const hasStorageSlots = !immutable && !variable.constant + if (hasStorageSlots && storagelocation.offset + type.storageBytes > 32) { storagelocation.slot++ storagelocation.offset = 0 } @@ -344,12 +346,13 @@ function computeOffsets (types, stateDefinitions, contractName, location) { name: variable.name, type: type, constant: variable.constant, + immutable, storagelocation: { - offset: variable.constant ? 0 : storagelocation.offset, - slot: variable.constant ? 0 : storagelocation.slot + offset: !hasStorageSlots ? 0 : storagelocation.offset, + slot: !hasStorageSlots ? 0 : storagelocation.slot } }) - if (!variable.constant) { + if (hasStorageSlots) { if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) { storagelocation.offset += type.storageBytes } else { diff --git a/libs/remix-debug/src/solidity-decoder/stateDecoder.ts b/libs/remix-debug/src/solidity-decoder/stateDecoder.ts index 0e27ac740c..b7908da3c3 100644 --- a/libs/remix-debug/src/solidity-decoder/stateDecoder.ts +++ b/libs/remix-debug/src/solidity-decoder/stateDecoder.ts @@ -15,9 +15,13 @@ export async function decodeState (stateVars, storageResolver) { try { const decoded = await stateVar.type.decodeFromStorage(stateVar.storagelocation, storageResolver) decoded.constant = stateVar.constant + decoded.immutable = stateVar.immutable if (decoded.constant) { decoded.value = '' } + if (decoded.immutable) { + decoded.value = '' + } ret[stateVar.name] = decoded } catch (e) { console.log(e) diff --git a/libs/remix-debug/src/trace/traceAnalyser.ts b/libs/remix-debug/src/trace/traceAnalyser.ts index ae8957b5ac..74651b228d 100644 --- a/libs/remix-debug/src/trace/traceAnalyser.ts +++ b/libs/remix-debug/src/trace/traceAnalyser.ts @@ -49,6 +49,17 @@ export class TraceAnalyser { this.traceCache.pushReturnValue(index, returnParamsObj) } + if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step) || traceHelper.isRevertInstruction(step)) { + this.traceCache.pushStopIndex(index, this.traceCache.currentCall.call.address) + } + + try { + if (parseInt(step.gas) - parseInt(step.gasCost) <= 0 || step.error === 'OutOfGas') { + this.traceCache.pushOutOfGasIndex(index, this.traceCache.currentCall.call.address) + } + } catch (e) { + console.error(e) + } } buildCalldata (index, step, tx, newContext) { diff --git a/libs/remix-debug/src/trace/traceCache.ts b/libs/remix-debug/src/trace/traceCache.ts index 7c2d381ccc..411e8e617c 100644 --- a/libs/remix-debug/src/trace/traceCache.ts +++ b/libs/remix-debug/src/trace/traceCache.ts @@ -5,6 +5,8 @@ const { sha3_256 } = util export class TraceCache { returnValues + stopIndexes + outofgasIndexes currentCall callsTree callsData @@ -24,6 +26,8 @@ export class TraceCache { // ...Changes contains index in the vmtrace of the corresponding changes this.returnValues = {} + this.stopIndexes = [] + this.outofgasIndexes = [] this.currentCall = null this.callsTree = null this.callsData = {} @@ -59,7 +63,7 @@ export class TraceCache { this.currentCall.call.reverted = reverted } var parent = this.currentCall.parent - this.currentCall = parent ? { call: parent.call, parent: parent.parent } : null + if (parent) this.currentCall = { call: parent.call, parent: parent.parent } return } const call = { @@ -78,6 +82,14 @@ export class TraceCache { this.currentCall = { call: call, parent: this.currentCall } } + pushOutOfGasIndex (index, address) { + this.outofgasIndexes.push({ index, address }) + } + + pushStopIndex (index, address) { + this.stopIndexes.push({ index, address }) + } + pushReturnValue (step, value) { this.returnValues[step] = value } diff --git a/libs/remix-debug/src/trace/traceManager.ts b/libs/remix-debug/src/trace/traceManager.ts index d3f3861e0f..848b3c57aa 100644 --- a/libs/remix-debug/src/trace/traceManager.ts +++ b/libs/remix-debug/src/trace/traceManager.ts @@ -1,9 +1,9 @@ 'use strict' +import { util, execution } from '@remix-project/remix-lib' import { TraceAnalyser } from './traceAnalyser' import { TraceCache } from './traceCache' import { TraceStepManager } from './traceStepManager' import { isCreateInstruction } from './traceHelper' -import { util } from '@remix-project/remix-lib' export class TraceManager { web3 @@ -17,7 +17,6 @@ export class TraceManager { constructor (options) { this.web3 = options.web3 - this.fork = options.fork this.isLoading = false this.trace = null this.traceCache = new TraceCache() @@ -37,6 +36,15 @@ export class TraceManager { if (result['structLogs'].length > 0) { this.trace = result['structLogs'] + try { + const networkId = await this.web3.eth.net.getId() + this.fork = execution.forkAt(networkId, tx.blockNumber) + } catch (e) { + this.fork = 'berlin' + console.log(`unable to detect fork, defaulting to ${this.fork}..`) + console.error(e) + } + this.traceAnalyser.analyse(result['structLogs'], tx) this.isLoading = false return true @@ -201,6 +209,14 @@ export class TraceManager { return this.trace[stepIndex].pc } + getAllStopIndexes () { + return this.traceCache.stopIndexes + } + + getAllOutofGasIndexes () { + return this.traceCache.outofgasIndexes + } + getReturnValue (stepIndex) { try { this.checkRequestedStep(stepIndex) diff --git a/libs/remix-lib/src/execution/forkAt.ts b/libs/remix-lib/src/execution/forkAt.ts new file mode 100644 index 0000000000..5c4316f868 --- /dev/null +++ b/libs/remix-lib/src/execution/forkAt.ts @@ -0,0 +1,121 @@ +'use strict' + +/** + * returns the fork name for the @argument networkId and @argument blockNumber + * + * @param {Object} networkId - network Id (1 for VM, 3 for Ropsten, 4 for Rinkeby, 5 for Goerli) + * @param {Object} blockNumber - block number + * @return {String} - fork name (Berlin, Istanbul, ...) + */ +export function forkAt (networkId, blockNumber) { + if (forks[networkId]) { + let currentForkName = forks[networkId][0].name + for (const fork of forks[networkId]) { + if (blockNumber >= fork.number) { + currentForkName = fork.name + } + } + return currentForkName + } + return 'berlin' +} + +// see https://github.com/ethereum/go-ethereum/blob/master/params/config.go +const forks = { + 1: [ + { + number: 4370000, + name: 'byzantium' + }, + { + number: 7280000, + name: 'constantinople' + }, + { + number: 7280000, + name: 'petersburg' + }, + { + number: 9069000, + name: 'istanbul' + }, + { + number: 9200000, + name: 'muirglacier' + }, + { + number: 12244000, + name: 'berlin' + } + ], + 3: [ + { + number: 1700000, + name: 'byzantium' + }, + { + number: 4230000, + name: 'constantinople' + }, + { + number: 4939394, + name: 'petersburg' + }, + { + number: 6485846, + name: 'istanbul' + }, + { + number: 7117117, + name: 'muirglacier' + }, + { + number: 9812189, + name: 'berlin' + }, + { + number: 10499401, + name: 'london' + } + ], + 4: [ + { + number: 1035301, + name: 'byzantium' + }, + { + number: 3660663, + name: 'constantinople' + }, + { + number: 4321234, + name: 'petersburg' + }, + { + number: 5435345, + name: 'istanbul' + }, + { + number: 8290928, + name: 'berlin' + }, + { + number: 8897988, + name: 'london' + } + ], + 5: [ + { + number: 1561651, + name: 'istanbul' + }, + { + number: 4460644, + name: 'berlin' + }, + { + number: 5062605, + name: 'london' + } + ] +} diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts index 639151c781..792175a48c 100644 --- a/libs/remix-lib/src/execution/txExecution.ts +++ b/libs/remix-lib/src/execution/txExecution.ts @@ -57,7 +57,7 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner, * @param {Object} execResult - execution result given by the VM * @return {Object} - { error: true/false, message: DOMNode } */ -export function checkVMError (execResult, abi) { +export function checkVMError (execResult, abi, contract) { const errorCode = { OUT_OF_GAS: 'out of gas', STACK_UNDERFLOW: 'stack underflow', @@ -92,7 +92,7 @@ export function checkVMError (execResult, abi) { const returnDataHex = returnData.slice(0, 4).toString('hex') let customError if (abi) { - let decodedCustomErrorInputs + let decodedCustomErrorInputsClean for (const item of abi) { if (item.type === 'error') { // ethers doesn't crash anymore if "error" type is specified, but it doesn't extract the errors. see: @@ -104,16 +104,36 @@ export function checkVMError (execResult, abi) { if (!sign) continue if (returnDataHex === sign.replace('0x', '')) { customError = item.name - decodedCustomErrorInputs = fn.decodeFunctionData(fn.getFunction(item.name), returnData) + const functionDesc = fn.getFunction(item.name) + // decoding error parameters + const decodedCustomErrorInputs = fn.decodeFunctionData(functionDesc, returnData) + decodedCustomErrorInputsClean = {} + let devdoc = {} + // "contract" reprensents the compilation result containing the NATSPEC documentation + if (contract && fn.functions && Object.keys(fn.functions).length) { + const functionSignature = Object.keys(fn.functions)[0] + // we check in the 'devdoc' if there's a developer documentation for this error + devdoc = contract.object.devdoc.errors[functionSignature][0] || {} + // we check in the 'userdoc' if there's an user documentation for this error + const userdoc = contract.object.userdoc.errors[functionSignature][0] || {} + if (userdoc) customError += ' : ' + (userdoc as any).notice // we append the user doc if any + } + for (const input of functionDesc.inputs) { + const v = decodedCustomErrorInputs[input.name] + decodedCustomErrorInputsClean[input.name] = { + value: v.toString ? v.toString() : v, + documentation: (devdoc as any).params[input.name] // we add the developer documentation for this input parameter if any + } + } break } } } - if (decodedCustomErrorInputs) { + if (decodedCustomErrorInputsClean) { msg = '\tThe transaction has been reverted to the initial state.\nError provided by the contract:' msg += `\n${customError}` msg += '\nParameters:' - msg += `\n${decodedCustomErrorInputs}` + msg += `\n${JSON.stringify(decodedCustomErrorInputsClean, null, ' ')}` } } if (!customError) { diff --git a/libs/remix-lib/src/index.ts b/libs/remix-lib/src/index.ts index ae3864a8c9..d3d37231f1 100644 --- a/libs/remix-lib/src/index.ts +++ b/libs/remix-lib/src/index.ts @@ -13,6 +13,7 @@ import * as txFormat from './execution/txFormat' import { TxListener } from './execution/txListener' import { TxRunner } from './execution/txRunner' import { LogsManager } from './execution/logsManager' +import { forkAt } from './execution/forkAt' import * as typeConversion from './execution/typeConversion' import { TxRunnerVM } from './execution/txRunnerVM' import { TxRunnerWeb3 } from './execution/txRunnerWeb3' @@ -45,7 +46,8 @@ function modules () { TxRunnerWeb3: TxRunnerWeb3, TxRunnerVM: TxRunnerVM, typeConversion: typeConversion, - LogsManager + LogsManager, + forkAt } } } diff --git a/libs/remix-lib/src/util.ts b/libs/remix-lib/src/util.ts index cc70e786e8..58f5d875dd 100644 --- a/libs/remix-lib/src/util.ts +++ b/libs/remix-lib/src/util.ts @@ -1,5 +1,6 @@ 'use strict' import { BN, bufferToHex, keccak, setLengthLeft, toBuffer, addHexPrefix } from 'ethereumjs-util' +import stringSimilarity from 'string-similarity' /* contains misc util: @TODO should be splitted @@ -222,9 +223,11 @@ export function compareByteCode (code1, code2) { code2 = this.extractSwarmHash(code2) code2 = this.extractcborMetadata(code2) - if (code1 && code2 && code1.indexOf(code2) === 0) { - return true + if (code1 && code2) { + const compare = stringSimilarity.compareTwoStrings(code1, code2) + return compare > 0.93 } + return false } /* util extracted out from remix-ide. @TODO split this file, cause it mix real util fn with solidity related stuff ... */ diff --git a/libs/remix-lib/src/web3Provider/web3VmProvider.ts b/libs/remix-lib/src/web3Provider/web3VmProvider.ts index 59b0e7e909..b5d8ea4732 100644 --- a/libs/remix-lib/src/web3Provider/web3VmProvider.ts +++ b/libs/remix-lib/src/web3Provider/web3VmProvider.ts @@ -138,7 +138,7 @@ export class Web3VmProvider { async txProcessed (data) { const lastOp = this.vmTraces[this.processingHash].structLogs[this.processingIndex - 1] if (lastOp) { - lastOp.error = lastOp.op !== 'RETURN' && lastOp.op !== 'STOP' && lastOp.op !== 'thisDESTRUCT' + lastOp.error = lastOp.op !== 'RETURN' && lastOp.op !== 'STOP' && lastOp.op !== 'DESTRUCT' } this.vmTraces[this.processingHash].gas = '0x' + data.gasUsed.toString(16) diff --git a/libs/remix-simulator/src/vm-context.ts b/libs/remix-simulator/src/vm-context.ts index e0a3aef849..363e9ab50e 100644 --- a/libs/remix-simulator/src/vm-context.ts +++ b/libs/remix-simulator/src/vm-context.ts @@ -91,7 +91,6 @@ export class VMContext { blocks latestBlockNumber txs - defaultFork currentVm web3vm logsManager @@ -100,8 +99,7 @@ export class VMContext { constructor (fork?) { this.blockGasLimitDefault = 4300000 this.blockGasLimit = this.blockGasLimitDefault - this.defaultFork = fork || 'berlin' - this.currentFork = this.defaultFork + this.currentFork = fork || 'berlin' this.currentVm = this.createVm(this.currentFork) this.blocks = {} this.latestBlockNumber = 0 diff --git a/libs/remix-solidity/src/compiler/types.ts b/libs/remix-solidity/src/compiler/types.ts index e10e99e52f..75091f366b 100644 --- a/libs/remix-solidity/src/compiler/types.ts +++ b/libs/remix-solidity/src/compiler/types.ts @@ -150,7 +150,7 @@ export interface CompilerInputOptions { language?: Language } -export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'muirGlacier' | 'berlin' | null +export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'muirGlacier' | 'berlin' | 'london' | null export type Language = 'Solidity' | 'Yul' diff --git a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx index 2bea5aed07..fbfdc6dfba 100644 --- a/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx +++ b/libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx @@ -6,6 +6,7 @@ import './copy-to-clipboard.css' export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => { const [message, setMessage] = useState(tip) + const handleClick = (event) => { if (content && content !== '') { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory try { diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 78ec2de174..7adc8a9312 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -208,8 +208,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => { } return null }, - debugWithGeneratedSources: state.opt.debugWithGeneratedSources, - fork: 'berlin' + debugWithGeneratedSources: state.opt.debugWithGeneratedSources }) debuggerInstance.debug(blockNumber, txNumber, tx, () => { @@ -275,7 +274,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {

Debugger Configuration

-
+
{ setState(prevState => { return { ...prevState, opt: { debugWithGeneratedSources: checked } } diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx index 67f9821960..1606ce8491 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx @@ -4,41 +4,116 @@ import './styles/assembly-items.css' export const AssemblyItems = ({ registerEvent }) => { const [assemblyItems, dispatch] = useReducer(reducer, initialState) + const [absoluteSelectedIndex, setAbsoluteSelectedIndex] = useState(0) const [selectedItem, setSelectedItem] = useState(0) + const [nextSelectedItem, setNextSelectedItem] = useState(1) + const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([]) + const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([]) const refs = useRef({}) const asmItemsRef = useRef(null) useEffect(() => { - registerEvent && registerEvent('codeManagerChanged', (code, address, index) => { - dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index } }) + registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => { + dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes } }) }) }, []) useEffect(() => { - if (selectedItem !== assemblyItems.index) { + if (absoluteSelectedIndex !== assemblyItems.index) { + clearItems() indexChanged(assemblyItems.index) + nextIndexChanged(assemblyItems.nextIndex) + returnIndexes(assemblyItems.returnInstructionIndexes) + outOfGasIndexes(assemblyItems.outOfGasInstructionIndexes) } - }, [assemblyItems.index]) - - const indexChanged = (index: number) => { - if (index < 0) return - let currentItem = refs.current[selectedItem] ? refs.current[selectedItem] : null + }, [assemblyItems.opCodes.index]) + const clearItem = (currentItem) => { if (currentItem) { currentItem.removeAttribute('selected') currentItem.removeAttribute('style') if (currentItem.firstChild) { currentItem.firstChild.removeAttribute('style') } - const codeView = asmItemsRef.current + } + } + + const clearItems = () => { + clearItem(refs.current[selectedItem] ? refs.current[selectedItem] : null) + clearItem(refs.current[nextSelectedItem] ? refs.current[nextSelectedItem] : null) + + returnInstructionIndexes.map((index) => { + if (index < 0) return + clearItem(refs.current[index] ? refs.current[index] : null) + }) + + outOfGasInstructionIndexes.map((index) => { + if (index < 0) return + clearItem(refs.current[index] ? refs.current[index] : null) + }) + } + + const indexChanged = (index: number) => { + if (index < 0) return - currentItem = codeView.children[index] - currentItem.style.setProperty('border-color', 'var(--primary)') - currentItem.style.setProperty('border-style', 'solid') + const codeView = asmItemsRef.current + + const currentItem = codeView.children[index] + if (currentItem) { + currentItem.style.setProperty('background-color', 'var(--primary)') + currentItem.style.setProperty('color', 'var(--light)') currentItem.setAttribute('selected', 'selected') codeView.scrollTop = currentItem.offsetTop - parseInt(codeView.offsetTop) - setSelectedItem(index) } + + setSelectedItem(index) + setAbsoluteSelectedIndex(assemblyItems.opCodes.index) + } + + const nextIndexChanged = (index: number) => { + if (index < 0) return + + const codeView = asmItemsRef.current + + const currentItem = codeView.children[index] + if (currentItem) { + currentItem.style.setProperty('border-color', 'var(--secondary)') + currentItem.style.setProperty('border-style', 'dotted') + currentItem.setAttribute('selected', 'selected') + } + setNextSelectedItem(index) + } + + const returnIndexes = (indexes) => { + indexes.map((index) => { + if (index < 0) return + + const codeView = asmItemsRef.current + + const currentItem = codeView.children[index] + if (currentItem) { + currentItem.style.setProperty('border-color', 'var(--warning)') + currentItem.style.setProperty('border-style', 'dotted') + currentItem.setAttribute('selected', 'selected') + } + }) + setReturnInstructionIndexes(indexes) + } + + const outOfGasIndexes = (indexes) => { + indexes.map((index) => { + if (index < 0) return + + const codeView = asmItemsRef.current + + const currentItem = codeView.children[index] + if (currentItem) { + currentItem.style.setProperty('border-color', 'var(--danger)') + currentItem.style.setProperty('border-style', 'dotted') + currentItem.setAttribute('selected', 'selected') + } + }) + setOutOfGasInstructionIndexes(indexes) } return ( diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx index 4ba9b16c12..207e99ea74 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx @@ -7,7 +7,7 @@ import './styles/dropdown-panel.css' export const DropdownPanel = (props: DropdownPanelProps) => { const [calldataObj, dispatch] = useReducer(reducer, initialState) - const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent } = props + const { dropdownName, dropdownMessage, calldata, header, loading, extractFunc, formatSelfFunc, registerEvent, triggerEvent, loadMoreEvent, loadMoreCompletedEvent, headStyle, bodyStyle, hexHighlight } = props const extractDataDefault: ExtractFunc = (item, parent?) => { const ret: ExtractData = {} @@ -34,10 +34,21 @@ export const DropdownPanel = (props: DropdownPanelProps) => { return ret } const formatSelfDefault = (key: string | number, data: ExtractData) => { + let value + if (hexHighlight && typeof (data.self) === 'string') { + const isHex = data.self.startsWith('0x') || hexHighlight + if (isHex) { + const regex = /^(0+)(.*)/g + const split = regex.exec(data.self.replace('0x', '')) + if (split && split[1]) { + value = (0x{split[1]}{ split[2] && {split[2]} }) + } else value = (0x{data.self.replace('0x', '')}) + } else value = {data.self} + } else value = {data.self} return (
- +
) } @@ -184,14 +195,14 @@ export const DropdownPanel = (props: DropdownPanelProps) => { return (
-
+
{dropdownName}
{header}
-
+
{ state.data && diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx index b772369ec6..8e6cd8b3be 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx @@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line export const MemoryPanel = ({ calldata }) => { return ( - + ) } diff --git a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx index 0bc60b9a3f..c30569b307 100644 --- a/libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx @@ -5,32 +5,36 @@ import { ExtractData } from '../../types' // eslint-disable-line export const SolidityState = ({ calldata, message }) => { const formatSelf = (key: string, data: ExtractData) => { - let color = 'var(--primary)' - if (data.isArray || data.isStruct || data.isMapping) { - color = 'var(--info)' - } else if ( - data.type.indexOf('uint') === 0 || + try { + let color = 'var(--primary)' + if (data.isArray || data.isStruct || data.isMapping) { + color = 'var(--info)' + } else if ( + data.type.indexOf('uint') === 0 || data.type.indexOf('int') === 0 || data.type.indexOf('bool') === 0 || data.type.indexOf('enum') === 0 - ) { - color = 'var(--green)' - } else if (data.type === 'string') { - color = 'var(--teal)' + ) { + color = 'var(--green)' + } else if (data.type === 'string') { + color = 'var(--teal)' } else if (data.self == 0x0) { // eslint-disable-line - color = 'var(--gray)' - } - return ( -
diff --git a/nx.json b/nx.json index 4220432156..c46cb5df38 100644 --- a/nx.json +++ b/nx.json @@ -96,6 +96,9 @@ "remix-ui-workspace": { "tags": [] }, + "remix-ui-settings": { + "tags": [] + }, "remix-ui-static-analyser": { "tags": [] }, @@ -103,7 +106,6 @@ "tags": [] }, "remix-ui-terminal": { - "tags": [] } } } diff --git a/package-lock.json b/package-lock.json index fa010f1b61..9e028132b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36072,6 +36072,11 @@ } } }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", diff --git a/package.json b/package.json index 10a169c65f..a66874796c 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,6 @@ "jquery": "^3.3.1", "jszip": "^3.6.0", "latest-version": "^5.1.0", - "lodash": "^4.17.21", "merge": "^1.2.0", "npm-install-version": "^6.0.2", "react": "16.13.1", @@ -172,12 +171,13 @@ "react-dom": "16.13.1", "selenium": "^2.20.0", "signale": "^1.4.0", + "string-similarity": "^4.0.4", "time-stamp": "^2.2.0", "tslib": "^2.3.0", "web3": "1.2.4", "winston": "^3.3.3", "ws": "^7.3.0" - }, + }, "devDependencies": { "@babel/core": "^7.4.5", "@babel/plugin-transform-modules-amd": "^7.10.4", diff --git a/tsconfig.json b/tsconfig.json index 3325513e53..3e7b20eceb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,6 +39,7 @@ "@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"], "@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"], "@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"], + "@remix-ui/settings": ["libs/remix-ui/settings/src/index.ts"], "@remix-ui/static-analyser": ["libs/remix-ui/static-analyser/src/index.ts"], "@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"], "@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"] diff --git a/workspace.json b/workspace.json index 14cfc136d9..72dbe2fe82 100644 --- a/workspace.json +++ b/workspace.json @@ -726,6 +726,22 @@ } } }, + "remix-ui-settings": { + "root": "libs/remix-ui/settings", + "sourceRoot": "libs/remix-ui/settings/src", + "projectType": "library", + "schematics": {}, + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["libs/remix-ui/settings/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "!libs/remix-ui/settings/**/*"] + } + } + } + }, "remix-ui-static-analyser": { "root": "libs/remix-ui/static-analyser", "sourceRoot": "libs/remix-ui/static-analyser/src", @@ -863,4 +879,4 @@ } }, "defaultProject": "remix-ide" -} +} \ No newline at end of file