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

pull/1182/head
filip mertens 3 years ago
commit b819e601ae
  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. 2
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  5. 17
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  6. 21
      apps/remix-ide-e2e/src/tests/gist.spec.ts
  7. 2
      apps/remix-ide-e2e/src/tests/pluginManager.spec.ts
  8. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  9. 4
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  10. 22
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  11. 17
      apps/remix-ide/src/app/panels/file-panel.js
  12. 13
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  13. 221
      apps/remix-ide/src/app/tabs/settings-tab.js
  14. 2
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  15. 21
      apps/remix-ide/src/app/ui/txLogger.js
  16. 8
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  17. 5
      apps/remix-ide/src/blockchain/blockchain.js
  18. 40
      apps/remix-ide/src/blockchain/execution-context.js
  19. 2
      apps/remix-ide/src/remixAppManager.js
  20. 4
      libs/remix-debug/src/Ethdebugger.ts
  21. 27
      libs/remix-debug/src/code/codeManager.ts
  22. 4
      libs/remix-debug/src/debugger/VmDebugger.ts
  23. 3
      libs/remix-debug/src/debugger/debugger.ts
  24. 11
      libs/remix-debug/src/solidity-decoder/decodeInfo.ts
  25. 4
      libs/remix-debug/src/solidity-decoder/stateDecoder.ts
  26. 2
      libs/remix-debug/src/solidity-decoder/types/Mapping.ts
  27. 3
      libs/remix-debug/src/solidity-decoder/types/Struct.ts
  28. 11
      libs/remix-debug/src/trace/traceAnalyser.ts
  29. 14
      libs/remix-debug/src/trace/traceCache.ts
  30. 20
      libs/remix-debug/src/trace/traceManager.ts
  31. 121
      libs/remix-lib/src/execution/forkAt.ts
  32. 30
      libs/remix-lib/src/execution/txExecution.ts
  33. 4
      libs/remix-lib/src/index.ts
  34. 7
      libs/remix-lib/src/util.ts
  35. 2
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  36. 4
      libs/remix-simulator/src/vm-context.ts
  37. 2
      libs/remix-solidity/src/compiler/types.ts
  38. 11
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  39. 5
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  40. 101
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  41. 19
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/dropdown-panel.tsx
  42. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/memory-panel.tsx
  43. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/stack-panel.tsx
  44. 2
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/step-detail.tsx
  45. 34
      libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts
  46. 5
      libs/remix-ui/debugger-ui/src/types/index.ts
  47. 8
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  48. 3
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  49. 4
      libs/remix-ui/settings/.babelrc
  50. 19
      libs/remix-ui/settings/.eslintrc
  51. 7
      libs/remix-ui/settings/README.md
  52. 1
      libs/remix-ui/settings/src/index.ts
  53. 12
      libs/remix-ui/settings/src/lib/constants.ts
  54. 0
      libs/remix-ui/settings/src/lib/remix-ui-settings.css
  55. 167
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  56. 52
      libs/remix-ui/settings/src/lib/settingsAction.ts
  57. 103
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  58. 16
      libs/remix-ui/settings/tsconfig.json
  59. 13
      libs/remix-ui/settings/tsconfig.lib.json
  60. 18
      libs/remixd/README.md
  61. 6
      libs/remixd/src/bin/remixd.ts
  62. 6
      nx.json
  63. 13
      package-lock.json
  64. 7
      package.json
  65. 4
      tsconfig.json
  66. 16
      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()
} }
} }

@ -74,7 +74,7 @@ module.exports = {
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]') .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]')
}, },
'Should publish all explorer files to github gist': function (browser: NightwatchBrowser) { 'Should publish all explorer files to github gist': '' + function (browser: NightwatchBrowser) {
const runtimeBrowser = browser.options.desiredCapabilities.browserName const runtimeBrowser = browser.options.desiredCapabilities.browserName
browser.refresh() browser.refresh()

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

@ -20,7 +20,7 @@ module.exports = {
- switch to a file in the new gist - switch to a file in the new gist
*/ */
console.log('token', process.env.gist_token) console.log('token', process.env.gist_token)
const gistid = 'c15ed7c182a7991ea0a4dea1544fa254'
browser browser
.refresh() .refresh()
.pause(10000) .pause(10000)
@ -34,12 +34,18 @@ module.exports = {
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER) .sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.addFile('File.sol', { content: '' }) .addFile('File.sol', { content: '' })
.click('*[data-id="fileExplorerNewFilepublishToGist"]') .executeScript(`remix.loadgist('${gistid}')`)
.pause(2000) // .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]') .waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok') .click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.pause(10000) .openFile(`gist-${gistid}/README.txt`)
.getText('[data-id="default_workspaceModalDialogModalBody-react"]', (result) => { // Remix publish to gist
/* .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.pause(2000)
.waitForElementVisible('*[data-id="default_workspaceModalDialogContainer-react"]')
.click('*[data-id="default_workspaceModalDialogContainer-react"] .modal-ok')
.pause(10000)
.getText('[data-id="default_workspaceModalDialogModalBody-react"]', (result) => {
console.log(result) console.log(result)
const value = typeof result.value === 'string' ? result.value : null const value = typeof result.value === 'string' ? result.value : null
const reg = /gist.github.com\/([^.]+)/ const reg = /gist.github.com\/([^.]+)/
@ -59,6 +65,7 @@ module.exports = {
.openFile(`gist-${gistid}/README.txt`) .openFile(`gist-${gistid}/README.txt`)
} }
}) })
*/
}, },
'Load Gist Modal': function (browser: NightwatchBrowser) { 'Load Gist Modal': function (browser: NightwatchBrowser) {

@ -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"]')

@ -177,10 +177,10 @@ module.exports = {
.pause(1000) .pause(1000)
.perform((done) => { .perform((done) => {
browser.getAddressAtPosition(4, (address) => { browser.getAddressAtPosition(4, (address) => {
browser.sendLowLevelTx(address, '1', '0xaa') browser.sendLowLevelTx(address, '999999998765257135', '0xaa')
.pause(1000) .pause(1000)
.journalLastChildIncludes('to: CheckSpecials.(fallback)') .journalLastChildIncludes('to: CheckSpecials.(fallback)')
.journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('value: 999999998765257135 wei')
.journalLastChildIncludes('data: 0xaa') .journalLastChildIncludes('data: 0xaa')
.perform(done) .perform(done)
}) })

@ -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 {

@ -60,6 +60,7 @@ 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
@ -89,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)
@ -106,6 +108,18 @@ module.exports = class Filepanel extends ViewPlugin {
return o.id === item.id & o.name === item.name return o.id === item.id & o.name === item.name
}).length) throw new Error(`Action ${item.name} already exists on ${item.id}`) }).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(id => item.id !== 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.id)
return false
}
})
this.renderComponent() this.renderComponent()
} }
@ -173,6 +187,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) => {

@ -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 Ethereum 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 () {

@ -534,7 +534,7 @@ export class LandingPage extends ViewPlugin {
<i class="far fa-hdd"></i> <i class="far fa-hdd"></i>
<span class="ml-1 ${css.text}" onclick=${() => connectToLocalhost()}>Connect to Localhost</span> <span class="ml-1 ${css.text}" onclick=${() => connectToLocalhost()}>Connect to Localhost</span>
</p> </p>
<p class="mt-3 mb-0"><label>IMPORT FROM:</label></p> <p class="mt-3 mb-0"><label>LOAD FROM:</label></p>
<div class="btn-group"> <div class="btn-group">
<button class="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onclick="${() => importFromGist()}">Gist</button> <button class="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onclick="${() => importFromGist()}">Gist</button>
<button class="btn mx-1 btn-secondary" onclick="${() => load('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}">GitHub</button> <button class="btn mx-1 btn-secondary" onclick="${() => load('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}">GitHub</button>

@ -354,6 +354,17 @@ module.exports = TxLogger
// helpers // helpers
function isDescendant (parent, child) {
var node = child.parentNode
while (node != null) {
if (node === parent) {
return true
}
node = node.parentNode
}
return false
}
function txDetails (e, tx, data, obj) { function txDetails (e, tx, data, obj) {
const from = obj.from const from = obj.from
const to = obj.to const to = obj.to
@ -368,9 +379,13 @@ function txDetails (e, tx, data, obj) {
} else break } else break
} }
let table = blockElement.querySelector(`#${tx.id} [class^="txTable"]`) const tables = blockElement.querySelectorAll(`#${tx.id} [class^="txTable"]`)
const log = blockElement.querySelector(`#${tx.id} [class^='log']`) const logs = blockElement.querySelectorAll(`#${tx.id} [class^='log']`)
const arrow = blockElement.querySelector(`#${tx.id} [class^='arrow']`) const arrows = blockElement.querySelectorAll(`#${tx.id} [class^='arrow']`)
let table = [...tables].filter((t) => isDescendant(tx, t))[0]
const log = [...logs].filter((t) => isDescendant(tx, t))[0]
const arrow = [...arrows].filter((t) => isDescendant(tx, t))[0]
if (table && table.parentNode) { if (table && table.parentNode) {
tx.removeChild(table) tx.removeChild(table)

@ -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)

@ -38,7 +38,7 @@ export class Mapping extends RefType {
decodeFromMemoryInternal (offset, memory) { decodeFromMemoryInternal (offset, memory) {
// mappings can only exist in storage and not in memory // mappings can only exist in storage and not in memory
// so this should never be called // so this should never be called
return { value: '<not implemented>', length: '0x', type: this.typeName } return { value: '', length: '0x0', type: this.typeName }
} }
async decodeMappingsLocation (preimages, location, storageResolver) { async decodeMappingsLocation (preimages, location, storageResolver) {

@ -1,6 +1,7 @@
'use strict' 'use strict'
import { add } from './util' import { add } from './util'
import { RefType } from './RefType' import { RefType } from './RefType'
import { Mapping } from './Mapping'
export class Struct extends RefType { export class Struct extends RefType {
members members
@ -33,7 +34,7 @@ export class Struct extends RefType {
var contentOffset = offset var contentOffset = offset
var member = item.type.decodeFromMemory(contentOffset, memory) var member = item.type.decodeFromMemory(contentOffset, memory)
ret[item.name] = member ret[item.name] = member
offset += 32 if (!(item.type instanceof Mapping)) offset += 32
}) })
return { value: ret, type: this.typeName } return { value: ret, type: this.typeName }
} }

@ -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,8 +6,9 @@ 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 = () => {
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 const handleClick = (event) => {
if (content && content !== '') { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
try { try {
if (typeof content !== 'string') { if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t') content = JSON.stringify(content, null, '\t')
@ -20,6 +21,8 @@ export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherP
} else { } else {
setMessage('Cannot copy empty content!') setMessage('Cannot copy empty content!')
} }
event.preventDefault()
return false
} }
const reset = () => { const reset = () => {
@ -27,10 +30,10 @@ export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherP
} }
return ( return (
<a href="#" onClick={handleClick} onMouseLeave={reset}> <a href='#' onClick={handleClick} onMouseLeave={reset}>
<OverlayTrigger placement="right" overlay={ <OverlayTrigger placement="right" overlay={
<Tooltip id="overlay-tooltip"> <Tooltip id="overlay-tooltip">
{ message } { message }
</Tooltip> </Tooltip>
}> }>
<i className={`far ${icon} ml-1 p-2`} aria-hidden="true" <i className={`far ${icon} ml-1 p-2`} aria-hidden="true"

@ -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 || {}} />
) )
} }

@ -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

@ -18,7 +18,7 @@ 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: '',
@ -204,6 +204,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
} }
}, [contextMenuItems]) }, [contextMenuItems])
useEffect(() => {
if (removedContextMenuItems) {
removeMenuItems(removedContextMenuItems)
}
}, [removedContextMenuItems])
useEffect(() => { useEffect(() => {
if (displayInput) { if (displayInput) {
handleNewFileInput() handleNewFileInput()

@ -9,8 +9,9 @@ export interface FileExplorerProps {
plugin: any, plugin: any,
focusRoot: boolean, focusRoot: boolean,
contextMenuItems: MenuItems, contextMenuItems: MenuItems,
removedContextMenuItems: string[],
displayInput?: boolean, displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement externalUploads?: EventTarget & HTMLInputElement,
} }
export interface File { export interface File {

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

@ -26,18 +26,20 @@ If you were using the old one you need to:
## HELP SECTION ## HELP SECTION
``` ```
Usage: remixd -s <shared folder> --remix-ide https://remix.ethereum.org Usage: remixd -s <shared folder>
Provide a two-way connection between the local computer and Remix IDE. Provide a two-way connection between the local computer and Remix IDE
Options:
-v, --version output the version number
-u, --remix-ide <url> URL of remix instance allowed to connect to this web sockect connection
-s, --shared-folder <path> Folder to share with Remix IDE
-r, --read-only Treat shared folder as read-only (experimental)
-h, --help output usage information
Options: Example:
--remix-ide <url> URL of remix instance allowed to connect to this remixd -s ./ -u http://localhost:8080
web sockect connection
-s, --shared-folder <path> Folder to share with Remix IDE
--read-only Treat shared folder as read-only (experimental)
-h, --help output usage information
``` ```

@ -58,11 +58,11 @@ function errorHandler (error: any, service: string) {
program program
.usage('-s <shared folder>') .usage('-s <shared folder>')
.description('Provide a two-way connection between the local computer and Remix IDE') .description('Provide a two-way connection between the local computer and Remix IDE')
.option('--remix-ide <url>', 'URL of remix instance allowed to connect to this web sockect connection') .option('-u, --remix-ide <url>', 'URL of remix instance allowed to connect to this web sockect connection')
.option('-s, --shared-folder <path>', 'Folder to share with Remix IDE') .option('-s, --shared-folder <path>', 'Folder to share with Remix IDE')
.option('--read-only', 'Treat shared folder as read-only (experimental)') .option('-r, --read-only', 'Treat shared folder as read-only (experimental)')
.on('--help', function () { .on('--help', function () {
console.log('\nExample:\n\n remixd -s ./ --remix-ide http://localhost:8080') console.log('\nExample:\n\n remixd -s ./ -u http://localhost:8080')
}).parse(process.argv) }).parse(process.argv)
// eslint-disable-next-line // eslint-disable-next-line

@ -96,11 +96,17 @@
"remix-ui-workspace": { "remix-ui-workspace": {
"tags": [] "tags": []
}, },
"remix-ui-settings": {
"tags": []
},
"remix-ui-static-analyser": { "remix-ui-static-analyser": {
"tags": [] "tags": []
}, },
"remix-ui-checkbox": { "remix-ui-checkbox": {
"tags": [] "tags": []
},
"remix-ui-settings": {
"tags": []
} }
} }
} }

13
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "remix-project", "name": "remix-project",
"version": "0.13.0-dev", "version": "0.14.0-dev",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -8443,9 +8443,9 @@
"dev": true "dev": true
}, },
"ace-mode-zokrates": { "ace-mode-zokrates": {
"version": "1.0.1", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/ace-mode-zokrates/-/ace-mode-zokrates-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ace-mode-zokrates/-/ace-mode-zokrates-1.0.4.tgz",
"integrity": "sha512-+rTOLj1AJzV/XRXsMLNkWIjNQCIa8TYjWRunCTGJ620iUy7WRlMkU7uVRydq//t4GUdr0j2TkNM0fSqVs0zNWw==", "integrity": "sha512-jLpIg+PhJTlCWKu52U/EdJPQPJez9mMB0uzvCiyHgCJsX6+FY+s7jmBDrpxGdgNdNWJPQ20/MKzOx3oUnSF27A==",
"dev": true "dev": true
}, },
"acorn": { "acorn": {
@ -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",

@ -1,6 +1,6 @@
{ {
"name": "remix-project", "name": "remix-project",
"version": "0.13.0-dev", "version": "0.14.0-dev",
"license": "MIT", "license": "MIT",
"description": "Ethereum Remix Monorepo", "description": "Ethereum Remix Monorepo",
"keywords": [ "keywords": [
@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox", "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs", "publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
@ -171,6 +171,7 @@
"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",
@ -218,7 +219,7 @@
"ace-mode-lexon": "^1.*.*", "ace-mode-lexon": "^1.*.*",
"ace-mode-move": "0.0.1", "ace-mode-move": "0.0.1",
"ace-mode-solidity": "^0.1.0", "ace-mode-solidity": "^0.1.0",
"ace-mode-zokrates": "^1.0.0", "ace-mode-zokrates": "^1.0.4",
"babel-eslint": "^10.0.0", "babel-eslint": "^10.0.0",
"babel-jest": "25.1.0", "babel-jest": "25.1.0",
"babel-plugin-add-module-exports": "^1.0.2", "babel-plugin-add-module-exports": "^1.0.2",

@ -39,8 +39,10 @@
"@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/settings": ["libs/remix-ui/settings/src/index.ts"]
} }
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]

@ -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",

Loading…
Cancel
Save