pull/1324/head
filip mertens 3 years ago
commit fbc18a2f23
  1. 3
      apps/debugger/src/app/debugger-api.ts
  2. 17
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  3. 2
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  4. 221
      apps/remix-ide/src/app/tabs/settings-tab.js
  5. 26
      apps/remix-ide/src/blockchain/execution-context.js
  6. 4
      libs/remix-debug/src/Ethdebugger.ts
  7. 3
      libs/remix-debug/src/debugger/debugger.ts
  8. 12
      libs/remix-debug/src/trace/traceManager.ts
  9. 121
      libs/remix-lib/src/execution/forkAt.ts
  10. 4
      libs/remix-lib/src/index.ts
  11. 4
      libs/remix-simulator/src/vm-context.ts
  12. 2
      libs/remix-solidity/src/compiler/types.ts
  13. 1
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  14. 3
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  15. 4
      libs/remix-ui/settings/.babelrc
  16. 19
      libs/remix-ui/settings/.eslintrc
  17. 7
      libs/remix-ui/settings/README.md
  18. 1
      libs/remix-ui/settings/src/index.ts
  19. 12
      libs/remix-ui/settings/src/lib/constants.ts
  20. 0
      libs/remix-ui/settings/src/lib/remix-ui-settings.css
  21. 166
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  22. 52
      libs/remix-ui/settings/src/lib/settingsAction.ts
  23. 103
      libs/remix-ui/settings/src/lib/settingsReducer.ts
  24. 16
      libs/remix-ui/settings/tsconfig.json
  25. 13
      libs/remix-ui/settings/tsconfig.lib.json
  26. 3
      nx.json
  27. 2
      package.json
  28. 9
      tsconfig.json
  29. 16
      workspace.json

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

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

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

@ -1,12 +1,10 @@
import React from 'react' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
import ReactDOM from 'react-dom'
import * as packageJson from '../../../../../package.json'
const yo = require('yo-yo')
import { RemixUiSettings } from '@remix-ui/settings' //eslint-disable-line
const globalRegistry = require('../../global/registry')
const tooltip = require('../ui/tooltip')
const copyToClipboard = require('../ui/copy-to-clipboard')
const EventManager = require('../../lib/events')
const css = require('./styles/settings-tab-styles')
const _paq = window._paq = window._paq || []
const profile = {
name: 'settings',
@ -51,210 +49,27 @@ module.exports = class SettingsTab extends ViewPlugin {
textWrapLabel: null
} /* eslint-enable */
this.event = new EventManager()
this.element = document.createElement('div')
this.element.setAttribute('id', 'settingsTab')
}
createThemeCheckies () {
const themes = this._deps.themeModule.getThemes()
const onswitchTheme = (event, name) => {
this._deps.themeModule.switchTheme(name)
}
if (themes) {
return yo`<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>`
}
onActivation () {
this.renderComponent()
}
render () {
const self = this
if (self._view.el) return self._view.el
// Gist settings
const token = this.config.get('settings/gist-access-token')
const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="form-control">`
if (token) gistAccessToken.value = token
const removeToken = () => { self.config.set('settings/gist-access-token', ''); gistAccessToken.value = ''; tooltip('Access token removed') }
const saveToken = () => {
this.config.set('settings/gist-access-token', gistAccessToken.value)
tooltip('Access token has been saved. RELOAD the page to apply it.')
}
const gistAddToken = yo`<input class="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onclick=${() => saveToken()} value="Save" type="button">`
const gistRemoveToken = yo`<button class="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onclick=${() => removeToken()}>Remove</button>`
this._view.gistToken = yo`
<div class="text-secondary mb-0 h6">
${gistAccessToken}
<div class="d-flex justify-content-end pt-2">
${copyToClipboard(() => gistAccessToken.value)}${gistAddToken}${gistRemoveToken}
</div>
<p class="pt-1">
<i class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>
<span class="text-warning">Please reload Remix after having saved the token.</span>
</p>
</div>
`
this._view.optionVM = yo`<input onchange=${onchangeOption} class="custom-control-input" id="alwaysUseVM" data-id="settingsTabAlwaysUseVM" type="checkbox">`
this._view.optionVMLabel = yo`<label class="form-check-label custom-control-label align-middle" for="alwaysUseVM">Always use JavaScript VM at Load</label>`
if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true)
if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '')
elementStateChanged(self._view.optionVMLabel, !this.config.get('settings/always-use-vm'))
this._view.textWrap = yo`<input id="editorWrap" class="custom-control-input" type="checkbox" onchange=${textWrapEvent}>`
this._view.textWrapLabel = yo`<label class="form-check-label custom-control-label align-middle" for="editorWrap">Text Wrap</label>`
if (this.config.get('settings/text-wrap')) this._view.textWrap.setAttribute('checked', '')
elementStateChanged(self._view.textWrapLabel, !this.config.get('settings/text-wrap'))
const warnText = `Transaction sent over Web3 will use the web3.personal API - be sure the endpoint is opened before enabling it.
This mode allows to provide the passphrase in the Remix interface without having to unlock the account.
Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...).
Remix never persist any passphrase.`.split('\n').map(s => s.trim()).join(' ')
this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="custom-control-input">`
this._view.warnPersonalMode = yo`<i class="${css.icon} fas fa-exclamation-triangle text-warning" aria-hidden="true"></i>`
this._view.personalLabel = yo`<label class="form-check-label custom-control-label align-middle" for="personal"> ${this._view.warnPersonalMode} Enable Personal Mode for web3 provider. ${warnText}></label>`
if (this.config.get('settings/personal-mode')) this._view.personal.setAttribute('checked', '')
elementStateChanged(self._view.personalLabel, !this.config.get('settings/personal-mode'))
this._view.useMatomoAnalytics = yo`<input onchange=${onchangeMatomoAnalytics} id="settingsMatomoAnalytics" type="checkbox" class="custom-control-input">`
this._view.useMatomoAnalyticsLabel = yo`
<label class="form-check-label custom-control-label align-middle" for="settingsMatomoAnalytics">
<span>Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the sites UX & UI. See more about</span>
<a href="https://medium.com/p/66ef69e14931/" target="_blank">Analytics in Remix IDE</a> <span>&</span> <a target="_blank" href="https://matomo.org/free-software">Matomo</a>
</label>
`
if (this.config.get('settings/matomo-analytics')) {
this._view.useMatomoAnalytics.setAttribute('checked', '')
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
} else {
_paq.push(['optUserOut'])
}
elementStateChanged(self._view.useMatomoAnalyticsLabel, !this.config.get('settings/matomo-analytics'))
this._view.generateContractMetadata = yo`<input onchange=${onchangeGenerateContractMetadata} id="generatecontractmetadata" data-id="settingsTabGenerateContractMetadata" type="checkbox" class="custom-control-input">`
this._view.generateContractMetadataLabel = yo`<label class="form-check-label custom-control-label align-middle" data-id="settingsTabGenerateContractMetadataLabel" for="generatecontractmetadata">Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.</label>`
if (this.config.get('settings/generate-contract-metadata') === undefined) this.config.set('settings/generate-contract-metadata', true)
if (this.config.get('settings/generate-contract-metadata')) this._view.generateContractMetadata.setAttribute('checked', '')
elementStateChanged(self._view.generateContractMetadataLabel, !this.config.get('settings/generate-contract-metadata'))
this._view.pluginInput = yo`<textarea rows="4" cols="70" id="plugininput" type="text" class="${css.pluginTextArea}" ></textarea>`
this._view.themes = this._deps.themeModule.getThemes()
this._view.themesCheckBoxes = this.createThemeCheckies()
this._view.config.general = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">General settings</h6>
<div class="mt-2 custom-control custom-checkbox mb-1">
${this._view.generateContractMetadata}
${this._view.generateContractMetadataLabel}
</div>
<div class="fmt-2 custom-control custom-checkbox mb-1">
${this._view.optionVM}
${this._view.optionVMLabel}
</div>
<div class="mt-2 custom-control custom-checkbox mb-1">
${this._view.textWrap}
${this._view.textWrapLabel}
</div>
<div class="custom-control custom-checkbox mb-1">
${this._view.personal}
${this._view.personalLabel}
</div>
<div class="custom-control custom-checkbox mb-1">
${this._view.useMatomoAnalytics}
${this._view.useMatomoAnalyticsLabel}
</div>
</div>
</div>
`
this._view.gistToken = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">Github Access Token</h6>
<p class="mb-1">Manage the access token used to publish to Gist and retrieve Github contents.</p>
<p class="">Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only 'create gist' permission.</p>
<p class="${css.crowNoFlex} mb-1"><a class="text-primary ${css.text}" target="_blank" href="https://github.com/settings/tokens">https://github.com/settings/tokens</a></p>
<div class="${css.crowNoFlex}"><label>TOKEN:</label>${this._view.gistToken}</div>
</div>
</div>`
this._view.config.themes = yo`
<div class="${css.info} border-top">
<div class="card-body pt-3 pb-2">
<h6 class="${css.title} card-title">Themes</h6>
${this._view.themesCheckBoxes}
</div>
</div>`
this._view.el = yo`
<div id="settingsView" data-id="settingsTabSettingsView">
${this._view.config.general}
${this._view.gistToken}
${this._view.config.themes}
</div>`
function onchangeGenerateContractMetadata (event) {
const isChecked = self.config.get('settings/generate-contract-metadata')
self.config.set('settings/generate-contract-metadata', !isChecked)
elementStateChanged(self._view.generateContractMetadataLabel, isChecked)
}
function onchangeOption (event) {
const isChecked = self.config.get('settings/always-use-vm')
self.config.set('settings/always-use-vm', !isChecked)
elementStateChanged(self._view.optionVMLabel, isChecked)
}
function textWrapEvent (event) {
const isChecked = self.config.get('settings/text-wrap')
self.config.set('settings/text-wrap', !isChecked)
elementStateChanged(self._view.textWrapLabel, isChecked)
self.editor.resize(!isChecked)
}
function onchangePersonal (event) {
const isChecked = self.config.get('settings/personal-mode')
self.config.set('settings/personal-mode', !isChecked)
elementStateChanged(self._view.personalLabel, isChecked)
}
function onchangeMatomoAnalytics (event) {
const isChecked = self.config.get('settings/matomo-analytics')
self.config.set('settings/matomo-analytics', !isChecked)
elementStateChanged(self._view.useMatomoAnalyticsLabel, isChecked)
if (event.target.checked) {
_paq.push(['forgetUserOptOut'])
// @TODO remove next line when https://github.com/matomo-org/matomo/commit/9e10a150585522ca30ecdd275007a882a70c6df5 is used
document.cookie = 'mtm_consent_removed=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
} else {
_paq.push(['optUserOut'])
}
}
function elementStateChanged (el, isChanged) {
if (isChanged) {
el.classList.remove('text-dark')
el.classList.add('text-secondary')
} else {
el.classList.add('text-dark')
el.classList.remove('text-secondary')
}
}
return this.element
}
this._deps.themeModule.switchTheme()
return this._view.el
renderComponent () {
ReactDOM.render(
<RemixUiSettings
config = { this.config }
editor = { this.editor }
_deps = { this._deps }
/>,
this.element
)
}
get (key) {

@ -1,6 +1,7 @@
/* global ethereum */
'use strict'
import Web3 from 'web3'
import { execution } from '@remix-project/remix-lib'
import EventManager from '../lib/events'
let web3
@ -21,8 +22,7 @@ export class ExecutionContext {
this.executionContext = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.defaultFork = 'berlin'
this.currentFork = this.defaultFork
this.currentFork = 'berlin'
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
@ -125,19 +125,16 @@ export class ExecutionContext {
executionContextChange (value, endPointUrl, confirmCb, infoCb, cb) {
const context = value.context
const fork = value.fork || this.defaultFork
if (!cb) cb = () => {}
if (!confirmCb) confirmCb = () => {}
if (!infoCb) infoCb = () => {}
if (context === 'vm') {
this.executionContext = context
this.currentFork = fork
this.currentFork = value.fork
this.event.trigger('contextChanged', ['vm'])
return cb()
}
this.currentFork = this.defaultFork // in the case of injected and web3, we default to the last fork.
if (context === 'injected') {
if (injectedProvider === undefined) {
infoCb('No injected Web3 provider found. Make sure your provider (e.g. MetaMask) is active and running (when recently activated you may have to reload the page).')
@ -146,7 +143,7 @@ export class ExecutionContext {
this.askPermission()
this.executionContext = context
web3.setProvider(injectedProvider)
this._updateBlockGasLimit()
this._updateChainContext()
this.event.trigger('contextChanged', ['injected'])
return cb()
}
@ -173,12 +170,19 @@ export class ExecutionContext {
this.listenOnLastBlockId = null
}
_updateBlockGasLimit () {
_updateChainContext () {
if (this.getProvider() !== 'vm') {
web3.eth.getBlock('latest', (err, block) => {
web3.eth.getBlock('latest', async (err, block) => {
if (!err) {
// we can't use the blockGasLimit cause the next blocks could have a lower limit : https://github.com/ethereum/remix/issues/506
this.blockGasLimit = (block && block.gasLimit) ? Math.floor(block.gasLimit - (5 * block.gasLimit) / 1024) : this.blockGasLimitDefault
try {
this.currentFork = execution.forkAt(await web3.eth.net.getId(), block.number)
} catch (e) {
this.currentFork = 'berlin'
console.log(`unable to detect fork, defaulting to ${this.currentFork}..`)
console.error(e)
}
} else {
this.blockGasLimit = this.blockGasLimitDefault
}
@ -188,7 +192,7 @@ export class ExecutionContext {
listenOnLastBlock () {
this.listenOnLastBlockId = setInterval(() => {
this._updateBlockGasLimit()
this._updateChainContext()
}, 15000)
}
@ -202,7 +206,7 @@ export class ExecutionContext {
web3.eth.net.isListening((err, isConnected) => {
if (!err && isConnected === true) {
this.executionContext = context
this._updateBlockGasLimit()
this._updateChainContext()
this.event.trigger('contextChanged', [context])
this.event.trigger('web3EndpointChanged')
cb()

@ -41,7 +41,7 @@ export class Ethdebugger {
this.opts = opts
this.event = new EventManager()
this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork })
this.traceManager = new TraceManager({ web3: this.web3 })
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.storageResolver = null
@ -55,7 +55,7 @@ export class Ethdebugger {
}
setManagers () {
this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork })
this.traceManager = new TraceManager({ web3: this.web3 })
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.storageResolver = null

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

@ -1,9 +1,9 @@
'use strict'
import { util, execution } from '@remix-project/remix-lib'
import { TraceAnalyser } from './traceAnalyser'
import { TraceCache } from './traceCache'
import { TraceStepManager } from './traceStepManager'
import { isCreateInstruction } from './traceHelper'
import { util } from '@remix-project/remix-lib'
export class TraceManager {
web3
@ -17,7 +17,6 @@ export class TraceManager {
constructor (options) {
this.web3 = options.web3
this.fork = options.fork
this.isLoading = false
this.trace = null
this.traceCache = new TraceCache()
@ -37,6 +36,15 @@ export class TraceManager {
if (result['structLogs'].length > 0) {
this.trace = result['structLogs']
try {
const networkId = await this.web3.eth.net.getId()
this.fork = execution.forkAt(networkId, tx.blockNumber)
} catch (e) {
this.fork = 'berlin'
console.log(`unable to detect fork, defaulting to ${this.fork}..`)
console.error(e)
}
this.traceAnalyser.analyse(result['structLogs'], tx)
this.isLoading = false
return true

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

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

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

@ -150,7 +150,7 @@ export interface CompilerInputOptions {
language?: Language
}
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'muirGlacier' | 'berlin' | null
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'muirGlacier' | 'berlin' | 'london' | null
export type Language = 'Solidity' | 'Yul'

@ -6,6 +6,7 @@ import './copy-to-clipboard.css'
export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => {
const [message, setMessage] = useState(tip)
const handleClick = (event) => {
if (content && content !== '') { // module `copy` keeps last copied thing in the memory, so don't show tooltip if nothing is copied, because nothing was added to memory
try {

@ -208,8 +208,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
return null
},
debugWithGeneratedSources: state.opt.debugWithGeneratedSources,
fork: 'berlin'
debugWithGeneratedSources: state.opt.debugWithGeneratedSources
})
debuggerInstance.debug(blockNumber, txNumber, tx, () => {

@ -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,166 @@
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(() => {
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 ? true : null}/>
<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"]
}

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

@ -41,7 +41,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"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,remix-core-plugin",
"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,remix-core-plugin",
"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,remix-core-plugin",
"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",

@ -39,13 +39,12 @@
"@remix-ui/toaster": ["libs/remix-ui/toaster/src/index.ts"],
"@remix-ui/file-explorer": ["libs/remix-ui/file-explorer/src/index.ts"],
"@remix-ui/workspace": ["libs/remix-ui/workspace/src/index.ts"],
"@remix-ui/static-analyser": [
"libs/remix-ui/static-analyser/src/index.ts"
],
"@remix-ui/checkbox": ["libs/remix-ui/checkbox/src/index.ts"],
"@remix-project/core-plugin": [
"libs/remix-core-plugin/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/settings": ["libs/remix-ui/settings/src/index.ts"]
}
},
"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": {
"root": "libs/remix-ui/static-analyser",
"sourceRoot": "libs/remix-ui/static-analyser/src",

Loading…
Cancel
Save