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

pull/5370/head
tizah 3 years ago
commit 9e318518cf
  1. 3
      apps/debugger/src/app/debugger-api.ts
  2. 9
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  3. 2
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  4. 17
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  5. 2
      apps/remix-ide-e2e/src/tests/pluginManager.spec.ts
  6. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  7. 22
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  8. 24
      apps/remix-ide/src/app/panels/file-panel.js
  9. 2
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  10. 13
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  11. 221
      apps/remix-ide/src/app/tabs/settings-tab.js
  12. 8
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  13. 5
      apps/remix-ide/src/blockchain/blockchain.js
  14. 40
      apps/remix-ide/src/blockchain/execution-context.js
  15. 2
      apps/remix-ide/src/remixAppManager.js
  16. 4
      libs/remix-debug/src/Ethdebugger.ts
  17. 27
      libs/remix-debug/src/code/codeManager.ts
  18. 4
      libs/remix-debug/src/debugger/VmDebugger.ts
  19. 3
      libs/remix-debug/src/debugger/debugger.ts
  20. 11
      libs/remix-debug/src/solidity-decoder/decodeInfo.ts
  21. 4
      libs/remix-debug/src/solidity-decoder/stateDecoder.ts
  22. 11
      libs/remix-debug/src/trace/traceAnalyser.ts
  23. 14
      libs/remix-debug/src/trace/traceCache.ts
  24. 20
      libs/remix-debug/src/trace/traceManager.ts
  25. 121
      libs/remix-lib/src/execution/forkAt.ts
  26. 30
      libs/remix-lib/src/execution/txExecution.ts
  27. 4
      libs/remix-lib/src/index.ts
  28. 7
      libs/remix-lib/src/util.ts
  29. 2
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  30. 4
      libs/remix-simulator/src/vm-context.ts
  31. 2
      libs/remix-solidity/src/compiler/types.ts
  32. 1
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  33. 5
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  34. 101
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  35. 19
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
  36. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx
  37. 46
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/solidity-state.tsx
  38. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/stack-panel.tsx
  39. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/step-detail.tsx
  40. 34
      libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts
  41. 5
      libs/remix-ui/debugger-ui/src/types/index.ts
  42. 70
      libs/remix-ui/debugger-ui/src/utils/solidityTypeFormatter.ts
  43. 3
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  44. 28
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  45. 7
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  46. 4
      libs/remix-ui/settings/.babelrc
  47. 19
      libs/remix-ui/settings/.eslintrc
  48. 7
      libs/remix-ui/settings/README.md
  49. 1
      libs/remix-ui/settings/src/index.ts
  50. 12
      libs/remix-ui/settings/src/lib/constants.ts
  51. 0
      libs/remix-ui/settings/src/lib/remix-ui-settings.css
  52. 167
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  53. 52
      libs/remix-ui/settings/src/lib/settingsAction.ts
  54. 103
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  55. 16
      libs/remix-ui/settings/tsconfig.json
  56. 13
      libs/remix-ui/settings/tsconfig.lib.json
  57. 6
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  58. 4
      nx.json
  59. 5
      package-lock.json
  60. 4
      package.json
  61. 1
      tsconfig.json
  62. 18
      workspace.json

@ -111,8 +111,7 @@ export const DebuggerApiMixin = (Base) => class extends Base {
} }
return null return null
}, },
debugWithGeneratedSources: false, debugWithGeneratedSources: false
fork: 'berlin'
}) })
return await debug.debugger.traceManager.getTrace(hash) return await debug.debugger.traceManager.getTrace(hash)
} }

@ -121,7 +121,8 @@ const stateCheck = {
chairperson: { chairperson: {
value: '0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C', value: '0xCA35B7D915458EF540ADE6068DFE2F44E8FA733C',
type: 'address', type: 'address',
constant: false constant: false,
immutable: false
}, },
voters: { voters: {
value: { value: {
@ -148,7 +149,8 @@ const stateCheck = {
} }
}, },
type: 'mapping(address => struct Ballot.Voter)', type: 'mapping(address => struct Ballot.Voter)',
constant: false constant: false,
immutable: false
}, },
proposals: { proposals: {
value: [ value: [
@ -168,7 +170,8 @@ const stateCheck = {
], ],
length: '0x1', length: '0x1',
type: 'struct Ballot.Proposal[]', type: 'struct Ballot.Proposal[]',
constant: false constant: false,
immutable: false
} }
} }

@ -228,7 +228,7 @@ module.exports = {
.waitForElementVisible('*[data-id="solidityLocals"]', 60000) .waitForElementVisible('*[data-id="solidityLocals"]', 60000)
.pause(10000) .pause(10000)
.checkVariableDebug('soliditylocals', { num: { value: '2', type: 'uint256' } }) .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() .end()
} }
} }

@ -18,6 +18,7 @@ module.exports = {
'Should activate `generate contract metadata`': function (browser) { 'Should activate `generate contract metadata`': function (browser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000) browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000) .waitForElementVisible('*[data-id="settingsTabGenerateContractMetadataLabel"]', 5000)
.verify.elementPresent('[data-id="settingsTabGenerateContractMetadata"]:checked')
.click('*[data-id="verticalIconsFileExplorerIcons"]') .click('*[data-id="verticalIconsFileExplorerIcons"]')
.click('[data-id="treeViewLitreeViewItemcontracts"]') .click('[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol') .openFile('contracts/3_Ballot.sol')
@ -39,22 +40,26 @@ module.exports = {
.click('*[data-id="verticalIconsKindsettings"]') .click('*[data-id="verticalIconsKindsettings"]')
.setValue('*[data-id="settingsTabGistAccessToken"]', '**********') .setValue('*[data-id="settingsTabGistAccessToken"]', '**********')
.click('*[data-id="settingsTabSaveGistToken"]') .click('*[data-id="settingsTabSaveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Access token has been saved') .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token has been saved')
.pause(3000)
}, },
'Should copy github access token to clipboard': function (browser: NightwatchBrowser) { 'Should copy github access token to clipboard': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000) browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.click('*[data-id="copyToClipboardCopyIcon"]') .click('*[data-id="copyToClipboardCopyIcon"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Copied value to clipboard.') // .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) { 'Should remove github access token': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000) browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.pause(1000)
.click('*[data-id="settingsTabRemoveGistToken"]') .click('*[data-id="settingsTabRemoveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 5000) .waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]:nth-last-of-type(1)', 'Access token removed') .assert.containsText('*[data-shared="tooltipPopup"]', 'Access token removed')
.assert.containsText('*[data-id="settingsTabGistAccessToken"]', '') .assert.containsText('*[data-id="settingsTabGistAccessToken"]', '')
}, },

@ -5,7 +5,7 @@ import init from '../helpers/init'
const testData = { const testData = {
pluginName: 'remixIde', pluginName: 'remixIde',
pluginDisplayName: 'Remix IDE', pluginDisplayName: 'Remix IDE',
pluginUrl: 'https://zokrates-remix-plugin.netlify.app/' pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/'
} }
module.exports = { module.exports = {

@ -32,6 +32,8 @@ module.exports = {
'Should sign message using account key': function (browser: NightwatchBrowser) { 'Should sign message using account key': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="settingsRemixRunSignMsg"]') browser.waitForElementPresent('*[data-id="settingsRemixRunSignMsg"]')
.click('select[id="selectExEnvOptions"] option[value="vm-berlin"]')
.pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]') .click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000) .pause(2000)
.waitForElementPresent('*[data-id="modalDialogCustomPromptText"]') .waitForElementPresent('*[data-id="modalDialogCustomPromptText"]')

@ -148,9 +148,14 @@ module.exports = {
.click('.instance:nth-of-type(3) > div > button') .click('.instance:nth-of-type(3) > div > button')
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError') .journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:') .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.') .journalLastChildIncludes('Debug the transaction to get more information.')
}, },
@ -163,9 +168,14 @@ module.exports = {
.click('.instance:nth-of-type(2) > div > button') .click('.instance:nth-of-type(2) > div > button')
.clickFunction('g - transact (not payable)') .clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:') .journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError') .journalLastChildIncludes('CustomError : error description')
.journalLastChildIncludes('Parameters:') .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.') .journalLastChildIncludes('Debug the transaction to get more information.')
.end() .end()
} }
@ -256,6 +266,10 @@ contract C {
pragma solidity ^0.8.4; pragma solidity ^0.8.4;
/// error description
/// @param a param1
/// @param b param2
/// @param c param3
error CustomError(uint a, uint b, string c); error CustomError(uint a, uint b, string c);
contract C { contract C {
function f() public pure { function f() public pure {

@ -34,7 +34,7 @@ const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = { const profile = {
name: 'filePanel', name: 'filePanel',
displayName: 'File explorers', displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace'], methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'], events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'],
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
description: ' - ', description: ' - ',
@ -60,9 +60,11 @@ module.exports = class Filepanel extends ViewPlugin {
this.gitHandle = new GitHandle() this.gitHandle = new GitHandle()
this.hardhatHandle = new HardhatHandle() this.hardhatHandle = new HardhatHandle()
this.registeredMenuItems = [] this.registeredMenuItems = []
this.removedMenuItems = []
this.request = {} this.request = {}
this.workspaces = [] this.workspaces = []
this.initialWorkspace = null this.initialWorkspace = null
this.appManager = appManager
} }
render () { render () {
@ -88,6 +90,7 @@ module.exports = class Filepanel extends ViewPlugin {
request={this.request} request={this.request}
workspaces={this.workspaces} workspaces={this.workspaces}
registeredMenuItems={this.registeredMenuItems} registeredMenuItems={this.registeredMenuItems}
removedMenuItems={this.removedMenuItems}
initialWorkspace={this.initialWorkspace} initialWorkspace={this.initialWorkspace}
/> />
, this.el) , this.el)
@ -101,8 +104,22 @@ module.exports = class Filepanel extends ViewPlugin {
if (!item) throw new Error('Invalid register context menu argument') 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.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 (!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.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() this.renderComponent()
} }
@ -163,6 +180,9 @@ module.exports = class Filepanel extends ViewPlugin {
} }
return return
} }
const self = this
this.appManager.on('manager', 'pluginDeactivated', self.removePluginActions.bind(this))
// insert example contracts if there are no files to show // insert example contracts if there are no files to show
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => { this._deps.fileProviders.browser.resolveDirectory('/', async (error, filesList) => {

@ -7,6 +7,7 @@ const addTooltip = require('../../ui/tooltip')
const semver = require('semver') const semver = require('semver')
const modalDialogCustom = require('../../ui/modal-dialog-custom') const modalDialogCustom = require('../../ui/modal-dialog-custom')
const css = require('../styles/compile-tab-styles') const css = require('../styles/compile-tab-styles')
const _paq = window._paq = window._paq || []
class CompilerContainer { class CompilerContainer {
constructor (compileTabLogic, editor, config, queryParams) { constructor (compileTabLogic, editor, config, queryParams) {
@ -111,6 +112,7 @@ class CompilerContainer {
this._view.compileIcon.setAttribute('title', 'idle') this._view.compileIcon.setAttribute('title', 'idle')
this._view.compileIcon.classList.remove(`${css.spinningIcon}`) this._view.compileIcon.classList.remove(`${css.spinningIcon}`)
this._view.compileIcon.classList.remove(`${css.bouncingIcon}`) this._view.compileIcon.classList.remove(`${css.bouncingIcon}`)
_paq.push(['trackEvent', 'compiler', `compiled_with_v_${this._retrieveVersion()}`])
}) })
} }

@ -17,11 +17,13 @@ export default class HardhatProvider extends Plugin {
constructor (blockchain) { constructor (blockchain) {
super(profile) super(profile)
this.provider = null this.provider = null
this.blocked = false // used to block any call when trying to recover after a failed connection.
this.blockchain = blockchain this.blockchain = blockchain
} }
onDeactivation () { onDeactivation () {
this.provider = null this.provider = null
this.blocked = false
} }
hardhatProviderDialogBody () { hardhatProviderDialogBody () {
@ -39,6 +41,8 @@ export default class HardhatProvider extends Plugin {
sendAsync (data) { sendAsync (data) {
return new Promise((resolve, reject) => { 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) { if (!this.provider) {
modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => { modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => {
this.provider = new Web3.providers.HttpProvider(target) this.provider = new Web3.providers.HttpProvider(target)
@ -54,9 +58,16 @@ export default class HardhatProvider extends Plugin {
sendAsyncInternal (data, resolve, reject) { sendAsyncInternal (data, resolve, reject) {
if (this.provider) { 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) { 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 this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
return reject(error) return reject(error)
} }
resolve(message) resolve(message)

@ -1,12 +1,10 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web' import { ViewPlugin } from '@remixproject/engine-web'
import ReactDOM from 'react-dom'
import * as packageJson from '../../../../../package.json' 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 globalRegistry = require('../../global/registry')
const tooltip = require('../ui/tooltip')
const copyToClipboard = require('../ui/copy-to-clipboard')
const EventManager = require('../../lib/events') const EventManager = require('../../lib/events')
const css = require('./styles/settings-tab-styles')
const _paq = window._paq = window._paq || []
const profile = { const profile = {
name: 'settings', name: 'settings',
@ -51,210 +49,27 @@ module.exports = class SettingsTab extends ViewPlugin {
textWrapLabel: null textWrapLabel: null
} /* eslint-enable */ } /* eslint-enable */
this.event = new EventManager() this.event = new EventManager()
this.element = document.createElement('div')
this.element.setAttribute('id', 'settingsTab')
} }
createThemeCheckies () { onActivation () {
const themes = this._deps.themeModule.getThemes() this.renderComponent()
const onswitchTheme = (event, name) => {
this._deps.themeModule.switchTheme(name)
}
if (themes) {
return yo`<div class="card-text themes-container">
${themes.map((aTheme) => {
const el = yo`<div class="radio custom-control custom-radio mb-1 form-check ${css.crow}">
<input type="radio" onchange=${event => { onswitchTheme(event, aTheme.name) }} class="align-middle custom-control-input" name="theme" id="${aTheme.name}" data-id="settingsTabTheme${aTheme.name}">
<label class="form-check-label custom-control-label" data-id="settingsTabThemeLabel${aTheme.name}" for="${aTheme.name}">${aTheme.name} (${aTheme.quality})</label>
</div>`
if (this._deps.themeModule.active === aTheme.name) el.querySelector('input').setAttribute('checked', 'checked')
return el
})}
</div>`
}
} }
render () { render () {
const self = this return this.element
if (self._view.el) return self._view.el }
// Gist settings
const token = this.config.get('settings/gist-access-token')
const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="form-control">`
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`<input class="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onclick=${() => saveToken()} value="Save" type="button">`
const gistRemoveToken = yo`<button class="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onclick=${() => removeToken()}>Remove</button>`
this._view.gistToken = yo`
<div class="text-secondary mb-0 h6">
${gistAccessToken}
<div class="d-flex justify-content-end pt-2">
${copyToClipboard(() => gistAccessToken.value)}${gistAddToken}${gistRemoveToken}
</div>
<p class="pt-1">
<i class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>
<span class="text-warning">Please reload Remix after having saved the token.</span>
</p>
</div>
`
this._view.optionVM = yo`<input onchange=${onchangeOption} class="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox">`
this._view.optionVMLabel = yo`<label class="form-check-label custom-control-label align-middle" for="alwaysUseVM">Always use JavaScript VM at Load</label>`
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`<input id="editorWrap" class="custom-control-input" type="checkbox" onchange=${textWrapEvent}>`
this._view.textWrapLabel = yo`<label class="form-check-label custom-control-label align-middle" for="editorWrap">Text Wrap</label>`
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`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="custom-control-input">`
this._view.warnPersonalMode = yo`<i class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>`
this._view.personalLabel = yo`<label class="form-check-label custom-control-label align-middle" for="personal"> ${this._view.warnPersonalMode} Enable Personal Mode for web3 provider. ${warnText}></label>`
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`<input onchange=${onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" class="custom-control-input">`
this._view.useMatomoAnalyticsLabel = yo`
<label class="form-check-label custom-control-label align-middle" for="settingsMatomoAnalytics">
<span>Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the sites UX & UI. See more about</span>
<a href="https://medium.com/p/66ef69e14931/" target="_blank">Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a>
</label>
`
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`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" class="custom-control-input">`
this._view.generateContractMetadataLabel = yo`<label class="form-check-label custom-control-label align-middle" data-id="settingsTabGenerateContractMetadataLabel" for="generatecontractmetadata">Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.</label>`
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`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
this._view.themes = this._deps.themeModule.getThemes()
this._view.themesCheckBoxes = this.createThemeCheckies()
this._view.config.general = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">General settings</h6>
<div class="mt-2 custom-control custom-checkbox mb-1">
${this._view.generateContractMetadata}
${this._view.generateContractMetadataLabel}
</div>
<div class="fmt-2 custom-control custom-checkbox mb-1">
${this._view.optionVM}
${this._view.optionVMLabel}
</div>
<div class="mt-2 custom-control custom-checkbox mb-1">
${this._view.textWrap}
${this._view.textWrapLabel}
</div>
<div class="custom-control custom-checkbox mb-1">
${this._view.personal}
${this._view.personalLabel}
</div>
<div class="custom-control custom-checkbox mb-1">
${this._view.useMatomoAnalytics}
${this._view.useMatomoAnalyticsLabel}
</div>
</div>
</div>
`
this._view.gistToken = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">Github Access Token</h6>
<p class="mb-1">Manage the access token used to publish to Gist and retrieve Github contents.</p>
<p class="">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.</p>
<p class="${css.crowNoFlex} mb-1"><a class="text-primary ${css.text}" target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
<div class="${css.crowNoFlex}"><label>TOKEN:</label>${this._view.gistToken}</div>
</div>
</div>`
this._view.config.themes = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">Themes</h6>
${this._view.themesCheckBoxes}
</div>
</div>`
this._view.el = yo`
<div id="settingsView" data-id="settingsTabSettingsView">
${this._view.config.general}
${this._view.gistToken}
${this._view.config.themes}
</div>`
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')
}
}
this._deps.themeModule.switchTheme() renderComponent () {
return this._view.el ReactDOM.render(
<RemixUiSettings
config = { this.config }
editor = { this.editor }
_deps = { this._deps }
/>,
this.element
)
} }
getGithubAccessToken () { getGithubAccessToken () {

@ -44,14 +44,14 @@ UniversalDAppUI.prototype.renderInstance = function (contract, address, contract
noInstances.parentNode.removeChild(noInstances) noInstances.parentNode.removeChild(noInstances)
} }
const abi = txHelper.sortAbiFunction(contract.abi) 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". // TODO this function was named before "appendChild".
// this will render an instance: contract name, contract address, and all the public functions // 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 // basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// this returns a DOM element // this returns a DOM element
UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName) { UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address, contractName, contract) {
const self = this const self = this
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex') address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
address = ethJSUtil.toChecksumAddress(address) address = ethJSUtil.toChecksumAddress(address)
@ -117,7 +117,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
funABI: funABI, funABI: funABI,
address: address, address: address,
contractABI: contractABI, contractABI: contractABI,
contractName: contractName contractName: contractName,
contract
})) }))
}) })
@ -255,6 +256,7 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i
args.contractName, args.contractName,
args.contractABI, args.contractABI,
args.funABI, args.funABI,
args.contract,
inputsValues, inputsValues,
args.address, args.address,
params, params,

@ -249,7 +249,7 @@ class Blockchain {
return txlistener 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 // contractsDetails is used to resolve libraries
txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => { txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => {
if (error) { if (error) {
@ -265,6 +265,7 @@ class Blockchain {
if (data) { if (data) {
data.contractName = contractName data.contractName = contractName
data.contractABI = contractAbi data.contractABI = contractAbi
data.contract = contract
} }
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure' const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => { this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
@ -490,7 +491,7 @@ class Blockchain {
if (execResult) { 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. // 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') 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) { if (vmError.error) {
return cb(vmError.message) return cb(vmError.message)
} }

@ -1,6 +1,7 @@
/* global ethereum */ /* global ethereum */
'use strict' 'use strict'
import Web3 from 'web3' import Web3 from 'web3'
import { execution } from '@remix-project/remix-lib'
import EventManager from '../lib/events' import EventManager from '../lib/events'
let web3 let web3
@ -21,8 +22,7 @@ export class ExecutionContext {
this.executionContext = null this.executionContext = null
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
this.defaultFork = 'berlin' this.currentFork = 'berlin'
this.currentFork = this.defaultFork
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3' this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {} this.customNetWorks = {}
this.blocks = {} this.blocks = {}
@ -123,21 +123,18 @@ export class ExecutionContext {
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null) 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 context = value.context
const fork = value.fork || this.defaultFork
if (!cb) cb = () => {} if (!cb) cb = () => {}
if (!confirmCb) confirmCb = () => {} if (!confirmCb) confirmCb = () => {}
if (!infoCb) infoCb = () => {} if (!infoCb) infoCb = () => {}
if (context === 'vm') { if (context === 'vm') {
this.executionContext = context this.executionContext = context
this.currentFork = fork this.currentFork = value.fork
this.event.trigger('contextChanged', ['vm']) this.event.trigger('contextChanged', ['vm'])
return cb() return cb()
} }
this.currentFork = this.defaultFork // in the case of injected and web3, we default to the last fork.
if (context === 'injected') { if (context === 'injected') {
if (injectedProvider === undefined) { 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).') 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.askPermission()
this.executionContext = context this.executionContext = context
web3.setProvider(injectedProvider) web3.setProvider(injectedProvider)
this._updateBlockGasLimit() await this._updateChainContext()
this.event.trigger('contextChanged', ['injected']) this.event.trigger('contextChanged', ['injected'])
return cb() return cb()
} }
@ -173,22 +170,29 @@ export class ExecutionContext {
this.listenOnLastBlockId = null this.listenOnLastBlockId = null
} }
_updateBlockGasLimit () { async _updateChainContext () {
if (this.getProvider() !== 'vm') { if (this.getProvider() !== 'vm') {
web3.eth.getBlock('latest', (err, block) => { try {
if (!err) { 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 // 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 this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault
} else { try {
this.blockGasLimit = this.blockGasLimitDefault 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 () { listenOnLastBlock () {
this.listenOnLastBlockId = setInterval(() => { this.listenOnLastBlockId = setInterval(() => {
this._updateBlockGasLimit() this._updateChainContext()
}, 15000) }, 15000)
} }
@ -202,7 +206,7 @@ export class ExecutionContext {
web3.eth.net.isListening((err, isConnected) => { web3.eth.net.isListening((err, isConnected) => {
if (!err && isConnected === true) { if (!err && isConnected === true) {
this.executionContext = context this.executionContext = context
this._updateBlockGasLimit() this._updateChainContext()
this.event.trigger('contextChanged', [context]) this.event.trigger('contextChanged', [context])
this.event.trigger('web3EndpointChanged') this.event.trigger('web3EndpointChanged')
cb() cb()

@ -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) const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) { 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) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -41,7 +41,7 @@ export class Ethdebugger {
this.opts = opts this.opts = opts
this.event = new EventManager() 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.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) }) this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.storageResolver = null this.storageResolver = null
@ -55,7 +55,7 @@ export class Ethdebugger {
} }
setManagers () { 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.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) }) this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.storageResolver = null this.storageResolver = null

@ -145,15 +145,38 @@ export class CodeManager {
}) })
} }
private retrieveIndexAndTrigger (codeMananger, address, step, code) { private async retrieveIndexAndTrigger (codeMananger, address, step, code) {
let result let result
let next
const returnInstructionIndexes = []
const outOfGasInstructionIndexes = []
try { try {
result = codeMananger.getInstructionIndex(address, step) 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) { } catch (error) {
return console.log(error) return console.log(error)
} }
try { try {
codeMananger.event.trigger('changed', [code, address, result]) codeMananger.event.trigger('changed', [code, address, result, next, returnInstructionIndexes, outOfGasInstructionIndexes])
} catch (e) { } catch (e) {
console.log('dispatching event failed', e) console.log('dispatching event failed', e)
} }

@ -59,8 +59,8 @@ export class VmDebuggerLogic {
} }
listenToCodeManagerEvents () { listenToCodeManagerEvents () {
this._codeManager.event.register('changed', (code, address, index) => { this._codeManager.event.register('changed', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
this.event.trigger('codeManagerChanged', [code, address, index]) this.event.trigger('codeManagerChanged', [code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes])
}) })
} }

@ -26,8 +26,7 @@ export class Debugger {
this.debugger = new Ethdebugger({ this.debugger = new Ethdebugger({
web3: options.web3, web3: options.web3,
debugWithGeneratedSources: options.debugWithGeneratedSources, debugWithGeneratedSources: options.debugWithGeneratedSources,
compilationResult: this.compilationResult, compilationResult: this.compilationResult
fork: options.fork
}) })
const { traceManager, callTree, solidityProxy } = this.debugger const { traceManager, callTree, solidityProxy } = this.debugger

@ -336,7 +336,9 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
console.log('unable to retrieve decode info of ' + variable.typeDescriptions.typeString) console.log('unable to retrieve decode info of ' + variable.typeDescriptions.typeString)
return null 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.slot++
storagelocation.offset = 0 storagelocation.offset = 0
} }
@ -344,12 +346,13 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
name: variable.name, name: variable.name,
type: type, type: type,
constant: variable.constant, constant: variable.constant,
immutable,
storagelocation: { storagelocation: {
offset: variable.constant ? 0 : storagelocation.offset, offset: !hasStorageSlots ? 0 : storagelocation.offset,
slot: variable.constant ? 0 : storagelocation.slot slot: !hasStorageSlots ? 0 : storagelocation.slot
} }
}) })
if (!variable.constant) { if (hasStorageSlots) {
if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) { if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) {
storagelocation.offset += type.storageBytes storagelocation.offset += type.storageBytes
} else { } else {

@ -15,9 +15,13 @@ export async function decodeState (stateVars, storageResolver) {
try { try {
const decoded = await stateVar.type.decodeFromStorage(stateVar.storagelocation, storageResolver) const decoded = await stateVar.type.decodeFromStorage(stateVar.storagelocation, storageResolver)
decoded.constant = stateVar.constant decoded.constant = stateVar.constant
decoded.immutable = stateVar.immutable
if (decoded.constant) { if (decoded.constant) {
decoded.value = '<constant>' decoded.value = '<constant>'
} }
if (decoded.immutable) {
decoded.value = '<immutable>'
}
ret[stateVar.name] = decoded ret[stateVar.name] = decoded
} catch (e) { } catch (e) {
console.log(e) console.log(e)

@ -49,6 +49,17 @@ export class TraceAnalyser {
this.traceCache.pushReturnValue(index, returnParamsObj) 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) { buildCalldata (index, step, tx, newContext) {

@ -5,6 +5,8 @@ const { sha3_256 } = util
export class TraceCache { export class TraceCache {
returnValues returnValues
stopIndexes
outofgasIndexes
currentCall currentCall
callsTree callsTree
callsData callsData
@ -24,6 +26,8 @@ export class TraceCache {
// ...Changes contains index in the vmtrace of the corresponding changes // ...Changes contains index in the vmtrace of the corresponding changes
this.returnValues = {} this.returnValues = {}
this.stopIndexes = []
this.outofgasIndexes = []
this.currentCall = null this.currentCall = null
this.callsTree = null this.callsTree = null
this.callsData = {} this.callsData = {}
@ -59,7 +63,7 @@ export class TraceCache {
this.currentCall.call.reverted = reverted this.currentCall.call.reverted = reverted
} }
var parent = this.currentCall.parent 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 return
} }
const call = { const call = {
@ -78,6 +82,14 @@ export class TraceCache {
this.currentCall = { call: call, parent: this.currentCall } 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) { pushReturnValue (step, value) {
this.returnValues[step] = value this.returnValues[step] = value
} }

@ -1,9 +1,9 @@
'use strict' 'use strict'
import { util, execution } from '@remix-project/remix-lib'
import { TraceAnalyser } from './traceAnalyser' import { TraceAnalyser } from './traceAnalyser'
import { TraceCache } from './traceCache' import { TraceCache } from './traceCache'
import { TraceStepManager } from './traceStepManager' import { TraceStepManager } from './traceStepManager'
import { isCreateInstruction } from './traceHelper' import { isCreateInstruction } from './traceHelper'
import { util } from '@remix-project/remix-lib'
export class TraceManager { export class TraceManager {
web3 web3
@ -17,7 +17,6 @@ export class TraceManager {
constructor (options) { constructor (options) {
this.web3 = options.web3 this.web3 = options.web3
this.fork = options.fork
this.isLoading = false this.isLoading = false
this.trace = null this.trace = null
this.traceCache = new TraceCache() this.traceCache = new TraceCache()
@ -37,6 +36,15 @@ export class TraceManager {
if (result['structLogs'].length > 0) { if (result['structLogs'].length > 0) {
this.trace = result['structLogs'] 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.traceAnalyser.analyse(result['structLogs'], tx)
this.isLoading = false this.isLoading = false
return true return true
@ -201,6 +209,14 @@ export class TraceManager {
return this.trace[stepIndex].pc return this.trace[stepIndex].pc
} }
getAllStopIndexes () {
return this.traceCache.stopIndexes
}
getAllOutofGasIndexes () {
return this.traceCache.outofgasIndexes
}
getReturnValue (stepIndex) { getReturnValue (stepIndex) {
try { try {
this.checkRequestedStep(stepIndex) this.checkRequestedStep(stepIndex)

@ -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'
}
]
}

@ -57,7 +57,7 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner,
* @param {Object} execResult - execution result given by the VM * @param {Object} execResult - execution result given by the VM
* @return {Object} - { error: true/false, message: DOMNode } * @return {Object} - { error: true/false, message: DOMNode }
*/ */
export function checkVMError (execResult, abi) { export function checkVMError (execResult, abi, contract) {
const errorCode = { const errorCode = {
OUT_OF_GAS: 'out of gas', OUT_OF_GAS: 'out of gas',
STACK_UNDERFLOW: 'stack underflow', STACK_UNDERFLOW: 'stack underflow',
@ -92,7 +92,7 @@ export function checkVMError (execResult, abi) {
const returnDataHex = returnData.slice(0, 4).toString('hex') const returnDataHex = returnData.slice(0, 4).toString('hex')
let customError let customError
if (abi) { if (abi) {
let decodedCustomErrorInputs let decodedCustomErrorInputsClean
for (const item of abi) { for (const item of abi) {
if (item.type === 'error') { if (item.type === 'error') {
// ethers doesn't crash anymore if "error" type is specified, but it doesn't extract the errors. see: // 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 (!sign) continue
if (returnDataHex === sign.replace('0x', '')) { if (returnDataHex === sign.replace('0x', '')) {
customError = item.name 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 break
} }
} }
} }
if (decodedCustomErrorInputs) { if (decodedCustomErrorInputsClean) {
msg = '\tThe transaction has been reverted to the initial state.\nError provided by the contract:' msg = '\tThe transaction has been reverted to the initial state.\nError provided by the contract:'
msg += `\n${customError}` msg += `\n${customError}`
msg += '\nParameters:' msg += '\nParameters:'
msg += `\n${decodedCustomErrorInputs}` msg += `\n${JSON.stringify(decodedCustomErrorInputsClean, null, ' ')}`
} }
} }
if (!customError) { if (!customError) {

@ -13,6 +13,7 @@ import * as txFormat from './execution/txFormat'
import { TxListener } from './execution/txListener' import { TxListener } from './execution/txListener'
import { TxRunner } from './execution/txRunner' import { TxRunner } from './execution/txRunner'
import { LogsManager } from './execution/logsManager' import { LogsManager } from './execution/logsManager'
import { forkAt } from './execution/forkAt'
import * as typeConversion from './execution/typeConversion' import * as typeConversion from './execution/typeConversion'
import { TxRunnerVM } from './execution/txRunnerVM' import { TxRunnerVM } from './execution/txRunnerVM'
import { TxRunnerWeb3 } from './execution/txRunnerWeb3' import { TxRunnerWeb3 } from './execution/txRunnerWeb3'
@ -45,7 +46,8 @@ function modules () {
TxRunnerWeb3: TxRunnerWeb3, TxRunnerWeb3: TxRunnerWeb3,
TxRunnerVM: TxRunnerVM, TxRunnerVM: TxRunnerVM,
typeConversion: typeConversion, typeConversion: typeConversion,
LogsManager LogsManager,
forkAt
} }
} }
} }

@ -1,5 +1,6 @@
'use strict' 'use strict'
import { BN, bufferToHex, keccak, setLengthLeft, toBuffer, addHexPrefix } from 'ethereumjs-util' import { BN, bufferToHex, keccak, setLengthLeft, toBuffer, addHexPrefix } from 'ethereumjs-util'
import stringSimilarity from 'string-similarity'
/* /*
contains misc util: @TODO should be splitted contains misc util: @TODO should be splitted
@ -222,9 +223,11 @@ export function compareByteCode (code1, code2) {
code2 = this.extractSwarmHash(code2) code2 = this.extractSwarmHash(code2)
code2 = this.extractcborMetadata(code2) code2 = this.extractcborMetadata(code2)
if (code1 && code2 && code1.indexOf(code2) === 0) { if (code1 && code2) {
return true const compare = stringSimilarity.compareTwoStrings(code1, code2)
return compare > 0.93
} }
return false return false
} }
/* util extracted out from remix-ide. @TODO split this file, cause it mix real util fn with solidity related stuff ... */ /* util extracted out from remix-ide. @TODO split this file, cause it mix real util fn with solidity related stuff ... */

@ -138,7 +138,7 @@ export class Web3VmProvider {
async txProcessed (data) { async txProcessed (data) {
const lastOp = this.vmTraces[this.processingHash].structLogs[this.processingIndex - 1] const lastOp = this.vmTraces[this.processingHash].structLogs[this.processingIndex - 1]
if (lastOp) { 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) this.vmTraces[this.processingHash].gas = '0x' + data.gasUsed.toString(16)

@ -91,7 +91,6 @@ export class VMContext {
blocks blocks
latestBlockNumber latestBlockNumber
txs txs
defaultFork
currentVm currentVm
web3vm web3vm
logsManager logsManager
@ -100,8 +99,7 @@ export class VMContext {
constructor (fork?) { constructor (fork?) {
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
this.defaultFork = fork || 'berlin' this.currentFork = fork || 'berlin'
this.currentFork = this.defaultFork
this.currentVm = this.createVm(this.currentFork) this.currentVm = this.createVm(this.currentFork)
this.blocks = {} this.blocks = {}
this.latestBlockNumber = 0 this.latestBlockNumber = 0

@ -150,7 +150,7 @@ export interface CompilerInputOptions {
language?: Language 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' export type Language = 'Solidity' | 'Yul'

@ -6,6 +6,7 @@ import './copy-to-clipboard.css'
export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => { export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => {
const [message, setMessage] = useState(tip) const [message, setMessage] = useState(tip)
const handleClick = (event) => { 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 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 { try {

@ -208,8 +208,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
} }
return null return null
}, },
debugWithGeneratedSources: state.opt.debugWithGeneratedSources, debugWithGeneratedSources: state.opt.debugWithGeneratedSources
fork: 'berlin'
}) })
debuggerInstance.debug(blockNumber, txNumber, tx, () => { debuggerInstance.debug(blockNumber, txNumber, tx, () => {
@ -275,7 +274,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="px-2"> <div className="px-2">
<div> <div>
<p className="my-2 debuggerLabel">Debugger Configuration</p> <p className="my-2 debuggerLabel">Debugger Configuration</p>
<div className="mt-2 debuggerConfig custom-control custom-checkbox"> <div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => { <input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => { setState(prevState => {
return { ...prevState, opt: { debugWithGeneratedSources: checked } } return { ...prevState, opt: { debugWithGeneratedSources: checked } }

@ -4,41 +4,116 @@ import './styles/assembly-items.css'
export const AssemblyItems = ({ registerEvent }) => { export const AssemblyItems = ({ registerEvent }) => {
const [assemblyItems, dispatch] = useReducer(reducer, initialState) const [assemblyItems, dispatch] = useReducer(reducer, initialState)
const [absoluteSelectedIndex, setAbsoluteSelectedIndex] = useState(0)
const [selectedItem, setSelectedItem] = useState(0) const [selectedItem, setSelectedItem] = useState(0)
const [nextSelectedItem, setNextSelectedItem] = useState(1)
const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([])
const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([])
const refs = useRef({}) const refs = useRef({})
const asmItemsRef = useRef(null) const asmItemsRef = useRef(null)
useEffect(() => { useEffect(() => {
registerEvent && registerEvent('codeManagerChanged', (code, address, index) => { registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index } }) dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes } })
}) })
}, []) }, [])
useEffect(() => { useEffect(() => {
if (selectedItem !== assemblyItems.index) { if (absoluteSelectedIndex !== assemblyItems.index) {
clearItems()
indexChanged(assemblyItems.index) indexChanged(assemblyItems.index)
nextIndexChanged(assemblyItems.nextIndex)
returnIndexes(assemblyItems.returnInstructionIndexes)
outOfGasIndexes(assemblyItems.outOfGasInstructionIndexes)
} }
}, [assemblyItems.index]) }, [assemblyItems.opCodes.index])
const indexChanged = (index: number) => {
if (index < 0) return
let currentItem = refs.current[selectedItem] ? refs.current[selectedItem] : null
const clearItem = (currentItem) => {
if (currentItem) { if (currentItem) {
currentItem.removeAttribute('selected') currentItem.removeAttribute('selected')
currentItem.removeAttribute('style') currentItem.removeAttribute('style')
if (currentItem.firstChild) { if (currentItem.firstChild) {
currentItem.firstChild.removeAttribute('style') 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] const codeView = asmItemsRef.current
currentItem.style.setProperty('border-color', 'var(--primary)')
currentItem.style.setProperty('border-style', 'solid') const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('background-color', 'var(--primary)')
currentItem.style.setProperty('color', 'var(--light)')
currentItem.setAttribute('selected', 'selected') currentItem.setAttribute('selected', 'selected')
codeView.scrollTop = currentItem.offsetTop - parseInt(codeView.offsetTop) 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 ( return (

@ -7,7 +7,7 @@ import './styles/dropdown-panel.css'
export const DropdownPanel = (props: DropdownPanelProps) => { export const DropdownPanel = (props: DropdownPanelProps) => {
const [calldataObj, dispatch] = useReducer(reducer, initialState) 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 extractDataDefault: ExtractFunc = (item, parent?) => {
const ret: ExtractData = {} const ret: ExtractData = {}
@ -34,10 +34,21 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
return ret return ret
} }
const formatSelfDefault = (key: string | number, data: ExtractData) => { 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 = (<span><span className="m-0 label_value">0x</span><span className="m-0 label_value">{split[1]}</span>{ split[2] && <span className="m-0 label_value font-weight-bold text-dark">{split[2]}</span> }</span>)
} else value = (<span><span className="m-0 label_value">0x</span><span className="m-0 label_value font-weight-bold text-dark">{data.self.replace('0x', '')}</span></span>)
} else value = <span className="m-0 label_value">{data.self}</span>
} else value = <span className="m-0 label_value">{data.self}</span>
return ( return (
<div className="d-flex mr-1 flex-row label_item"> <div className="d-flex mr-1 flex-row label_item">
<label className="small font-weight-bold mb-0 pr-1 label_key">{key}:</label> <label className="small font-weight-bold mb-0 pr-1 label_key">{key}:</label>
<label className="m-0 label_value">{data.self}</label> <label className="m-0 label_value">{value}</label>
</div> </div>
) )
} }
@ -184,14 +195,14 @@ export const DropdownPanel = (props: DropdownPanelProps) => {
return ( return (
<div className="border rounded px-1 mt-1 bg-light"> <div className="border rounded px-1 mt-1 bg-light">
<div className="py-0 px-1 title"> <div className="py-0 px-1 title" style={headStyle}>
<div className={state.toggleDropdown ? 'icon fas fa-caret-down' : 'icon fas fa-caret-right'} onClick={handleToggle}></div> <div className={state.toggleDropdown ? 'icon fas fa-caret-down' : 'icon fas fa-caret-right'} onClick={handleToggle}></div>
<div className="name" data-id={`dropdownPanel${uniquePanelName}`} onClick={handleToggle}>{dropdownName}</div><span className="nameDetail" onClick={handleToggle}>{header}</span> <div className="name" data-id={`dropdownPanel${uniquePanelName}`} onClick={handleToggle}>{dropdownName}</div><span className="nameDetail" onClick={handleToggle}>{header}</span>
<CopyToClipboard content={state.copiableContent} data-id={`dropdownPanelCopyToClipboard${uniquePanelName}`} /> <CopyToClipboard content={state.copiableContent} data-id={`dropdownPanelCopyToClipboard${uniquePanelName}`} />
</div> </div>
<div className='dropdownpanel' style={{ display: state.toggleDropdown ? 'block' : 'none' }}> <div className='dropdownpanel' style={{ display: state.toggleDropdown ? 'block' : 'none' }}>
<i className="refresh fas fa-sync" style={{ display: state.updating ? 'inline-block' : 'none' }} aria-hidden="true"></i> <i className="refresh fas fa-sync" style={{ display: state.updating ? 'inline-block' : 'none' }} aria-hidden="true"></i>
<div className='dropdowncontent' style={{ display: state.dropdownContent.display }}> <div className='dropdowncontent' style={{ display: state.dropdownContent.display, ...bodyStyle }}>
{ {
state.data && state.data &&
<TreeView id="treeView"> <TreeView id="treeView">

@ -3,7 +3,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const MemoryPanel = ({ calldata }) => { export const MemoryPanel = ({ calldata }) => {
return ( return (
<DropdownPanel dropdownName='Memory' calldata={calldata || {}} /> <DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Memory' calldata={calldata || {}} />
) )
} }

@ -5,32 +5,36 @@ import { ExtractData } from '../../types' // eslint-disable-line
export const SolidityState = ({ calldata, message }) => { export const SolidityState = ({ calldata, message }) => {
const formatSelf = (key: string, data: ExtractData) => { const formatSelf = (key: string, data: ExtractData) => {
let color = 'var(--primary)' try {
if (data.isArray || data.isStruct || data.isMapping) { let color = 'var(--primary)'
color = 'var(--info)' if (data.isArray || data.isStruct || data.isMapping) {
} else if ( color = 'var(--info)'
data.type.indexOf('uint') === 0 || } else if (
data.type.indexOf('uint') === 0 ||
data.type.indexOf('int') === 0 || data.type.indexOf('int') === 0 ||
data.type.indexOf('bool') === 0 || data.type.indexOf('bool') === 0 ||
data.type.indexOf('enum') === 0 data.type.indexOf('enum') === 0
) { ) {
color = 'var(--green)' color = 'var(--green)'
} else if (data.type === 'string') { } else if (data.type === 'string') {
color = 'var(--teal)' color = 'var(--teal)'
} else if (data.self == 0x0) { // eslint-disable-line } else if (data.self == 0x0) { // eslint-disable-line
color = 'var(--gray)' color = 'var(--gray)'
} }
return ( return (
<label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}> <label className='mb-0' style={{ color: data.isProperty ? 'var(--info)' : '', whiteSpace: 'pre-wrap' }}>
{' ' + key}: {' ' + key}:
<label className='mb-0' style={{ color }}> <label className='mb-0' style={{ color }}>
{' ' + data.self} {' ' + data.self}
</label> </label>
<label style={{ fontStyle: 'italic' }}> <label style={{ fontStyle: 'italic' }}>
{data.isProperty || !data.type ? '' : ' ' + data.type} {data.isProperty || !data.type ? '' : ' ' + data.type}
</label>
</label> </label>
</label> )
) } catch (e) {
return (<></>)
}
} }
return ( return (

@ -4,7 +4,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StackPanel = ({ calldata }) => { export const StackPanel = ({ calldata }) => {
return ( return (
<div id='stackpanel'> <div id='stackpanel'>
<DropdownPanel dropdownName='Stack' calldata={calldata || {}} /> <DropdownPanel hexHighlight={true} bodyStyle={{ fontFamily: 'monospace' }} dropdownName='Stack' calldata={calldata || {}} />
</div> </div>
) )
} }

@ -4,7 +4,7 @@ import DropdownPanel from './dropdown-panel' // eslint-disable-line
export const StepDetail = ({ stepDetail }) => { export const StepDetail = ({ stepDetail }) => {
return ( return (
<div id='stepdetail' data-id='stepdetail'> <div id='stepdetail' data-id='stepdetail'>
<DropdownPanel dropdownName='Step details' calldata={stepDetail || {}} /> <DropdownPanel hexHighlight={false} dropdownName='Step details' calldata={stepDetail || {}} />
</div> </div>
) )
} }

@ -13,6 +13,9 @@ export const initialState = {
}, },
display: [], display: [],
index: 0, index: 0,
nextIndex: -1,
returnInstructionIndexes: [],
outOfGasInstructionIndexes: [],
top: 0, top: 0,
bottom: 0, bottom: 0,
isRequesting: false, isRequesting: false,
@ -20,6 +23,20 @@ export const initialState = {
hasError: null hasError: null
} }
const reducedOpcode = (opCodes, payload) => {
const length = 100
let bottom = opCodes.index - 10
bottom = bottom < 0 ? 0 : bottom
const top = bottom + length
return {
index: opCodes.index - bottom,
nextIndex: opCodes.nextIndex - bottom,
display: opCodes.code.slice(bottom, top),
returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom),
outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom)
}
}
export const reducer = (state = initialState, action: Action) => { export const reducer = (state = initialState, action: Action) => {
switch (action.type) { switch (action.type) {
case 'FETCH_OPCODES_REQUEST': { case 'FETCH_OPCODES_REQUEST': {
@ -32,21 +49,20 @@ export const reducer = (state = initialState, action: Action) => {
} }
case 'FETCH_OPCODES_SUCCESS': { case 'FETCH_OPCODES_SUCCESS': {
const opCodes = action.payload.address === state.opCodes.address ? { const opCodes = action.payload.address === state.opCodes.address ? {
...state.opCodes, index: action.payload.index ...state.opCodes, index: action.payload.index, nextIndex: action.payload.nextIndex
} : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload } : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload
const top = opCodes.index - 3 > 0 ? opCodes.index - 3 : 0
const bottom = opCodes.index + 4 < opCodes.code.length ? opCodes.index + 4 : opCodes.code.length
const display = opCodes.code.slice(top, bottom)
const reduced = reducedOpcode(opCodes, action.payload)
return { return {
opCodes, opCodes,
display, display: reduced.display,
index: display.findIndex(code => code === opCodes.code[opCodes.index]), index: reduced.index,
top, nextIndex: reduced.nextIndex,
bottom,
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
hasError: null hasError: null,
returnInstructionIndexes: reduced.returnInstructionIndexes,
outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes
} }
} }
case 'FETCH_OPCODES_ERROR': { case 'FETCH_OPCODES_ERROR': {

@ -27,7 +27,10 @@ export interface DropdownPanelProps {
registerEvent?: Function, registerEvent?: Function,
triggerEvent?: Function, triggerEvent?: Function,
loadMoreEvent?: string, loadMoreEvent?: string,
loadMoreCompletedEvent?: string loadMoreCompletedEvent?: string,
bodyStyle?: React.CSSProperties,
headStyle?: React.CSSProperties,
hexHighlight?: boolean // highlight non zero value of hex value
} }
export type FormatSelfFunc = (key: string | number, data: ExtractData) => JSX.Element export type FormatSelfFunc = (key: string | number, data: ExtractData) => JSX.Element

@ -4,41 +4,45 @@ import { ExtractData } from '../types' // eslint-disable-line
export function extractData (item, parent): ExtractData { export function extractData (item, parent): ExtractData {
const ret: ExtractData = {} const ret: ExtractData = {}
if (item.isProperty) { if (item.isProperty || !item.type) {
return item return item
} }
if (item.type.lastIndexOf(']') === item.type.length - 1) { try {
ret.children = (item.value || []).map(function (item, index) { if (item.type.lastIndexOf(']') === item.type.length - 1) {
return { key: index, value: item } ret.children = (item.value || []).map(function (item, index) {
}) return { key: index, value: item }
ret.children.unshift({ })
key: 'length', ret.children.unshift({
value: { key: 'length',
self: (new BN(item.length.replace('0x', ''), 16)).toString(10), value: {
type: 'uint', self: (new BN(item.length.replace('0x', ''), 16)).toString(10),
isProperty: true type: 'uint',
} isProperty: true
}) }
ret.isArray = true })
ret.self = parent.isArray ? '' : item.type ret.isArray = true
ret.cursor = item.cursor ret.self = parent.isArray ? '' : item.type
ret.hasNext = item.hasNext ret.cursor = item.cursor
} else if (item.type.indexOf('struct') === 0) { ret.hasNext = item.hasNext
ret.children = Object.keys((item.value || {})).map(function (key) { } else if (item.type.indexOf('struct') === 0) {
return { key: key, value: item.value[key] } ret.children = Object.keys((item.value || {})).map(function (key) {
}) return { key: key, value: item.value[key] }
ret.self = item.type })
ret.isStruct = true ret.self = item.type
} else if (item.type.indexOf('mapping') === 0) { ret.isStruct = true
ret.children = Object.keys((item.value || {})).map(function (key) { } else if (item.type.indexOf('mapping') === 0) {
return { key: key, value: item.value[key] } ret.children = Object.keys((item.value || {})).map(function (key) {
}) return { key: key, value: item.value[key] }
ret.isMapping = true })
ret.self = item.type ret.isMapping = true
} else { ret.self = item.type
ret.children = null } else {
ret.self = item.value ret.children = null
ret.type = item.type ret.self = item.value
ret.type = item.type
}
} catch (e) {
console.log(e)
} }
return ret return ret
} }

@ -2,6 +2,7 @@ import React, { useRef, useEffect } from 'react' // eslint-disable-line
import { action, FileExplorerContextMenuProps } from './types' import { action, FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css' import './css/file-explorer-context-menu.css'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => { export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props
@ -96,7 +97,7 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
deletePath(getPath()) deletePath(getPath())
break break
default: default:
emit && emit(item.id, getPath()) emit && emit({ ...item, path: [path] } as customAction)
break break
} }
hideContextMenu() hideContextMenu()

@ -11,13 +11,14 @@ import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem' import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper' import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params' import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
import './css/file-explorer.css' import './css/file-explorer.css'
const queryParams = new QueryParams() const queryParams = new QueryParams()
export const FileExplorer = (props: FileExplorerProps) => { export const FileExplorer = (props: FileExplorerProps) => {
const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads } = props const { name, registry, plugin, focusRoot, contextMenuItems, displayInput, externalUploads, removedContextMenuItems } = props
const [state, setState] = useState({ const [state, setState] = useState({
focusElement: [{ focusElement: [{
key: '', key: '',
@ -203,6 +204,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
}, [contextMenuItems]) }, [contextMenuItems])
useEffect(() => {
if (removedContextMenuItems) {
removeMenuItems(removedContextMenuItems)
}
}, [contextMenuItems])
useEffect(() => { useEffect(() => {
if (displayInput) { if (displayInput) {
handleNewFileInput() handleNewFileInput()
@ -278,7 +285,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
multiselect: false multiselect: false
}]) }])
} else { } else {
removeMenuItems(['paste']) removeMenuItems([{
id: 'paste',
name: 'Paste',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: [],
multiselect: false
}])
} }
}, [canPaste]) }, [canPaste])
@ -291,10 +306,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
}) })
} }
const removeMenuItems = (ids: string[]) => { const removeMenuItems = (items: MenuItems) => {
setState(prevState => { setState(prevState => {
const actions = prevState.actions.filter(({ id }) => ids.findIndex(value => value === id) === -1) const actions = prevState.actions.filter(({ id, name }) => items.findIndex(item => id === item.id && name === item.name) === -1)
return { ...prevState, actions } return { ...prevState, actions }
}) })
} }
@ -601,8 +615,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
}) })
} }
const emitContextMenuEvent = (id: string, path: string | string[]) => { const emitContextMenuEvent = (cmd: customAction) => {
plugin.emit(id, path) plugin.call(cmd.id, cmd.name, cmd)
} }
const handleHideModal = () => { const handleHideModal = () => {

@ -1,3 +1,5 @@
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface FileExplorerProps { export interface FileExplorerProps {
name: string, name: string,
@ -7,8 +9,9 @@ export interface FileExplorerProps {
plugin: any, plugin: any,
focusRoot: boolean, focusRoot: boolean,
contextMenuItems: MenuItems, contextMenuItems: MenuItems,
removedContextMenuItems: MenuItems,
displayInput?: boolean, displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement externalUploads?: EventTarget & HTMLInputElement,
} }
export interface File { export interface File {
@ -44,7 +47,7 @@ export interface FileExplorerContextMenuProps {
publishFolderToGist?: (path?: string, type?: string) => void, publishFolderToGist?: (path?: string, type?: string) => void,
publishFileToGist?: (path?: string, type?: string) => void, publishFileToGist?: (path?: string, type?: string) => void,
runScript?: (path: string) => void, runScript?: (path: string) => void,
emit?: (id: string, path: string | string[]) => void, emit?: (cmd: customAction) => void,
pageX: number, pageX: number,
pageY: number, pageY: number,
path: string, path: string,

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-settings
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-settings` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-settings'

@ -0,0 +1,12 @@
export const generateContractMetadataText = 'Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.'
export const textSecondary = 'text-secondary'
export const textDark = 'text-dark'
export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a 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 persists any passphrase'.split('\n').map(s => s.trim()).join(' ')
export const gitAccessTokenTitle = 'Github Access Token'
export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve Github contents.'
export const gitAccessTokenText2 = '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.'
export const gitAccessTokenLink = 'https://github.com/settings/tokens'
export const ethereunVMText = 'Always use Ethereum VM at load'
export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '

@ -0,0 +1,167 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, generateContractMetadataText, gitAccessTokenLink, gitAccessTokenText, gitAccessTokenText2, gitAccessTokenTitle, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText } from './constants'
import './remix-ui-settings.css'
import { etherumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast } from './settingsAction'
import { initialState, toastInitialState, toastReducer, settingReducer } from './settingsReducer'
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
/* eslint-disable-next-line */
export interface RemixUiSettingsProps {
config: any,
editor: any,
_deps: any
}
export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [, dispatch] = useReducer(settingReducer, initialState)
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState)
const [tokenValue, setTokenValue] = useState('')
const [themeName, setThemeName] = useState('')
useEffect(() => {
props._deps.themeModule.switchTheme()
const token = props.config.get('settings/gist-access-token')
if (token === undefined) {
props.config.set('settings/generate-contract-metadata', true)
dispatch({ type: 'contractMetadata', payload: { name: 'contractMetadata', isChecked: true, textClass: textDark } })
}
if (token) {
setTokenValue(token)
}
}, [themeName, state.message])
const onchangeGenerateContractMetadata = (event) => {
generateContractMetadat(props, event, dispatch)
}
const onchangeOption = (event) => {
etherumVM(props, event, dispatch)
}
const textWrapEvent = (event) => {
textWrapEventAction(props, event, dispatch)
}
const onchangePersonal = event => {
personal(props, event, dispatch)
}
const onchangeMatomoAnalytics = event => {
useMatomoAnalytics(props, event, dispatch)
}
const onswitchTheme = (event, name) => {
props._deps.themeModule.switchTheme(name)
setThemeName(name)
}
const getTextClass = (key) => {
if (props.config.get(key)) {
return textDark
} else {
return textSecondary
}
}
const generalConfig = () => (
<div className="$border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">General settings</h6>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" className="custom-control-input" name="contractMetadata" checked = { props.config.get('settings/generate-contract-metadata') }/>
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/generate-contract-metadata')}`} data-id="settingsTabGenerateContractMetadataLabel" htmlFor="generatecontractmetadata">{generateContractMetadataText}</label>
</div>
<div className="fmt-2 custom-control custom-checkbox mb-1">
<input onChange={onchangeOption} className="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox" name="ethereumVM" checked={ props.config.get('settings/always-use-vm') }/>
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/always-use-vm')}`} htmlFor="alwaysUseVM">{ethereunVMText}</label>
</div>
<div className="mt-2 custom-control custom-checkbox mb-1">
<input id="editorWrap" className="custom-control-input" type="checkbox" onChange={textWrapEvent} checked = { props.config.get('settings/text-wrap')}/>
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/text-wrap')}`} htmlFor="editorWrap">{wordWrapText}</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangePersonal} id="personal" type="checkbox" className="custom-control-input" checked = { props.config.get('settings/personal-mode')}/>
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/personal-mode')}`} htmlFor="personal">
<i className="fas fa-exclamation-triangle text-warning" aria-hidden="true"></i> <span> </span>
<span> </span>{enablePersonalModeText} {warnText}
</label>
</div>
<div className="custom-control custom-checkbox mb-1">
<input onChange={onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" className="custom-control-input" checked={ props.config.get('settings/matomo-analytics')}/>
<label className={`form-check-label custom-control-label align-middle ${getTextClass('settings/matomo-analytics')}`} htmlFor="settingsMatomoAnalytics">
<span>{matomoAnalytics}</span>
<a href="https://medium.com/p/66ef69e14931/" target="_blank"> Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a>
</label>
</div>
</div>
</div>
)
const saveToken = () => {
saveTokenToast(props, dispatchToast, tokenValue)
}
const removeToken = () => {
setTokenValue('')
removeTokenToast(props, dispatchToast)
}
const handleSaveTokenState = useCallback(
(event) => {
setTokenValue(event.target.value)
},
[tokenValue]
)
const gistToken = () => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ gitAccessTokenTitle }</h6>
<p className="mb-1">{ gitAccessTokenText }</p>
<p className="">{ gitAccessTokenText2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href="https://github.com/settings/tokens">{ gitAccessTokenLink }</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={handleSaveTokenState} value={ tokenValue } />
<div className="d-flex justify-content-end pt-2">
<CopyToClipboard content={tokenValue} data-id='copyToClipboardCopyIcon' />
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken()} value="Save" type="button" disabled={tokenValue === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken()}>Remove</button>
</div>
</div></div>
</div>
</div>
)
const themes = () => {
const themes = props._deps.themeModule.getThemes()
if (themes) {
return themes.map((aTheme, index) => (
<div className="radio custom-control custom-radio mb-1 form-check" key={index}>
<input type="radio" onChange={event => { onswitchTheme(event, aTheme.name) }} className="align-middle custom-control-input" name='theme' id={aTheme.name} data-id={`settingsTabTheme${aTheme.name}`} checked = {props._deps.themeModule.active === aTheme.name }/>
<label className="form-check-label custom-control-label" data-id={`settingsTabThemeLabel${aTheme.name}`} htmlFor={aTheme.name}>{aTheme.name} ({aTheme.quality})</label>
</div>
)
)
}
}
return (
<div>
{state.message ? <Toaster message= {state.message}/> : null}
{generalConfig()}
{gistToken()}
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">Themes</h6>
<div className="card-text themes-container">
{themes()}
</div>
</div>
</div>
</div>
)
}

@ -0,0 +1,52 @@
import { textDark, textSecondary } from './constants'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
export const generateContractMetadat = (element, event, dispatch) => {
element.config.set('settings/generate-contract-metadata', event.target.checked)
dispatch({ type: 'contractMetadata', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } })
}
export const etherumVM = (element, event, dispatch) => {
element.config.set('settings/always-use-vm', event.target.checked)
dispatch({ type: 'ethereumVM', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } })
}
export const textWrapEventAction = (element, event, dispatch) => {
element.config.set('settings/text-wrap', event.target.checked)
element.editor.resize(event.target.checked)
dispatch({ type: 'textWrap', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } })
}
export const personal = (element, event, dispatch) => {
element.config.set('settings/personal-mode', event.target.checked)
dispatch({ type: 'personal', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } })
}
export const useMatomoAnalytics = (element, event, dispatch) => {
element.config.set('settings/matomo-analytics', event.target.checked)
dispatch({ type: 'useMatomoAnalytics', payload: { name: event.target.name, isChecked: event.target.checked, textClass: event.target.checked ? textDark : textSecondary } })
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'])
}
}
export const saveTokenToast = (props, dispatch, tokenValue) => {
props.config.set('settings/gist-access-token', tokenValue)
dispatch({ type: 'save', payload: { message: 'Access token has been saved' } })
}
export const removeTokenToast = (props, dispatch) => {
props.config.set('settings/gist-access-token', '')
dispatch({ type: 'removed', payload: { message: 'Access token removed' } })
}

@ -0,0 +1,103 @@
import { textSecondary } from './constants'
export const initialState = {
elementState: [
{
name: 'contractMetadata',
isChecked: false,
textClass: textSecondary
},
{
name: 'ethereumVM',
isChecked: false,
textClass: textSecondary
},
{
name: 'textWrap',
isChecked: false,
textClass: textSecondary
},
{
name: 'personal',
isChecked: false,
textClass: textSecondary
},
{
name: 'useMatomoAnalytics',
isChecked: false,
textClass: textSecondary
}
]
}
export const settingReducer = (state, action) => {
switch (action.type) {
case 'contractMetadata':
state.elementState.map(element => {
if (element.name === 'contractMetadata') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'ethereumVM':
state.elementState.map(element => {
if (element.name === 'ethereumVM') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'textWrap':
state.elementState.map(element => {
if (element.name === 'textWrap') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'personal':
state.elementState.map(element => {
if (element.name === 'personal') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
case 'useMatomoAnalytics':
state.elementState.map(element => {
if (element.name === 'useMatomoAnalytics') {
element.isChecked = action.payload.isChecked
element.textClass = action.payload.textClass
}
})
return {
...state
}
default:
return initialState
}
}
export const toastInitialState = {
message: ''
}
export const toastReducer = (state, action) => {
switch (action.type) {
case 'save' :
return { ...state, message: action.payload.message }
case 'removed' :
return { ...state, message: action.payload.message }
default :
return { ...state, message: '' }
}
}

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -3,6 +3,7 @@ import { FileExplorer } from '@remix-ui/file-explorer' // eslint-disable-line
import './remix-ui-workspace.css' import './remix-ui-workspace.css'
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster'// eslint-disable-line import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
import { MenuItems } from 'libs/remix-ui/file-explorer/src/lib/types'
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface WorkspaceProps { export interface WorkspaceProps {
@ -20,7 +21,8 @@ export interface WorkspaceProps {
plugin: any // plugin call and resetFocus plugin: any // plugin call and resetFocus
request: any // api request, request: any // api request,
workspaces: any, workspaces: any,
registeredMenuItems: [] // menu items registeredMenuItems: MenuItems // menu items
removedMenuItems: MenuItems
initialWorkspace: string initialWorkspace: string
} }
@ -409,6 +411,7 @@ export const Workspace = (props: WorkspaceProps) => {
plugin={props.plugin} plugin={props.plugin}
focusRoot={state.reset} focusRoot={state.reset}
contextMenuItems={props.registeredMenuItems} contextMenuItems={props.registeredMenuItems}
removedContextMenuItems={props.removedMenuItems}
displayInput={state.displayNewFile} displayInput={state.displayNewFile}
externalUploads={state.uploadFileEvent} externalUploads={state.uploadFileEvent}
/> />
@ -426,6 +429,7 @@ export const Workspace = (props: WorkspaceProps) => {
plugin={props.plugin} plugin={props.plugin}
focusRoot={state.reset} focusRoot={state.reset}
contextMenuItems={props.registeredMenuItems} contextMenuItems={props.registeredMenuItems}
removedContextMenuItems={props.removedMenuItems}
/> />
} }
</div> </div>

@ -96,6 +96,9 @@
"remix-ui-workspace": { "remix-ui-workspace": {
"tags": [] "tags": []
}, },
"remix-ui-settings": {
"tags": []
},
"remix-ui-static-analyser": { "remix-ui-static-analyser": {
"tags": [] "tags": []
}, },
@ -103,7 +106,6 @@
"tags": [] "tags": []
}, },
"remix-ui-terminal": { "remix-ui-terminal": {
"tags": []
} }
} }
} }

5
package-lock.json generated

@ -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": { "string-width": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",

@ -163,7 +163,6 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"jszip": "^3.6.0", "jszip": "^3.6.0",
"latest-version": "^5.1.0", "latest-version": "^5.1.0",
"lodash": "^4.17.21",
"merge": "^1.2.0", "merge": "^1.2.0",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"react": "16.13.1", "react": "16.13.1",
@ -172,12 +171,13 @@
"react-dom": "16.13.1", "react-dom": "16.13.1",
"selenium": "^2.20.0", "selenium": "^2.20.0",
"signale": "^1.4.0", "signale": "^1.4.0",
"string-similarity": "^4.0.4",
"time-stamp": "^2.2.0", "time-stamp": "^2.2.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"web3": "1.2.4", "web3": "1.2.4",
"winston": "^3.3.3", "winston": "^3.3.3",
"ws": "^7.3.0" "ws": "^7.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.4.5",
"@babel/plugin-transform-modules-amd": "^7.10.4", "@babel/plugin-transform-modules-amd": "^7.10.4",

@ -39,6 +39,7 @@
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"], "@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/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/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/static-analyser": ["libs/remix-ui/static-analyser/src/index.ts"],
"@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"], "@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"],
"@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"] "@remix-ui/terminal": ["libs/remix-ui/terminal/src/index.ts"]

@ -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": { "remix-ui-static-analyser": {
"root": "libs/remix-ui/static-analyser", "root": "libs/remix-ui/static-analyser",
"sourceRoot": "libs/remix-ui/static-analyser/src", "sourceRoot": "libs/remix-ui/static-analyser/src",
@ -863,4 +879,4 @@
} }
}, },
"defaultProject": "remix-ide" "defaultProject": "remix-ide"
} }
Loading…
Cancel
Save