Merge branch 'master' into logfix

pull/5370/head
Liana Husikyan 3 years ago committed by GitHub
commit 8b48d11730
  1. 8
      apps/debugger/src/app/debugger-api.ts
  2. 19
      apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts
  3. 7
      apps/remix-ide-e2e/src/tests/debugger.spec.ts
  4. 2
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  5. 21
      apps/remix-ide-e2e/src/tests/gist.spec.ts
  6. 14
      apps/remix-ide-e2e/src/tests/recorder.spec.ts
  7. 4
      apps/remix-ide-e2e/src/tests/specialFunctions.test.ts
  8. 2
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  9. 15
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  10. 12
      apps/remix-ide-e2e/src/tests/url.spec.ts
  11. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  12. 5
      apps/remix-ide/src/app/files/remixd-handle.js
  13. 12
      apps/remix-ide/src/app/panels/file-panel.js
  14. 14
      apps/remix-ide/src/app/tabs/compile-tab.js
  15. 2
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  16. 31
      apps/remix-ide/src/app/tabs/runTab/settings.js
  17. 97
      apps/remix-ide/src/app/tabs/test-tab.js
  18. 8
      apps/remix-ide/src/app/tabs/testTab/testTab.js
  19. 2
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  20. 10
      apps/remix-ide/src/blockchain/blockchain.js
  21. 21
      apps/remix-ide/src/blockchain/execution-context.js
  22. 12
      apps/remix-ide/src/blockchain/providers/vm.js
  23. 8
      apps/remix-ide/src/lib/helper.js
  24. 16
      libs/remix-analyzer/package.json
  25. 18
      libs/remix-astwalker/package.json
  26. 16
      libs/remix-debug/package.json
  27. 4
      libs/remix-debug/src/Ethdebugger.ts
  28. 6
      libs/remix-debug/src/code/codeManager.ts
  29. 6
      libs/remix-debug/src/code/codeResolver.ts
  30. 40
      libs/remix-debug/src/code/codeUtils.ts
  31. 12
      libs/remix-debug/src/debugger/VmDebugger.ts
  32. 3
      libs/remix-debug/src/debugger/debugger.ts
  33. 3
      libs/remix-debug/src/init.ts
  34. 2
      libs/remix-debug/src/solidity-decoder/types/Mapping.ts
  35. 3
      libs/remix-debug/src/solidity-decoder/types/Struct.ts
  36. 6
      libs/remix-debug/src/trace/traceHelper.ts
  37. 6
      libs/remix-debug/src/trace/traceManager.ts
  38. 10
      libs/remix-lib/package.json
  39. 48
      libs/remix-lib/src/execution/txRunnerVM.ts
  40. 3
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  41. 3
      libs/remix-lib/src/init.ts
  42. 14
      libs/remix-simulator/package.json
  43. 6
      libs/remix-simulator/src/provider.ts
  44. 30
      libs/remix-simulator/src/vm-context.ts
  45. 2
      libs/remix-simulator/test/accounts.ts
  46. 4
      libs/remix-simulator/test/blocks.ts
  47. 2
      libs/remix-simulator/test/misc.ts
  48. 16
      libs/remix-solidity/package.json
  49. 20
      libs/remix-tests/package.json
  50. 10
      libs/remix-ui/clipboard/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
  51. 7
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  52. 52
      libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx
  53. 141
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  54. 14
      libs/remix-ui/file-explorer/src/lib/types/index.ts
  55. 2
      libs/remix-ui/tree-view/src/lib/tree-view-item/tree-view-item.tsx
  56. 12
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  57. 4
      libs/remix-url-resolver/package.json
  58. 2
      libs/remixd/package.json
  59. 23
      libs/remixd/src/bin/remixd.ts
  60. 8
      libs/remixd/src/websocket.ts
  61. 5278
      package-lock.json
  62. 15
      package.json

@ -74,7 +74,10 @@ export const DebuggerApiMixin = (Base) => class extends Base {
const targetAddress = target || receipt.contractAddress || receipt.to
const codeAtAddress = await this._web3.eth.getCode(targetAddress)
const output = await this.call('fetchAndCompile', 'resolve', targetAddress, codeAtAddress, 'browser/.debug')
return new CompilerAbstract(output.languageversion, output.data, output.source)
if (output) {
return new CompilerAbstract(output.languageversion, output.data, output.source)
}
return null
}
async getDebugWeb3 () {
@ -108,7 +111,8 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
return null
},
debugWithGeneratedSources: false
debugWithGeneratedSources: false,
fork: 'berlin'
})
return await debug.debugger.traceManager.getTrace(hash)
}

@ -0,0 +1,19 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class CurrentWorkspaceIs extends EventEmitter {
command (this: NightwatchBrowser, name: string): NightwatchBrowser {
this.api
.execute(function () {
const el = document.querySelector('select[data-id="workspacesSelect"]') as HTMLSelectElement
return el.value
}, [], (result) => {
console.log(result)
this.api.assert.equal(result.value, name)
this.emit('complete')
})
return this
}
}
module.exports = CurrentWorkspaceIs

@ -178,7 +178,7 @@ module.exports = {
.click('*[data-id="debuggerTransactionStartButton"]') // start debugging
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32) { revert(0, 0) }') !== -1, 'current displayed content is not a generated source')
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32)') !== -1, 'current displayed content is not a generated source')
})
.click('*[data-id="debuggerTransactionStartButton"]')
},
@ -210,7 +210,10 @@ module.exports = {
},
'Should start debugging using remix debug nodes (rinkeby)': function (browser: NightwatchBrowser) {
browser.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.8.4+commit.c7e474f2.js')
.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromptText"]')

@ -74,7 +74,7 @@ module.exports = {
.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
browser.refresh()

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

@ -129,7 +129,7 @@ contract t2est {
const records = `{
"accounts": {
"account{10}": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"
"account{2}": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"
},
"linkReferences": {
"testLib": "created{1512830014773}"
@ -146,7 +146,7 @@ const records = `{
"linkReferences": {},
"inputs": "()",
"type": "constructor",
"from": "account{10}"
"from": "account{2}"
}
},
{
@ -172,7 +172,7 @@ const records = `{
"name": "",
"type": "constructor",
"inputs": "(uint256)",
"from": "account{10}"
"from": "account{2}"
}
},
{
@ -188,7 +188,7 @@ const records = `{
"name": "set",
"inputs": "(uint256,address)",
"type": "function",
"from": "account{10}"
"from": "account{2}"
}
}
],
@ -287,7 +287,7 @@ const records = `{
const scenario = {
accounts: {
'account{10}': '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
'account{2}': '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'
},
linkReferences: {},
transactions: [
@ -305,7 +305,7 @@ const scenario = {
name: '',
type: 'constructor',
inputs: '(uint256)',
from: 'account{10}'
from: 'account{2}'
}
},
{
@ -320,7 +320,7 @@ const scenario = {
name: 'set',
inputs: '(uint256)',
type: 'function',
from: 'account{10}'
from: 'account{2}'
}
}
],

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

@ -62,7 +62,7 @@ module.exports = {
'Call web3.eth.getAccounts() using JavaScript VM': function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C", "0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB", "0x583031D1113aD414F02576BD6afaBfb302140225", "0xdD870fA1b7C4700F2BD7f44238821C26f7392148"', 80000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB", "0x617F2E2fD72FD9D5503197092aC168c91465E7f2", "0x17F6AD8Ef982297579C203069C1DbfFE4348c372", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C"', 80000)
},
'Call web3.eth.getAccounts() using Web3 Provider': function (browser: NightwatchBrowser) {

@ -152,6 +152,21 @@ module.exports = {
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('2,3,error_string_2')
.journalLastChildIncludes('Debug the transaction to get more information.')
},
'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal , using London VM Fork': function (browser: NightwatchBrowser) {
browser
.click('*[data-id="settingsVMLondonMode"]') // switch to London fork
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]')
.waitForElementPresent('.instance:nth-of-type(2)')
.click('.instance:nth-of-type(2) > div > button')
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('2,3,error_string_2')
.journalLastChildIncludes('Debug the transaction to get more information.')
.end()
}
}

@ -10,7 +10,7 @@ const sources = [
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0', true)
init(browser, done, 'http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js', true)
},
'@sources': function () {
@ -19,6 +19,11 @@ module.exports = {
'Should load the code from URL params': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js&code=cHJhZ21hIHNvbGlkaXR5ID49MC42LjAgPDAuNy4wOwoKaW1wb3J0ICJodHRwczovL2dpdGh1Yi5jb20vT3BlblplcHBlbGluL29wZW56ZXBwZWxpbi1jb250cmFjdHMvYmxvYi9tYXN0ZXIvY29udHJhY3RzL2FjY2Vzcy9Pd25hYmxlLnNvbCI7Cgpjb250cmFjdCBHZXRQYWlkIGlzIE93bmFibGUgewogIGZ1bmN0aW9uIHdpdGhkcmF3KCkgZXh0ZXJuYWwgb25seU93bmVyIHsKICB9Cn0')
.refresh() // we do one reload for making sure we already have the default workspace
.pause(5000)
.currentWorkspaceIs('code-sample')
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(
'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol') !== -1,
@ -28,6 +33,11 @@ module.exports = {
'Should load using URL compiler params': function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#optimize=true&runs=300&evmVersion=istanbul&version=soljson-v0.7.4+commit.3f05b770.js')
.refresh()
.pause(5000)
.clickLaunchIcon('solidity')
.assert.containsText('#versionSelector option[selected="selected"]', '0.7.4+commit.3f05b770')
.assert.containsText('#evmVersionSelector option[selected="selected"]', 'istanbul')
.verify.elementPresent('#optimize:checked')

@ -56,6 +56,7 @@ declare module "nightwatch" {
checkAnnotations(type: string, line: number): NightwatchBrowser
checkAnnotationsNotPresent(type: string): NightwatchBrowser
getLastTransactionHash(callback: (hash: string) => void)
currentWorkspaceIs(name: string): NightwatchBrowser
}
export interface NightwatchBrowser {

@ -19,6 +19,7 @@ var css = csjs`
word-break: break-word;
}
`
const LOCALHOST = ' - connect to localhost - '
const profile = {
name: 'remixd',
@ -83,7 +84,9 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled()
}
}, 3000)
this.localhostProvider.init(() => {})
this.localhostProvider.init(() => {
this.call('filePanel', 'setWorkspace', { name: LOCALHOST, isLocalhost: true }, true)
})
this.call('manager', 'activatePlugin', 'hardhat')
}
}

@ -34,7 +34,7 @@ const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = {
name: 'filePanel',
displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace'],
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'],
icon: 'assets/img/fileManager.webp',
description: ' - ',
@ -43,7 +43,6 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/file_explorer.html',
version: packageJson.version
}
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
@ -114,7 +113,6 @@ module.exports = class Filepanel extends ViewPlugin {
async getWorkspaces () {
const result = new Promise((resolve, reject) => {
const workspacesPath = this._deps.fileProviders.workspace.workspacesPath
this._deps.fileProviders.browser.resolveDirectory('/' + workspacesPath, (error, items) => {
if (error) {
console.error(error)
@ -247,14 +245,16 @@ module.exports = class Filepanel extends ViewPlugin {
}
/** these are called by the react component, action is already finished whent it's called */
async setWorkspace (workspace) {
this._deps.fileManager.closeAllFiles()
async setWorkspace (workspace, setEvent = true) {
if (workspace.isLocalhost) {
this.call('manager', 'activatePlugin', 'remixd')
} else if (await this.call('manager', 'isActive', 'remixd')) {
this.call('manager', 'deactivatePlugin', 'remixd')
}
this.emit('setWorkspace', workspace)
if (setEvent) {
this._deps.fileManager.setMode(workspace.isLocalhost ? 'localhost' : 'browser')
this.emit('setWorkspace', workspace)
}
}
workspaceRenamed (workspace) {

@ -65,7 +65,14 @@ class CompileTab extends ViewPlugin {
eventHandlers: {},
loading: false
}
this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProvider, this.contentImport)
this.compileTabLogic = new CompileTabLogic(
this.queryParams,
this.fileManager,
this.editor,
this.config,
this.fileProvider,
this.contentImport
)
}
onActivationInternal () {
@ -126,9 +133,14 @@ class CompileTab extends ViewPlugin {
this.emit('statusChanged', { key: 'loading', title: 'compiling...', type: 'info' })
}
this.data.eventHandlers.onRemoveAnnotations = () => {
this.call('editor', 'clearAnnotations')
}
this.on('filePanel', 'setWorkspace', () => this.resetResults())
this.compileTabLogic.event.on('startingCompilation', this.data.eventHandlers.onStartingCompilation)
this.compileTabLogic.event.on('removeAnnotations', this.data.eventHandlers.onRemoveAnnotations)
this.data.eventHandlers.onCurrentFileChanged = (name) => {
this.compilerContainer.currentFile = name

@ -118,7 +118,7 @@ class CompileTab extends Plugin {
}
}
this.fileManager.saveCurrentFile()
this.call('editor', 'clearAnnotations')
this.event.emit('removeAnnotations')
var currentFile = this.config.get('currentFile')
return this.compileFile(currentFile)
} catch (err) {

@ -98,11 +98,15 @@ class SettingsUI {
</label>
<div class="${css.environment}">
<select id="selectExEnvOptions" data-id="settingsSelectEnvOptions" class="form-control ${css.select} custom-select">
<option id="vm-mode"
<option id="vm-mode-berlin" data-id="settingsVMBerlinMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm" name="executionContext"> JavaScript VM
value="vm-berlin" name="executionContext" fork="berlin" > JavaScript VM (Berlin)
</option>
<option id="injected-mode"
<option id="vm-mode-london" data-id="settingsVMLondonMode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm-london" name="executionContext" fork="london"> JavaScript VM (London)
</option>
<option id="injected-mode" data-id="settingsInjectedMode"
title="Execution environment has been provided by Metamask or similar provider."
value="injected" name="executionContext"> Injected Web3
</option>
@ -236,11 +240,14 @@ class SettingsUI {
this.blockchain.event.register('removeProvider', name => removeProvider(name))
selectExEnv.addEventListener('change', (event) => {
const context = selectExEnv.options[selectExEnv.selectedIndex].value
this.setExecutionContext(context)
const provider = selectExEnv.options[selectExEnv.selectedIndex]
const fork = provider.getAttribute('fork') // can be undefined if connected to an external source (web3 provider / injected)
let context = provider.value
context = context.startsWith('vm') ? 'vm' : context // context has to be 'vm', 'web3' or 'injected'
this.setExecutionContext({ context, fork })
})
selectExEnv.value = this.blockchain.getProvider()
selectExEnv.value = this._getProviderDropdownValue()
}
setExecutionContext (context) {
@ -278,9 +285,19 @@ class SettingsUI {
`
}
/**
* generate a value used by the env dropdown list.
* @return {String} - can return 'vm-berlin, 'vm-london', 'injected' or 'web3'
*/
_getProviderDropdownValue () {
const provider = this.blockchain.getProvider()
const fork = this.blockchain.getCurrentFork()
return provider === 'vm' ? provider + '-' + fork : provider
}
setFinalContext () {
// set the final context. Cause it is possible that this is not the one we've originaly selected
this.selectExEnv.value = this.blockchain.getProvider()
this.selectExEnv.value = this._getProviderDropdownValue()
this.event.trigger('clearInstance', [])
this.updatePlusButton()
}

@ -1,5 +1,7 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { canUseWorker, urlFromVersion } from '../compiler/compiler-utils'
import { removeMultipleSlashes, removeTrailingSlashes } from '../../lib/helper'
var yo = require('yo-yo')
var async = require('async')
var tooltip = require('../ui/tooltip')
@ -51,19 +53,34 @@ module.exports = class TestTab extends ViewPlugin {
this.listenToEvents()
}
onDeactivation () {
this.off('filePanel', 'newTestFileCreated')
this.off('filePanel', 'setWorkspace')
this.fileManager.events.removeListener('currentFileChanged', this.updateForNewCurrent)
}
listenToEvents () {
this.on('filePanel', 'newTestFileCreated', file => {
var testList = this._view.el.querySelector("[class^='testList']")
var test = this.createSingleTest(file)
testList.appendChild(test)
this.on('filePanel', 'newTestFileCreated', async file => {
try {
await this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error)
this.data.allTests = tests
this.data.selectedTests = [...this.data.allTests]
this.updateTestFileList(tests)
if (!this.testsOutput) return // eslint-disable-line
})
} catch (e) {
console.log(e)
}
this.data.allTests.push(file)
this.data.selectedTests.push(file)
})
this.on('filePanel', 'setWorkspace', () => {
this.on('filePanel', 'setWorkspace', async () => {
this.testTabLogic.setCurrentPath(this.defaultPath)
this.inputPath.value = this.defaultPath
this.updateForNewCurrent()
this.updateDirList(this.defaultPath)
await this.updateForNewCurrent()
})
this.fileManager.events.on('noFileSelected', () => {
@ -72,18 +89,23 @@ module.exports = class TestTab extends ViewPlugin {
this.fileManager.events.on('currentFileChanged', (file, provider) => this.updateForNewCurrent(file))
}
updateForNewCurrent (file) {
this.updateGenerateFileAction()
if (!this.areTestsRunning) this.updateRunAction(file)
async updateForNewCurrent (file) {
this.data.allTests = []
this.updateTestFileList()
this.clearResults()
this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error)
this.data.allTests = tests
this.data.selectedTests = [...this.data.allTests]
this.updateTestFileList(tests)
if (!this.testsOutput) return // eslint-disable-line
})
this.updateGenerateFileAction()
if (!this.areTestsRunning) this.updateRunAction(file)
try {
await this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error)
this.data.allTests = tests
this.data.selectedTests = [...this.data.allTests]
this.updateTestFileList(tests)
if (!this.testsOutput) return // eslint-disable-line
})
} catch (e) {
console.log(e)
}
}
createSingleTest (testFile) {
@ -96,7 +118,7 @@ module.exports = class TestTab extends ViewPlugin {
}
listTests () {
if (!this.data.allTests) return []
if (!this.data.allTests || !this.data.allTests.length) return []
return this.data.allTests.map(
testFile => this.createSingleTest(testFile)
)
@ -425,7 +447,10 @@ module.exports = class TestTab extends ViewPlugin {
handleCreateFolder () {
this.inputPath.value = this.trimTestDirInput(this.inputPath.value)
let path = removeMultipleSlashes(this.inputPath.value)
if (path !== '/') path = removeTrailingSlashes(path)
if (this.inputPath.value === '') this.inputPath.value = this.defaultPath
this.inputPath.value = path
this.testTabLogic.generateTestFolder(this.inputPath.value)
this.createTestFolder.disabled = true
this.updateGenerateFileAction().disabled = false
@ -587,9 +612,11 @@ module.exports = class TestTab extends ViewPlugin {
}
async handleTestDirInput (e) {
const testDirInput = this.trimTestDirInput(this.inputPath.value)
let testDirInput = this.trimTestDirInput(this.inputPath.value)
testDirInput = removeMultipleSlashes(testDirInput)
if (testDirInput !== '/') testDirInput = removeTrailingSlashes(testDirInput)
if (e.key === 'Enter') {
this.inputPath.value = this.trimTestDirInput(testDirInput)
this.inputPath.value = testDirInput
if (await this.testTabLogic.pathExists(testDirInput)) {
this.testTabLogic.setCurrentPath(testDirInput)
this.updateForNewCurrent()
@ -598,7 +625,8 @@ module.exports = class TestTab extends ViewPlugin {
}
if (testDirInput) {
if (testDirInput.endsWith('/')) {
if (testDirInput.endsWith('/') && testDirInput !== '/') {
testDirInput = removeTrailingSlashes(testDirInput)
if (this.testTabLogic.currentPath === testDirInput.substr(0, testDirInput.length - 1)) {
this.createTestFolder.disabled = true
this.updateGenerateFileAction().disabled = true
@ -606,13 +634,13 @@ module.exports = class TestTab extends ViewPlugin {
this.updateDirList(testDirInput)
} else {
// If there is no matching folder in the workspace with entered text, enable Create button
if (this.testTabLogic.pathExists(testDirInput)) {
if (await this.testTabLogic.pathExists(testDirInput)) {
this.createTestFolder.disabled = true
this.updateGenerateFileAction().disabled = false
} else {
// Enable Create button
this.createTestFolder.disabled = false
// Disable Generate button because dir is not existing
// Disable Generate button because dir does not exist
this.updateGenerateFileAction().disabled = true
}
}
@ -621,6 +649,16 @@ module.exports = class TestTab extends ViewPlugin {
}
}
async handleEnter (e) {
this.inputPath.value = removeMultipleSlashes(this.trimTestDirInput(this.inputPath.value))
if (this.createTestFolder.disabled) {
if (await this.testTabLogic.pathExists(this.inputPath.value)) {
this.testTabLogic.setCurrentPath(this.inputPath.value)
this.updateForNewCurrent()
}
}
}
render () {
this.onActivationInternal()
this.testsOutput = yo`<div class="mx-3 mb-2 pb-4 border-top border-primary" hidden='true' id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput"></a>`
@ -634,18 +672,10 @@ module.exports = class TestTab extends ViewPlugin {
id="utPath"
data-id="uiPathInput"
name="utPath"
title="Press 'Enter' to change the path for test files."
style="background-image: var(--primary);"
onkeyup=${(e) => this.handleTestDirInput(e)}
onchange=${(e) => {
if (this.createTestFolder.disabled) {
this.inputPath.value = this.trimTestDirInput(this.inputPath.value)
if (this.testTabLogic.pathExists(this.inputPath.value)) {
this.inputPath.value = this.trimTestDirInput(this.inputPath.value)
this.testTabLogic.setCurrentPath(this.inputPath.value)
this.updateForNewCurrent()
}
}
}}
onchange=${async (e) => this.handleEnter(e)}
/>`
this.createTestFolder = yo`
@ -654,7 +684,8 @@ module.exports = class TestTab extends ViewPlugin {
data-id="testTabGenerateTestFolder"
title="Create a test folder"
disabled=true
onclick=${(e) => this.handleCreateFolder()}>
onclick=${(e) => this.handleCreateFolder()}
>
Create
</button>
`

@ -10,24 +10,26 @@ class TestTabLogic {
setCurrentPath (path) {
if (path.indexOf('/') === 0) return
this.currentPath = path
this.currentPath = helper.removeMultipleSlashes(helper.removeTrailingSlashes(path))
}
generateTestFolder (path) {
// Todo move this check to File Manager after refactoring
// Checking to ignore the value which contains only whitespaces
if (!path || !(/\S/.test(path))) return
path = helper.removeMultipleSlashes(path)
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
fileProvider.exists(path).then(res => {
if (!res) fileProvider.createDir(path)
})
}
pathExists (path) {
async pathExists (path) {
// Checking to ignore the value which contains only whitespaces
if (!path || !(/\S/.test(path))) return
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
return fileProvider.exists(path, (e, res) => { return res })
const res = await fileProvider.exists(path, (e, res) => { return res })
return res
}
generateTestFile () {

@ -534,7 +534,7 @@ export class LandingPage extends ViewPlugin {
<i class="far fa-hdd"></i>
<span class="ml-1 ${css.text}" onclick=${() => connectToLocalhost()}>Connect to Localhost</span>
</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">
<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>

@ -23,6 +23,7 @@ class Blockchain {
detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
},
isVM: () => { return this.executionContext.isVM() },
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}
@ -208,6 +209,14 @@ class Blockchain {
return this.executionContext.getProvider()
}
/**
* return the fork name applied to the current envionment
* @return {String} - fork name
*/
getCurrentFork () {
return this.executionContext.getCurrentFork()
}
isWeb3Provider () {
const isVM = this.getProvider() === 'vm'
const isInjected = this.getProvider() === 'injected'
@ -314,6 +323,7 @@ class Blockchain {
detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
},
isVM: () => { return this.executionContext.isVM() },
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}

@ -21,7 +21,8 @@ export class ExecutionContext {
this.executionContext = null
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'berlin'
this.defaultFork = 'berlin'
this.currentFork = this.defaultFork
this.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
@ -48,6 +49,10 @@ export class ExecutionContext {
return this.executionContext
}
getCurrentFork () {
return this.currentFork
}
isVM () {
return this.executionContext === 'vm'
}
@ -58,7 +63,7 @@ export class ExecutionContext {
web3 () {
if (this.customWeb3[this.executionContext]) return this.customWeb3[this.executionContext]
return this.isVM() ? this.vms[this.currentFork].web3vm : web3
return web3
}
detectNetwork (callback) {
@ -118,16 +123,21 @@ export class ExecutionContext {
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null)
}
executionContextChange (context, endPointUrl, confirmCb, infoCb, cb) {
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.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).')
@ -147,7 +157,7 @@ export class ExecutionContext {
}
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
this.setProviderFromEndpoint(network.provider, network.name, (error) => {
this.setProviderFromEndpoint(network.provider, { context: network.name }, (error) => {
if (error) infoCb(error)
cb()
})
@ -184,8 +194,9 @@ export class ExecutionContext {
// TODO: remove this when this function is moved
setProviderFromEndpoint (endpoint, context, cb) {
setProviderFromEndpoint (endpoint, value, cb) {
const oldProvider = web3.currentProvider
const context = value.context
web3.setProvider(endpoint)
web3.eth.net.isListening((err, isConnected) => {

@ -5,12 +5,6 @@ const { Provider, extend } = require('@remix-project/remix-simulator')
class VMProvider {
constructor (executionContext) {
this.executionContext = executionContext
this.RemixSimulatorProvider = new Provider({})
this.RemixSimulatorProvider.init()
this.web3 = new Web3(this.RemixSimulatorProvider)
extend(this.web3)
this.accounts = {}
this.executionContext.setWeb3('vm', this.web3)
}
getAccounts (cb) {
@ -23,8 +17,14 @@ class VMProvider {
}
resetEnvironment () {
this.accounts = {}
this.RemixSimulatorProvider = new Provider({ fork: this.executionContext.getCurrentFork() })
this.RemixSimulatorProvider.init()
this.RemixSimulatorProvider.Accounts.resetAccounts()
this.web3 = new Web3(this.RemixSimulatorProvider)
extend(this.web3)
this.accounts = {}
this.executionContext.setWeb3('vm', this.web3)
}
// TODO: is still here because of the plugin API

@ -104,6 +104,14 @@ module.exports = {
const hexValue = hash.slice(2, hash.length)
return this.is0XPrefixed(hash) && /^[0-9a-fA-F]{64}$/.test(hexValue)
},
removeTrailingSlashes (text) {
// Remove single or consecutive trailing slashes
return text.replace(/\/+$/g, '')
},
removeMultipleSlashes (text) {
// Replace consecutive slashes with '/'
return text.replace(/\/+/g, '/')
},
find: find,
getPathIcon (path) {
return path.endsWith('.txt')

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.8",
"version": "0.5.11",
"description": "Tool to perform static analysis on Solidity smart contracts",
"main": "index.js",
"types": ".index.d.ts",
@ -19,11 +19,15 @@
}
],
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-astwalker": "^0.0.26",
"@remix-project/remix-lib": "^0.4.34",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3"
"@remix-project/remix-lib": "^0.5.2",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"web3": "1.2.4"
},
"publishConfig": {
"access": "public"
@ -46,5 +50,5 @@
"typescript": "^3.7.5"
},
"typings": "index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.29",
"version": "0.0.32",
"description": "Tool to walk through Solidity AST",
"main": "index.js",
"scripts": {
@ -34,19 +34,23 @@
]
},
"dependencies": {
"@remix-project/remix-lib": "^0.4.34",
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@types/tape": "^4.2.33",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"nyc": "^13.3.0",
"tape": "^4.10.1",
"ts-node": "^8.0.3",
"typescript": "^3.4.3"
"typescript": "^3.4.3",
"web3": "1.2.4"
},
"devDependencies": {
"tap-spec": "^5.0.0"
},
"typings": "index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.4.10",
"version": "0.5.2",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -18,13 +18,17 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-astwalker": "^0.0.26",
"@remix-project/remix-lib": "^0.4.34",
"@remix-project/remix-lib": "^0.5.2",
"async": "^2.6.2",
"commander": "^2.19.0",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"deep-equal": "^1.0.1",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"web3": "^1.2.4"
},
"devDependencies": {
@ -56,5 +60,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -41,7 +41,7 @@ export class Ethdebugger {
this.opts = opts
this.event = new EventManager()
this.traceManager = new TraceManager({ web3: this.web3 })
this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork })
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 })
this.traceManager = new TraceManager({ web3: this.web3, fork: this.opts.fork })
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

@ -4,6 +4,7 @@ import { EventManager } from '../eventManager'
import { isContractCreation } from '../trace/traceHelper'
import { findNodeAtInstructionIndex } from '../source/sourceMappingDecoder'
import { CodeResolver } from './codeResolver'
import { TraceManager } from '../trace/traceManager' // eslint-disable-line
/*
resolve contract code referenced by vmtrace in order to be used by asm listview.
@ -15,7 +16,7 @@ import { CodeResolver } from './codeResolver'
export class CodeManager {
event
isLoading: boolean
traceManager
traceManager: TraceManager
codeResolver
constructor (_traceManager) {
@ -32,7 +33,8 @@ export class CodeManager {
return resolve(code)
})
})
}
},
fork: this.traceManager.getCurrentFork()
})
}

@ -6,12 +6,14 @@ export class CodeResolver {
bytecodeByAddress
instructionsByAddress
instructionsIndexByBytesOffset
fork
constructor ({ getCode }) {
constructor ({ getCode, fork }) {
this.getCode = getCode
this.bytecodeByAddress = {} // bytes code by contract addesses
this.instructionsByAddress = {} // assembly items instructions list by contract addesses
this.instructionsIndexByBytesOffset = {} // mapping between bytes offset and instructions index.
this.fork = fork
}
clear () {
@ -39,7 +41,7 @@ export class CodeResolver {
}
formatCode (hexCode) {
const [code, instructionsIndexByBytesOffset] = nameOpCodes(Buffer.from(hexCode.substring(2), 'hex'))
const [code, instructionsIndexByBytesOffset] = nameOpCodes(Buffer.from(hexCode.substring(2), 'hex'), this.fork)
return { code, instructionsIndexByBytesOffset }
}

@ -1,14 +1,24 @@
'use strict'
import opcodes from './opcodes'
import Common from '@ethereumjs/common'
import { getOpcodesForHF } from '@ethereumjs/vm/dist/evm/opcodes'
import getOpcodes from './opcodes'
export function nameOpCodes (raw, hardfork) {
const common = new Common({ chain: 'mainnet', hardfork })
const opcodes = getOpcodesForHF(common)
export function nameOpCodes (raw) {
let pushData = ''
const codeMap = {}
const code = []
for (let i = 0; i < raw.length; i++) {
const pc = i
const curOpCode = opcodes(raw[pc], false).name
let curOpCode
try {
curOpCode = opcodes.get(raw[pc]).fullName
} catch (e) {
curOpCode = 'INVALID'
}
codeMap[i] = code.length
// no destinations into the middle of PUSH
if (curOpCode.slice(0, 4) === 'PUSH') {
@ -25,17 +35,35 @@ export function nameOpCodes (raw) {
return [code, codeMap]
}
type Opcode = {
name: String,
pushData?: Array<number>
in?: number
out?: number
}
/**
* Parses code as a list of integers into a list of objects containing
* information about the opcode.
*/
export function parseCode (raw) {
const common = new Common({ chain: 'mainnet', hardfork: 'berlin' })
const opcodes = getOpcodesForHF(common)
const code = []
for (let i = 0; i < raw.length; i++) {
const opcode = opcodes(raw[i], true)
const opcode: Opcode = { name: 'INVALID' }
try {
const code = opcodes.get(raw[i])
const opcodeDetails = getOpcodes(raw[i], false)
opcode.in = opcodeDetails.in
opcode.out = opcodeDetails.out
opcode.name = code.fullName
} catch (e) {
opcode.name = 'INVALID'
}
if (opcode.name.slice(0, 4) === 'PUSH') {
const length = raw[i] - 0x5f
opcode['pushData'] = raw.slice(i + 1, i + length + 1)
opcode.pushData = raw.slice(i + 1, i + length + 1)
// in case pushdata extends beyond code
if (i + 1 + length > raw.length) {
for (let j = opcode['pushData'].length; j < length; j++) {
@ -60,5 +88,5 @@ export function log (num, base) {
}
export function roundLog (num, base) {
return Math.ceil(this.log(num, base))
return Math.ceil(log(num, base))
}

@ -189,7 +189,7 @@ export class VmDebuggerLogic {
})
})
this.debugger.event.register('indexChanged', this, (index) => {
this.debugger.event.register('indexChanged', this, async (index) => {
if (index < 0) return
if (this.stepManager.currentStepIndex !== index) return
if (!this.storageResolver) return
@ -201,11 +201,13 @@ export class VmDebuggerLogic {
for (var k in this.addresses) {
var address = this.addresses[k]
var storageViewer = new StorageViewer({ stepIndex: this.stepManager.currentStepIndex, tx: this.tx, address: address }, this.storageResolver, this._traceManager)
storageViewer.storageRange().then((result) => {
storageJSON[address] = result
this.event.trigger('traceStorageUpdate', [storageJSON])
})
try {
storageJSON[address] = await storageViewer.storageRange()
} catch (e) {
console.error(e)
}
}
this.event.trigger('traceStorageUpdate', [storageJSON])
})
}

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

@ -22,8 +22,7 @@ export function web3DebugNode (network) {
Main: 'https://rpc.archivenode.io/e50zmkroshle2e2e50zm0044i7ao04ym',
Rinkeby: 'https://remix-rinkeby.ethdevops.io',
Ropsten: 'https://remix-ropsten.ethdevops.io',
Goerli: 'https://remix-goerli.ethdevops.io',
Kovan: 'https://remix-kovan.ethdevops.io'
Goerli: 'https://remix-goerli.ethdevops.io'
}
if (web3DebugNodes[network]) {
return loadWeb3(web3DebugNodes[network])

@ -38,7 +38,7 @@ export class Mapping extends RefType {
decodeFromMemoryInternal (offset, memory) {
// mappings can only exist in storage and not in memory
// 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) {

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

@ -15,11 +15,11 @@ export function resolveCalledAddress (vmTraceIndex, trace) {
}
export function isCallInstruction (step) {
return ['CALL', 'STATICCALL', 'CALLCODE', 'CREATE', 'DELEGATECALL'].includes(step.op)
return ['CALL', 'STATICCALL', 'CALLCODE', 'CREATE', 'DELEGATECALL', 'CREATE2'].includes(step.op)
}
export function isCreateInstruction (step) {
return step.op === 'CREATE'
return step.op === 'CREATE' || step.op === 'CREATE2'
}
export function isReturnInstruction (step) {
@ -47,7 +47,7 @@ export function isSHA3Instruction (step) {
}
export function newContextStorage (step) {
return step.op === 'CREATE' || step.op === 'CALL'
return step.op === 'CREATE' || step.op === 'CALL' || step.op === 'CREATE2'
}
export function isCallToPrecompiledContract (index, trace) {

@ -7,6 +7,7 @@ import { util } from '@remix-project/remix-lib'
export class TraceManager {
web3
fork: string
isLoading: boolean
trace
traceCache
@ -16,6 +17,7 @@ export class TraceManager {
constructor (options) {
this.web3 = options.web3
this.fork = options.fork
this.isLoading = false
this.trace = null
this.traceCache = new TraceCache()
@ -70,6 +72,10 @@ export class TraceManager {
this.traceCache.init()
}
getCurrentFork () {
return this.fork
}
// API section
inRange (step) {
return this.isLoaded() && step >= 0 && step < this.trace.length

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.4.34",
"version": "0.5.2",
"description": "Library to various Remix tools",
"contributors": [
{
@ -14,10 +14,10 @@
],
"main": "src/index.js",
"dependencies": {
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"async": "^2.1.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"@ethereumjs/vm": "^5.3.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^4.0.40",
"events": "^3.0.0",
@ -52,5 +52,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -1,5 +1,5 @@
'use strict'
import { Transaction } from '@ethereumjs/tx'
import { Transaction, FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
import { Block } from '@ethereumjs/block'
import { BN, bufferToHex, Address } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
@ -60,15 +60,40 @@ export class TxRunnerVM {
this.getVMObject().stateManager.getAccount(Address.fromString(from)).then((res) => {
// See https://github.com/ethereumjs/ethereumjs-tx/blob/master/docs/classes/transaction.md#constructor
// for initialization fields and their types
value = value ? parseInt(value) : 0
const tx = Transaction.fromTxData({
nonce: new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
}, { common: this.commonContext }).sign(account.privateKey)
if (!value) value = 0
if (typeof value === 'string') {
if (value.startsWith('0x')) value = new BN(value.replace('0x', ''), 'hex')
else {
try {
value = new BN(value, 10)
} catch (e) {
return callback('Unable to parse the value ' + e.message)
}
}
}
const EIP1559 = this.commonContext.hardfork() !== 'berlin'
let tx
if (!EIP1559) {
tx = Transaction.fromTxData({
nonce: new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
}, { common: this.commonContext }).sign(account.privateKey)
} else {
tx = FeeMarketEIP1559Transaction.fromTxData({
nonce: new BN(res.nonce),
maxPriorityFeePerGas: '0x01',
maxFeePerGas: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
}).sign(account.privateKey)
}
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
@ -79,7 +104,8 @@ export class TxRunnerVM {
number: self.blockNumber,
coinbase: coinbases[self.blockNumber % coinbases.length],
difficulty: difficulties[self.blockNumber % difficulties.length],
gasLimit: new BN(gasLimit.replace('0x', ''), 16).imuln(2)
gasLimit: new BN(gasLimit.replace('0x', ''), 16).imuln(2),
baseFeePerGas: EIP1559 ? '0x1' : undefined
},
transactions: [tx]
}, { common: this.commonContext })

@ -72,9 +72,8 @@ export class TxRunnerWeb3 {
const tx = { from: from, to: to, data: data, value: value }
if (useCall) {
const tag = Date.now() // for e2e reference
tx['gas'] = gasLimit
tx['timestamp'] = timestamp
if (this._api && this._api.isVM()) tx['timestamp'] = timestamp
return this.getWeb3().eth.call(tx, function (error, result: any) {
if (error) return callback(error)
callback(null, {

@ -21,8 +21,7 @@ export function web3DebugNode (network) {
Main: 'https://gethmainnet.komputing.org',
Rinkeby: 'https://remix-rinkeby.ethdevops.io',
Ropsten: 'https://remix-ropsten.ethdevops.io',
Goerli: 'https://remix-goerli.ethdevops.io',
Kovan: 'https://remix-kovan.ethdevops.io'
Goerli: 'https://remix-goerli.ethdevops.io'
}
if (web3DebugNodes[network]) {
return this.loadWeb3(web3DebugNodes[network])

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.1.10-beta.0",
"version": "0.2.2",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -14,17 +14,19 @@
],
"main": "src/index.js",
"dependencies": {
"@remix-project/remix-lib": "../remix-lib",
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
"color-support": "^1.1.3",
"commander": "^2.19.0",
"cors": "^2.8.5",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"express": "^4.16.3",
"express-ws": "^4.0.0",
"merge": "^1.2.0",
@ -63,5 +65,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -18,14 +18,12 @@ export class Provider {
Accounts
Transactions
methods
host: string
connected: boolean;
constructor (host: string = 'vm', options: Record<string, unknown> = {}) {
constructor (options: Record<string, unknown> = {}) {
this.options = options
this.host = host
this.connected = true
this.vmContext = new VMContext()
this.vmContext = new VMContext(options['fork'])
this.Accounts = new Accounts(this.vmContext)
this.Transactions = new Transactions(this.vmContext)

@ -91,24 +91,18 @@ export class VMContext {
blocks
latestBlockNumber
txs
vms
defaultFork
currentVm
web3vm
logsManager
exeResults
constructor () {
constructor (fork?) {
this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault
this.currentFork = 'berlin'
this.vms = {
/*
byzantium: createVm('byzantium'),
constantinople: createVm('constantinople'),
petersburg: createVm('petersburg'),
istanbul: createVm('istanbul'),
*/
berlin: this.createVm('berlin')
}
this.defaultFork = fork || 'berlin'
this.currentFork = this.defaultFork
this.currentVm = this.createVm(this.currentFork)
this.blocks = {}
this.latestBlockNumber = 0
this.txs = {}
@ -122,7 +116,7 @@ export class VMContext {
const vm = new VM({
common,
activatePrecompiles: true,
stateManager: stateManager,
stateManager,
allowUnlimitedContractSize: true
})
@ -131,8 +125,12 @@ export class VMContext {
return { vm, web3vm, stateManager, common }
}
getCurrentFork () {
return this.currentFork
}
web3 () {
return this.vms[this.currentFork].web3vm
return this.currentVm.web3vm
}
blankWeb3 () {
@ -140,11 +138,11 @@ export class VMContext {
}
vm () {
return this.vms[this.currentFork].vm
return this.currentVm.vm
}
vmObject () {
return this.vms[this.currentFork]
return this.currentVm
}
addBlock (block) {

@ -8,7 +8,7 @@ describe('Accounts', () => {
before(async function () {
const provider = new Provider()
await provider.init()
web3.setProvider(provider)
web3.setProvider(provider as any)
})
describe('eth_getAccounts', () => {

@ -6,11 +6,11 @@ import * as assert from 'assert'
describe('blocks', () => {
before(async () => {
const provider = new Provider('vm', {
const provider = new Provider({
coinbase: '0x0000000000000000000000000000000000000001'
})
await provider.init()
web3.setProvider(provider)
web3.setProvider(provider as any)
})
describe('eth_getBlockByNumber', () => {

@ -8,7 +8,7 @@ describe('Misc', () => {
before(async () => {
const provider = new Provider()
await provider.init()
web3.setProvider(provider)
web3.setProvider(provider as any)
})
describe('web3_clientVersion', () => {

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.3.35",
"version": "0.4.2",
"description": "Tool to load and run Solidity compiler",
"main": "index.js",
"types": "./index.d.ts",
@ -15,12 +15,16 @@
}
],
"dependencies": {
"@remix-project/remix-lib": "../remix-lib",
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"solc": "^0.7.4",
"web3": "1.2.4",
"webworkify-webpack": "^2.1.5"
},
"devDependencies": {
@ -54,5 +58,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.1.38",
"version": "0.2.2",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -35,9 +35,13 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-tests#readme",
"dependencies": {
"@remix-project/remix-lib": "../remix-lib",
"@remix-project/remix-simulator": "../remix-simulator",
"@remix-project/remix-solidity": "../remix-solidity",
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remix-project/remix-lib": "^0.5.2",
"@remix-project/remix-simulator": "^0.2.2",
"@remix-project/remix-solidity": "^0.4.2",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
@ -45,13 +49,13 @@
"color-support": "^1.1.3",
"colors": "^1.1.2",
"commander": "^2.13.0",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/tx": "^3.1.3",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"express-ws": "^4.0.0",
"merge": "^1.2.0",
"signale": "^1.4.0",
"time-stamp": "^2.2.0",
"tslib": "^2.3.0",
"web3": "^1.2.4",
"winston": "^3.0.0"
},
@ -72,5 +76,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -6,8 +6,8 @@ import './copy-to-clipboard.css'
export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherProps }) => {
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 {
if (typeof content !== 'string') {
content = JSON.stringify(content, null, '\t')
@ -20,6 +20,8 @@ export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherP
} else {
setMessage('Cannot copy empty content!')
}
event.preventDefault()
return false
}
const reset = () => {
@ -27,10 +29,10 @@ export const CopyToClipboard = ({ content, tip='Copy', icon='fa-copy', ...otherP
}
return (
<a href="#" onClick={handleClick} onMouseLeave={reset}>
<a href='#' onClick={handleClick} onMouseLeave={reset}>
<OverlayTrigger placement="right" overlay={
<Tooltip id="overlay-tooltip">
{ message }
{ message }
</Tooltip>
}>
<i className={`far ${icon} ml-1 p-2`} aria-hidden="true"

@ -174,7 +174,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
setState(prevState => {
return {
...prevState,
validationError: 'The Kovan network is unfortunately not supported.'
validationError: 'Unfortunately, the Kovan network is not supported.'
}
})
return
@ -208,7 +208,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
return null
},
debugWithGeneratedSources: state.opt.debugWithGeneratedSources
debugWithGeneratedSources: state.opt.debugWithGeneratedSources,
fork: 'berlin'
})
debuggerInstance.debug(blockNumber, txNumber, tx, () => {
@ -282,7 +283,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}} type="checkbox" title="Debug with generated sources" />
<label data-id="debugGeneratedSourcesLabel" className="form-check-label custom-control-label" htmlFor="debugGeneratedSourcesInput">Use generated sources (from Solidity v0.7.2)</label>
</div>
{ (state.validationError && !state.txNumberIsEmpty) && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> }
{ state.validationError && <span className="w-100 py-1 text-danger validationError">{state.validationError}</span> }
</div>
<TxBrowser requestDebug={ requestDebug } unloadRequested={ unloadRequested } updateTxNumberFlag={ updateTxNumberFlag } transactionNumber={ state.txNumber } debugging={ state.debugging } />
{ state.debugging && <StepManager stepManager={ stepManager } /> }

@ -1,12 +1,11 @@
import React, { useRef, useEffect } from 'react' // eslint-disable-line
import { FileExplorerContextMenuProps } from './types'
import { action, FileExplorerContextMenuProps } from './types'
import './css/file-explorer-context-menu.css'
export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => {
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, ...otherProps } = props
const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, pushChangesToGist, publishFileToGist, publishFolderToGist, copy, paste, runScript, emit, pageX, pageY, path, type, focus, ...otherProps } = props
const contextMenuRef = useRef(null)
useEffect(() => {
contextMenuRef.current.focus()
}, [])
@ -22,14 +21,40 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
}
}, [pageX, pageY])
const filterItem = (item: action) => {
/**
* if there are multiple elements focused we need to take this and all conditions must be met
* for example : 'downloadAsZip' with type ['file','folder'] will work on files and folders when multiple are selected
**/
const nonRootFocus = focus.filter((el) => { return !(el.key === '' && el.type === 'folder') })
if (nonRootFocus.length > 1) {
for (const element of nonRootFocus) {
if (!itemMatchesCondition(item, element.type, element.key)) return false
}
return true
} else {
return itemMatchesCondition(item, type, path)
}
}
const itemMatchesCondition = (item: action, itemType: string, itemPath: string) => {
if (item.type && Array.isArray(item.type) && (item.type.findIndex(name => name === itemType) !== -1)) return true
else if (item.path && Array.isArray(item.path) && (item.path.findIndex(key => key === itemPath) !== -1)) return true
else if (item.extension && Array.isArray(item.extension) && (item.extension.findIndex(ext => itemPath.endsWith(ext)) !== -1)) return true
else if (item.pattern && Array.isArray(item.pattern) && (item.pattern.filter(value => itemPath.match(new RegExp(value))).length > 0)) return true
else return false
}
const getPath = () => {
if (focus.length > 1) {
return focus.map((element) => element.key)
} else {
return path
}
}
const menu = () => {
return actions.filter(item => {
if (item.type && Array.isArray(item.type) && (item.type.findIndex(name => name === type) !== -1)) return true
else if (item.path && Array.isArray(item.path) && (item.path.findIndex(key => key === path) !== -1)) return true
else if (item.extension && Array.isArray(item.extension) && (item.extension.findIndex(ext => path.endsWith(ext)) !== -1)) return true
else if (item.pattern && Array.isArray(item.pattern) && (item.pattern.filter(value => path.match(new RegExp(value))).length > 0)) return true
else return false
}).map((item, index) => {
return actions.filter(item => filterItem(item)).map((item, index) => {
return <li
id={`menuitem${item.name.toLowerCase()}`}
key={index}
@ -47,7 +72,7 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
renamePath(path, type)
break
case 'Delete':
deletePath(path)
deletePath(getPath())
break
case 'Push changes to gist':
pushChangesToGist(path, type)
@ -67,8 +92,11 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) =>
case 'Paste':
paste(path, type)
break
case 'Delete All':
deletePath(getPath())
break
default:
emit && emit(item.id, path)
emit && emit(item.id, getPath())
break
}
hideContextMenu()

@ -6,7 +6,7 @@ import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import Gists from 'gists'
import { FileExplorerMenu } from './file-explorer-menu' // eslint-disable-line
import { FileExplorerContextMenu } from './file-explorer-context-menu' // eslint-disable-line
import { FileExplorerProps, File } from './types'
import { FileExplorerProps, File, MenuItems } from './types'
import { fileSystemReducer, fileSystemInitialState } from './reducers/fileSystem'
import { fetchDirectory, init, resolveDirectory, addInputField, removeInputField } from './actions/fileSystem'
import * as helper from '../../../../../apps/remix-ide/src/lib/helper'
@ -33,63 +33,80 @@ export const FileExplorer = (props: FileExplorerProps) => {
type: ['folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'newFolder',
name: 'New Folder',
type: ['folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'rename',
name: 'Rename',
type: ['file', 'folder'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'delete',
name: 'Delete',
type: ['file', 'folder', 'gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'run',
name: 'Run',
type: [],
path: [],
extension: ['.js'],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: ['gist'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'publishFolderToGist',
name: 'Publish folder to gist',
type: ['folder'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'publishFileToGist',
name: 'Publish file to gist',
type: ['file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'copy',
name: 'Copy',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}, {
id: 'deleteAll',
name: 'Delete All',
type: ['folder', 'file'],
path: [],
extension: [],
pattern: [],
multiselect: true
}],
focusContext: {
element: null,
@ -224,6 +241,31 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}, [state.modals])
useEffect(() => {
const keyPressHandler = (e: KeyboardEvent) => {
if (e.shiftKey) {
setState(prevState => {
return { ...prevState, ctrlKey: true }
})
}
}
const keyUpHandler = (e: KeyboardEvent) => {
if (!e.shiftKey) {
setState(prevState => {
return { ...prevState, ctrlKey: false }
})
}
}
document.addEventListener('keydown', keyPressHandler)
document.addEventListener('keyup', keyUpHandler)
return () => {
document.removeEventListener('keydown', keyPressHandler)
document.removeEventListener('keyup', keyUpHandler)
}
}, [])
useEffect(() => {
if (canPaste) {
addMenuItems([{
@ -232,14 +274,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
type: ['folder', 'file'],
path: [],
extension: [],
pattern: []
pattern: [],
multiselect: false
}])
} else {
removeMenuItems(['paste'])
}
}, [canPaste])
const addMenuItems = (items: { id: string, name: string, type: string[], path: string[], extension: string[], pattern: string[] }[]) => {
const addMenuItems = (items: MenuItems) => {
setState(prevState => {
// filter duplicate items
const actions = items.filter(({ name }) => prevState.actions.findIndex(action => action.name === name) === -1)
@ -325,21 +368,23 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
}
const deletePath = async (path: string) => {
const deletePath = async (path: string | string[]) => {
const filesProvider = fileSystem.provider.provider
if (filesProvider.isReadOnly(path)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
if (!Array.isArray(path)) path = [path]
for (const p of path) {
if (filesProvider.isReadOnly(p)) {
return toast('cannot delete file. ' + name + ' is a read only explorer')
}
}
const isDir = state.fileManager.isDirectory(path)
modal(`Delete ${isDir ? 'folder' : 'file'}`, `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, 'OK', async () => {
try {
const fileManager = state.fileManager
await fileManager.remove(path)
} catch (e) {
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${path}.`)
modal(`Delete ${path.length > 1 ? 'items' : 'item'}`, deleteMessage(path), 'OK', async () => {
const fileManager = state.fileManager
for (const p of path) {
try {
await fileManager.remove(p)
} catch (e) {
const isDir = state.fileManager.isDirectory(p)
toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${p}.`)
}
}
}, 'Cancel', () => {})
}
@ -556,7 +601,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const emitContextMenuEvent = (id: string, path: string) => {
const emitContextMenuEvent = (id: string, path: string | string[]) => {
plugin.emit(id, path)
}
@ -566,7 +611,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const modal = (title: string, message: string, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
const modal = (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setState(prevState => {
return {
...prevState,
@ -592,10 +637,25 @@ export const FileExplorer = (props: FileExplorerProps) => {
const handleClickFile = (path: string, type: string) => {
path = path.indexOf(props.name + '/') === 0 ? path.replace(props.name + '/', '') : path
state.fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type }] }
})
if (!state.ctrlKey) {
state.fileManager.open(path)
setState(prevState => {
return { ...prevState, focusElement: [{ key: path, type }] }
})
} else {
if (state.focusElement.findIndex(item => item.key === path) !== -1) {
setState(prevState => {
return { ...prevState, focusElement: prevState.focusElement.filter(item => item.key !== path) }
})
} else {
setState(prevState => {
const nonRootFocus = prevState.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') })
nonRootFocus.push({ key: path, type })
return { ...prevState, focusElement: nonRootFocus }
})
}
}
}
const handleClickFolder = async (path: string, type: string) => {
@ -606,7 +666,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
} else {
setState(prevState => {
return { ...prevState, focusElement: [...prevState.focusElement, { key: path, type }] }
const nonRootFocus = prevState.focusElement.filter((el) => { return !(el.key === '' && el.type === 'folder') })
nonRootFocus.push({ key: path, type })
return { ...prevState, focusElement: nonRootFocus }
})
}
} else {
@ -763,6 +826,17 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
const deleteMessage = (path: string[]) => {
return (
<div>
<div>Are you sure you want to delete {path.length > 1 ? 'these items' : 'this item'}?</div>
{
path.map((item, i) => (<li key={i}>{item}</li>))
}
</div>
)
}
const label = (file: File) => {
return (
<div
@ -929,7 +1003,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
<Toaster message={state.toasterMsg} />
{ state.showContextMenu &&
<FileExplorerContextMenu
actions={state.actions}
actions={state.focusElement.length > 1 ? state.actions.filter(item => item.multiselect) : state.actions.filter(item => !item.multiselect)}
hideContextMenu={hideContextMenu}
createNewFile={handleNewFileInput}
createNewFolder={handleNewFolderInput}
@ -943,6 +1017,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
pageY={state.focusContext.y}
path={state.focusContext.element}
type={state.focusContext.type}
focus={state.focusElement}
onMouseOver={(e) => {
e.stopPropagation()
handleMouseOver(state.focusContext.element)

@ -6,7 +6,7 @@ export interface FileExplorerProps {
menuItems?: string[],
plugin: any,
focusRoot: boolean,
contextMenuItems: { id: string, name: string, type: string[], path: string[], extension: string[], pattern: string[] }[],
contextMenuItems: MenuItems,
displayInput?: boolean,
externalUploads?: EventTarget & HTMLInputElement
}
@ -29,11 +29,14 @@ export interface FileExplorerMenuProps {
uploadFile: (target: EventTarget & HTMLInputElement) => void
}
export type action = { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string, multiselect: boolean }
export type MenuItems = action[]
export interface FileExplorerContextMenuProps {
actions: { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string }[],
actions: action[],
createNewFile: (folder?: string) => void,
createNewFolder: (parentFolder?: string) => void,
deletePath: (path: string) => void,
deletePath: (path: string | string[]) => void,
renamePath: (path: string, type: string) => void,
hideContextMenu: () => void,
publishToGist?: (path?: string, type?: string) => void,
@ -41,12 +44,13 @@ export interface FileExplorerContextMenuProps {
publishFolderToGist?: (path?: string, type?: string) => void,
publishFileToGist?: (path?: string, type?: string) => void,
runScript?: (path: string) => void,
emit?: (id: string, path: string) => void,
emit?: (id: string, path: string | string[]) => void,
pageX: number,
pageY: number,
path: string,
type: string,
focus: {key:string, type:string}[],
onMouseOver?: (...args) => void,
copy?: (path: string, type: string) => void
copy?: (path: string, type: string) => void,
paste?: (destination: string, type: string) => void
}

@ -14,7 +14,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => {
return (
<li ref={innerRef} key={`treeViewLi${id}`} data-id={`treeViewLi${id}`} className='li_tv' {...otherProps}>
<div key={`treeViewDiv${id}`} data-id={`treeViewDiv${id}`} className={`d-flex flex-row align-items-center ${labelClass}`} onClick={() => !controlBehaviour && setIsExpanded(!isExpanded)}>
{ controlBehaviour ? null : children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null }
{ children ? <div className={isExpanded ? `px-1 ${iconY} caret caret_tv` : `px-1 ${iconX} caret caret_tv`} style={{ visibility: children ? 'visible' : 'hidden' }}></div> : icon ? <div className={`pr-3 pl-1 ${icon} caret caret_tv`}></div> : null }
<span className='w-100 pl-1'>
{ label }
</span>

@ -6,7 +6,7 @@ import { Toaster } from '@remix-ui/toaster'// eslint-disable-line
/* eslint-disable-next-line */
export interface WorkspaceProps {
setWorkspace: ({ name: string, isLocalhost: boolean }) => void,
setWorkspace: ({ name: string, isLocalhost: boolean }, setEvent: boolean) => void,
createWorkspace: (name: string) => void,
renameWorkspace: (oldName: string, newName: string) => void
workspaceRenamed: ({ name: string }) => void,
@ -74,9 +74,10 @@ export const Workspace = (props: WorkspaceProps) => {
let getWorkspaces = async () => {
if (props.workspaces && Array.isArray(props.workspaces)) {
if (props.workspaces.length > 0 && state.currentWorkspace === NO_WORKSPACE) {
props.workspace.setWorkspace(props.workspaces[0])
const currentWorkspace = props.workspace.getWorkspace() ? props.workspace.getWorkspace() : props.workspaces[0]
await props.workspace.setWorkspace(currentWorkspace)
setState(prevState => {
return { ...prevState, workspaces: props.workspaces, currentWorkspace: props.workspaces[0] }
return { ...prevState, workspaces: props.workspaces, currentWorkspace }
})
} else {
setState(prevState => {
@ -240,14 +241,15 @@ export const Workspace = (props: WorkspaceProps) => {
}
const setWorkspace = async (name) => {
await props.setWorkspace({ name, isLocalhost: name === LOCALHOST })
await props.fileManager.closeAllFiles()
if (name === LOCALHOST) {
props.workspace.clearWorkspace()
} else if (name === NO_WORKSPACE) {
props.workspace.clearWorkspace()
} else {
props.workspace.setWorkspace(name)
await props.workspace.setWorkspace(name)
}
await props.setWorkspace({ name, isLocalhost: name === LOCALHOST }, !(name === LOCALHOST || name === NO_WORKSPACE))
props.plugin.getWorkspaces()
setState(prevState => {
return { ...prevState, currentWorkspace: name }

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-url-resolver",
"version": "0.0.20",
"version": "0.0.23",
"description": "Solidity import url resolver engine",
"main": "index.js",
"types": "./index.d.ts",
@ -42,5 +42,5 @@
"typescript": "^3.1.6"
},
"typings": "index.d.ts",
"gitHead": "a0db8476e581f72690f735891fdf1282a97ec100"
"gitHead": "50b32cc20d2d4dcc793bf7de955957e073e5b5b8"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remixd",
"version": "0.3.5",
"version": "0.4.1",
"description": "remix server: allow accessing file system from remix.ethereum.org and start a dev environment (see help section)",
"main": "index.js",
"types": "./index.d.ts",

@ -36,12 +36,21 @@ const ports = {
}
const killCallBack: Array<Function> = []
function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error?:Error) => void) {
const socket = new WebSocket(ports[service], { remixIdeUrl: program.remixIde }, () => services[service](program.readOnly || false))
socket.start(callback)
killCallBack.push(socket.close.bind(socket))
}
function errorHandler (error: any, service: string) {
const port = ports[service]
if (error.code && error.code === 'EADDRINUSE') {
console.log('\x1b[31m%s\x1b[0m', `[ERR] There is already a client running on port ${port}!`)
} else {
console.log('\x1b[31m%s\x1b[0m', '[ERR]', error)
}
}
(async () => {
const { version } = require('../package.json')
program.version(version, '-v, --version')
@ -76,7 +85,11 @@ function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callb
console.log('\x1b[33m%s\x1b[0m', '[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.')
console.log('\x1b[33m%s\x1b[0m', '[WARN] Symbolic links are not forwarded to Remix IDE\n')
try {
startService('folder', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
startService('folder', (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error: any) => {
if (error) {
errorHandler(error, 'hardhat')
return false
}
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.setupNotifications(program.sharedFolder)
sharedFolderClient.sharedFolder(program.sharedFolder)
@ -85,7 +98,11 @@ function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callb
const hardhatConfigFilePath = absolutePath('./', program.sharedFolder) + '/hardhat.config.js'
const isHardhatProject = fs.existsSync(hardhatConfigFilePath)
if (isHardhatProject) {
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder, error: Error) => {
if (error) {
errorHandler(error, 'hardhat')
return false
}
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder)
})

@ -9,7 +9,7 @@ export default class WebSocket {
constructor (public port: number, public opt: WebsocketOpt, public getclient: () => ServiceClient) {} //eslint-disable-line
start (callback?: (ws: WS, client: ServiceClient) => void): void {
start (callback?: (ws: WS, client: ServiceClient, error?: Error) => void): void {
this.server = http.createServer((request, response) => {
console.log((new Date()) + ' Received request for ' + request.url)
response.writeHead(404)
@ -21,9 +21,15 @@ export default class WebSocket {
65521: 'git',
65522: 'hardhat'
}
this.server.on('error', (error: Error) => {
if (callback)callback(null, null, error)
})
this.server.listen(this.port, loopback, () => {
console.log('\x1b[32m%s\x1b[0m', `[INFO] ${new Date()} ${listeners[this.port]} is listening on ${loopback}:${this.port}`)
})
this.wsServer = new WS.Server({
server: this.server,
verifyClient: (info, done) => {

5278
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "remix-project",
"version": "0.12.0-dev",
"version": "0.14.0-dev",
"license": "MIT",
"description": "Ethereum Remix Monorepo",
"keywords": [
@ -44,7 +44,7 @@
"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",
"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",
"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",
"build:e2e": "tsc -p apps/remix-ide-e2e/tsconfig.e2e.json",
"watch:e2e": "nodemon",
"bumpVersion:libs": "gulp & gulp syncLibVersions;",
@ -130,10 +130,10 @@
},
"dependencies": {
"@erebos/bzz-node": "^0.13.0",
"@ethereumjs/block": "^3.2.1",
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.1.3",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.3.0",
"@ethereumjs/common": "^2.3.1",
"@ethereumjs/tx": "^3.2.1",
"@ethereumjs/vm": "^5.4.1",
"@remixproject/engine": "^0.3.17",
"@remixproject/engine-web": "^0.3.17",
"@remixproject/plugin": "^0.3.17",
@ -173,6 +173,7 @@
"selenium": "^2.20.0",
"signale": "^1.4.0",
"time-stamp": "^2.2.0",
"tslib": "^2.3.0",
"web3": "1.2.4",
"winston": "^3.3.3",
"ws": "^7.3.0"
@ -218,7 +219,7 @@
"ace-mode-lexon": "^1.*.*",
"ace-mode-move": "0.0.1",
"ace-mode-solidity": "^0.1.0",
"ace-mode-zokrates": "^1.0.0",
"ace-mode-zokrates": "^1.0.4",
"babel-eslint": "^10.0.0",
"babel-jest": "25.1.0",
"babel-plugin-add-module-exports": "^1.0.2",

Loading…
Cancel
Save