diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts
index 448642f4b5..a7d43153fa 100644
--- a/apps/remix-ide-e2e/src/tests/terminal.test.ts
+++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts
@@ -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"]', '[ "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db", "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB", "0x617F2E2fD72FD9D5503197092aC168c91465E7f2", "0x17F6AD8Ef982297579C203069C1DbfFE4348c372", "0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678", "0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7", "0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C", "0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC", "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C", "0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB", "0x583031D1113aD414F02576BD6afaBfb302140225", "0xdD870fA1b7C4700F2BD7f44238821C26f7392148" ]', 60000)
+ .waitForElementContainsText('*[data-id="terminalJournal"]', '"0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c", "0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C", "0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB", "0x583031D1113aD414F02576BD6afaBfb302140225", "0xdD870fA1b7C4700F2BD7f44238821C26f7392148"', 80000)
},
'Call web3.eth.getAccounts() using Web3 Provider': function (browser: NightwatchBrowser) {
diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js
index b05b4ef488..93dd6753b5 100644
--- a/apps/remix-ide/src/app.js
+++ b/apps/remix-ide/src/app.js
@@ -431,12 +431,14 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
engine.register([
compileTab,
+ compileTab.compileTabLogic,
run,
debug,
analysis,
test,
filePanel.remixdHandle,
- filePanel.gitHandle
+ filePanel.gitHandle,
+ filePanel.hardhatHandle
])
if (isElectron()) {
diff --git a/apps/remix-ide/src/app/files/file-explorer.js b/apps/remix-ide/src/app/files/file-explorer.js
index 9f5f3e3f69..cef877b68b 100644
--- a/apps/remix-ide/src/app/files/file-explorer.js
+++ b/apps/remix-ide/src/app/files/file-explorer.js
@@ -9,7 +9,7 @@ const helper = require('../../lib/helper')
const yo = require('yo-yo')
const Treeview = require('../ui/TreeView')
const modalDialog = require('../ui/modaldialog')
-const EventManager = require('../../lib/events')
+const EventManager = require('events')
const contextMenu = require('../ui/contextMenu')
const css = require('./styles/file-explorer-styles')
const globalRegistry = require('../../global/registry')
@@ -94,11 +94,11 @@ function fileExplorer (localRegistry, files, menuItems, plugin) {
})
// register to event of the file provider
- files.event.register('fileRemoved', fileRemoved)
- files.event.register('fileRenamed', fileRenamed)
- files.event.register('fileRenamedError', fileRenamedError)
- files.event.register('fileAdded', fileAdded)
- files.event.register('folderAdded', folderAdded)
+ files.event.on('fileRemoved', fileRemoved)
+ files.event.on('fileRenamed', fileRenamed)
+ files.event.on('fileRenamedError', fileRenamedError)
+ files.event.on('fileAdded', fileAdded)
+ files.event.on('folderAdded', folderAdded)
function fileRenamedError (error) {
modalDialogCustom.alert(error)
diff --git a/apps/remix-ide/src/app/files/fileManager.js b/apps/remix-ide/src/app/files/fileManager.js
index fd74b1722d..44e33ff0d2 100644
--- a/apps/remix-ide/src/app/files/fileManager.js
+++ b/apps/remix-ide/src/app/files/fileManager.js
@@ -329,18 +329,18 @@ class FileManager extends Plugin {
workspaceExplorer: this._components.registry.get('fileproviders/workspace').api,
filesProviders: this._components.registry.get('fileproviders').api
}
- this._deps.browserExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
- this._deps.browserExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
- this._deps.localhostExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
- this._deps.browserExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
- this._deps.browserExplorer.event.register('fileAdded', (path) => { this.fileAddedEvent(path) })
- this._deps.localhostExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
- this._deps.localhostExplorer.event.register('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
- this._deps.localhostExplorer.event.register('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
- this._deps.workspaceExplorer.event.register('fileChanged', (path) => { this.fileChangedEvent(path) })
- this._deps.workspaceExplorer.event.register('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
- this._deps.workspaceExplorer.event.register('fileRemoved', (path) => { this.fileRemovedEvent(path) })
- this._deps.workspaceExplorer.event.register('fileAdded', (path) => { this.fileAddedEvent(path) })
+ this._deps.browserExplorer.event.on('fileChanged', (path) => { this.fileChangedEvent(path) })
+ this._deps.browserExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
+ this._deps.localhostExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
+ this._deps.browserExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
+ this._deps.browserExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
+ this._deps.localhostExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
+ this._deps.localhostExplorer.event.on('errored', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
+ this._deps.localhostExplorer.event.on('closed', (event) => { this.removeTabsOf(this._deps.localhostExplorer) })
+ this._deps.workspaceExplorer.event.on('fileChanged', (path) => { this.fileChangedEvent(path) })
+ this._deps.workspaceExplorer.event.on('fileRenamed', (oldName, newName, isFolder) => { this.fileRenamedEvent(oldName, newName, isFolder) })
+ this._deps.workspaceExplorer.event.on('fileRemoved', (path) => { this.fileRemovedEvent(path) })
+ this._deps.workspaceExplorer.event.on('fileAdded', (path) => { this.fileAddedEvent(path) })
this.getCurrentFile = this.file
this.getFile = this.readFile
diff --git a/apps/remix-ide/src/app/files/fileProvider.js b/apps/remix-ide/src/app/files/fileProvider.js
index 521dcec453..5d152ec74b 100644
--- a/apps/remix-ide/src/app/files/fileProvider.js
+++ b/apps/remix-ide/src/app/files/fileProvider.js
@@ -1,7 +1,7 @@
'use strict'
const CompilerImport = require('../compiler/compiler-imports')
-const EventManager = require('../../lib/events')
+const EventManager = require('events')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const tooltip = require('../ui/tooltip')
const remixLib = require('@remix-project/remix-lib')
@@ -111,9 +111,9 @@ class FileProvider {
return false
}
if (!exists) {
- this.event.trigger('fileAdded', [this._normalizePath(unprefixedpath), false])
+ this.event.emit('fileAdded', this._normalizePath(unprefixedpath), false)
} else {
- this.event.trigger('fileChanged', [this._normalizePath(unprefixedpath)])
+ this.event.emit('fileChanged', this._normalizePath(unprefixedpath))
}
cb()
return true
@@ -128,7 +128,7 @@ class FileProvider {
currentCheck = currentCheck + '/' + value
if (!window.remixFileSystem.existsSync(currentCheck)) {
window.remixFileSystem.mkdirSync(currentCheck)
- this.event.trigger('folderAdded', [this._normalizePath(currentCheck)])
+ this.event.emit('folderAdded', this._normalizePath(currentCheck))
}
})
if (cb) cb()
@@ -184,7 +184,7 @@ class FileProvider {
// folder is empty
window.remixFileSystem.rmdirSync(path, console.log)
}
- this.event.trigger('fileRemoved', [this._normalizePath(path)])
+ this.event.emit('fileRemoved', this._normalizePath(path))
}
} catch (e) {
console.log(e)
@@ -249,7 +249,7 @@ class FileProvider {
path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path) && !window.remixFileSystem.statSync(path).isDirectory()) {
window.remixFileSystem.unlinkSync(path, console.log)
- this.event.trigger('fileRemoved', [this._normalizePath(path)])
+ this.event.emit('fileRemoved', this._normalizePath(path))
return true
} else return false
}
@@ -259,11 +259,11 @@ class FileProvider {
var unprefixednewPath = this.removePrefix(newPath)
if (this._exists(unprefixedoldPath)) {
window.remixFileSystem.renameSync(unprefixedoldPath, unprefixednewPath)
- this.event.trigger('fileRenamed', [
+ this.event.emit('fileRenamed',
this._normalizePath(unprefixedoldPath),
this._normalizePath(unprefixednewPath),
isFolder
- ])
+ )
return true
}
return false
diff --git a/apps/remix-ide/src/app/files/hardhat-handle.js b/apps/remix-ide/src/app/files/hardhat-handle.js
new file mode 100644
index 0000000000..e9e8b770ef
--- /dev/null
+++ b/apps/remix-ide/src/app/files/hardhat-handle.js
@@ -0,0 +1,18 @@
+import { WebsocketPlugin } from '@remixproject/engine-web'
+import * as packageJson from '../../../../../package.json'
+
+const profile = {
+ name: 'hardhat',
+ displayName: 'Hardhat',
+ url: 'ws://127.0.0.1:65522',
+ methods: ['compile'],
+ description: 'Using Remixd daemon, allow to access hardhat API',
+ kind: 'other',
+ version: packageJson.version
+}
+
+export class HardhatHandle extends WebsocketPlugin {
+ constructor () {
+ super(profile)
+ }
+}
diff --git a/apps/remix-ide/src/app/files/remixDProvider.js b/apps/remix-ide/src/app/files/remixDProvider.js
index 60337c11e4..bc59bfe90b 100644
--- a/apps/remix-ide/src/app/files/remixDProvider.js
+++ b/apps/remix-ide/src/app/files/remixDProvider.js
@@ -17,32 +17,32 @@ module.exports = class RemixDProvider extends FileProvider {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((value) => {
this._appManager.on('remixd', value, (event) => {
- this.event.trigger(value, [event])
+ this.event.emit(value, event)
})
})
this._appManager.on('remixd', 'folderAdded', (path) => {
- this.event.trigger('folderAdded', [path])
+ this.event.emit('folderAdded', path)
})
this._appManager.on('remixd', 'fileAdded', (path) => {
- this.event.trigger('fileAdded', [path])
+ this.event.emit('fileAdded', path)
})
this._appManager.on('remixd', 'fileChanged', (path) => {
- this.event.trigger('fileChanged', [path])
+ this.event.emit('fileChanged', path)
})
this._appManager.on('remixd', 'fileRemoved', (path) => {
- this.event.trigger('fileRemoved', [path])
+ this.event.emit('fileRemoved', path)
})
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
- this.event.trigger('fileRemoved', [oldPath, newPath])
+ this.event.emit('fileRemoved', oldPath, newPath)
})
this._appManager.on('remixd', 'rootFolderChanged', () => {
- this.event.trigger('rootFolderChanged', [])
+ this.event.emit('rootFolderChanged')
})
}
@@ -53,11 +53,11 @@ module.exports = class RemixDProvider extends FileProvider {
close (cb) {
this._isReady = false
cb()
- this.event.trigger('disconnected')
+ this.event.emit('disconnected')
}
preInit () {
- this.event.trigger('loading')
+ this.event.emit('loading')
}
init (cb) {
@@ -67,7 +67,7 @@ module.exports = class RemixDProvider extends FileProvider {
this._isReady = true
this._readOnlyMode = result
this._registerEvent()
- this.event.trigger('connected')
+ this.event.emit('connected')
cb && cb()
}).catch((error) => {
cb && cb(error)
@@ -164,13 +164,13 @@ module.exports = class RemixDProvider extends FileProvider {
this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath]
this.init(() => {
- this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
+ this.event.emit('fileRenamed', oldPath, newPath, isFolder)
})
return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
- this.event.trigger('fileRenamedError', [this.error[error.code]])
+ this.event.emit('fileRenamedError', this.error[error.code])
})
}
diff --git a/apps/remix-ide/src/app/files/remixd-handle.js b/apps/remix-ide/src/app/files/remixd-handle.js
index 3ec635040c..a2d94eb3ea 100644
--- a/apps/remix-ide/src/app/files/remixd-handle.js
+++ b/apps/remix-ide/src/app/files/remixd-handle.js
@@ -39,6 +39,7 @@ export class RemixdHandle extends WebsocketPlugin {
deactivate () {
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
+ this.appManager.deactivatePlugin('hardhat')
this.locahostProvider.close((error) => {
if (error) console.log(error)
})
@@ -51,6 +52,7 @@ export class RemixdHandle extends WebsocketPlugin {
async canceled () {
// await this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
await this.appManager.deactivatePlugin('remixd')
+ await this.appManager.deactivatePlugin('hardhat')
}
/**
@@ -81,7 +83,7 @@ export class RemixdHandle extends WebsocketPlugin {
}
}, 3000)
this.locahostProvider.init(() => {})
- // this.call('manager', 'activatePlugin', 'git')
+ this.call('manager', 'activatePlugin', 'hardhat')
}
}
if (this.locahostProvider.isConnected()) {
diff --git a/apps/remix-ide/src/app/files/workspaceFileProvider.js b/apps/remix-ide/src/app/files/workspaceFileProvider.js
index 156723b622..4b0ac2af2e 100644
--- a/apps/remix-ide/src/app/files/workspaceFileProvider.js
+++ b/apps/remix-ide/src/app/files/workspaceFileProvider.js
@@ -1,6 +1,6 @@
'use strict'
-const EventManager = require('../../lib/events')
+const EventManager = require('events')
const FileProvider = require('./fileProvider')
const pathModule = require('path')
@@ -82,7 +82,7 @@ class WorkspaceFileProvider extends FileProvider {
createWorkspace (name) {
if (!name) name = 'default_workspace'
- this.event.trigger('createWorkspace', [name])
+ this.event.emit('createWorkspace', name)
}
}
diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js
index 991108285c..ac7ae97089 100644
--- a/apps/remix-ide/src/app/panels/file-panel.js
+++ b/apps/remix-ide/src/app/panels/file-panel.js
@@ -6,13 +6,13 @@ import ReactDOM from 'react-dom'
import { Workspace } from '@remix-ui/workspace' // eslint-disable-line
import { bufferToHex, keccakFromString } from 'ethereumjs-util'
import { checkSpecialChars, checkSlash } from '../../lib/helper'
-var EventManager = require('../../lib/events')
-var { RemixdHandle } = require('../files/remixd-handle.js')
-var { GitHandle } = require('../files/git-handle.js')
-var globalRegistry = require('../../global/registry')
-var examples = require('../editor/examples')
-var GistHandler = require('../../lib/gist-handler')
-var QueryParams = require('../../lib/query-params')
+const { RemixdHandle } = require('../files/remixd-handle.js')
+const { GitHandle } = require('../files/git-handle.js')
+const { HardhatHandle } = require('../files/hardhat-handle.js')
+const globalRegistry = require('../../global/registry')
+const examples = require('../editor/examples')
+const GistHandler = require('../../lib/gist-handler')
+const QueryParams = require('../../lib/query-params')
const modalDialogCustom = require('../ui/modal-dialog-custom')
/*
Overview of APIs:
@@ -47,7 +47,6 @@ const profile = {
module.exports = class Filepanel extends ViewPlugin {
constructor (appManager) {
super(profile)
- this.event = new EventManager()
this._components = {}
this._components.registry = globalRegistry
this._deps = {
@@ -60,6 +59,7 @@ module.exports = class Filepanel extends ViewPlugin {
this.remixdHandle = new RemixdHandle(this._deps.fileProviders.localhost, appManager)
this.gitHandle = new GitHandle()
+ this.hardhatHandle = new HardhatHandle()
this.registeredMenuItems = []
this.request = {}
this.workspaces = []
diff --git a/apps/remix-ide/src/app/tabs/compile-tab.js b/apps/remix-ide/src/app/tabs/compile-tab.js
index 12b93ff594..8bb5b48575 100644
--- a/apps/remix-ide/src/app/tabs/compile-tab.js
+++ b/apps/remix-ide/src/app/tabs/compile-tab.js
@@ -65,15 +65,10 @@ class CompileTab extends ViewPlugin {
eventHandlers: {},
loading: false
}
+ this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProvider, this.contentImport)
}
onActivationInternal () {
- const miscApi = {
- clearAnnotations: () => {
- this.call('editor', 'clearAnnotations')
- }
- }
- this.compileTabLogic = new CompileTabLogic(this.queryParams, this.fileManager, this.editor, this.config, this.fileProvider, this.contentImport, miscApi)
this.compiler = this.compileTabLogic.compiler
this.compileTabLogic.init()
@@ -85,11 +80,28 @@ class CompileTab extends ViewPlugin {
)
}
+ resetResults () {
+ if (this._view.errorContainer) {
+ this._view.errorContainer.innerHTML = ''
+ }
+ this.compilerContainer.currentFile = ''
+ this.data.contractsDetails = {}
+ yo.update(this._view.contractSelection, this.contractSelection())
+ this.emit('statusChanged', { key: 'none' })
+ }
+
/************
* EVENTS
*/
listenToEvents () {
+ this.on('filePanel', 'setWorkspace', (workspace) => {
+ this.compileTabLogic.isHardhatProject().then((result) => {
+ if (result && workspace.isLocalhost) this.compilerContainer.hardhatCompilation.style.display = 'flex'
+ else this.compilerContainer.hardhatCompilation.style.display = 'none'
+ })
+ })
+
this.data.eventHandlers.onContentChanged = () => {
this.emit('statusChanged', { key: 'edited', title: 'the content has changed, needs recompilation', type: 'info' })
}
@@ -113,6 +125,9 @@ class CompileTab extends ViewPlugin {
}
this.emit('statusChanged', { key: 'loading', title: 'compiling...', type: 'info' })
}
+
+ this.on('filePanel', 'setWorkspace', () => this.resetResults())
+
this.compileTabLogic.event.on('startingCompilation', this.data.eventHandlers.onStartingCompilation)
this.data.eventHandlers.onCurrentFileChanged = (name) => {
@@ -199,7 +214,7 @@ class CompileTab extends ViewPlugin {
// ctrl+s or command+s
if ((e.metaKey || e.ctrlKey) && e.keyCode === 83) {
e.preventDefault()
- this.compileTabLogic.runCompiler()
+ this.compileTabLogic.runCompiler(this.compilerContainer.hhCompilation)
}
})
}
@@ -479,6 +494,7 @@ class CompileTab extends ViewPlugin {
}
onActivation () {
+ this.call('manager', 'activatePlugin', 'solidity-logic')
this.listenToEvents()
}
@@ -492,6 +508,7 @@ class CompileTab extends ViewPlugin {
this.fileManager.events.removeListener('noFileSelected', this.data.eventHandlers.onNoFileSelected)
this.compiler.event.unregister('compilationFinished', this.data.eventHandlers.onCompilationFinished)
globalRegistry.get('themeModule').api.events.removeListener('themeChanged', this.data.eventHandlers.onThemeChanged)
+ this.call('manager', 'deactivatePlugin', 'solidity-logic')
}
}
diff --git a/apps/remix-ide/src/app/tabs/compileTab/compileTab.js b/apps/remix-ide/src/app/tabs/compileTab/compileTab.js
index 79228d1663..1e5de9d5c7 100644
--- a/apps/remix-ide/src/app/tabs/compileTab/compileTab.js
+++ b/apps/remix-ide/src/app/tabs/compileTab/compileTab.js
@@ -1,10 +1,19 @@
+import * as packageJson from '../../../../../../package.json'
+import { Plugin } from '@remixproject/engine'
const EventEmitter = require('events')
var Compiler = require('@remix-project/remix-solidity').Compiler
-class CompileTab {
- constructor (queryParams, fileManager, editor, config, fileProvider, contentImport, miscApi) {
+const profile = {
+ name: 'solidity-logic',
+ displayName: 'Solidity compiler logic',
+ description: 'Compile solidity contracts - Logic',
+ version: packageJson.version
+}
+
+class CompileTab extends Plugin {
+ constructor (queryParams, fileManager, editor, config, fileProvider, contentImport) {
+ super(profile)
this.event = new EventEmitter()
- this.miscApi = miscApi
this.queryParams = queryParams
this.compilerImport = contentImport
this.compiler = new Compiler((url, cb) => this.compilerImport.resolveAndSave(url).then((result) => cb(null, result)).catch((error) => cb(error.message)))
@@ -78,10 +87,32 @@ class CompileTab {
})
}
- runCompiler () {
+ async isHardhatProject () {
+ if (this.fileManager.mode === 'localhost') {
+ return await this.fileManager.exists('hardhat.config.js')
+ } else return false
+ }
+
+ runCompiler (hhCompilation) {
try {
+ if (this.fileManager.mode === 'localhost' && hhCompilation) {
+ const { currentVersion, optimize, runs } = this.compiler.state
+ const fileContent = `module.exports = {
+ solidity: '${currentVersion.substring(0, currentVersion.indexOf('+commit'))}',
+ settings: {
+ optimizer: {
+ enabled: ${optimize},
+ runs: ${runs}
+ }
+ }
+ }
+ `
+ const configFilePath = 'remix-compiler.config.js'
+ this.fileManager.setFileContent(configFilePath, fileContent)
+ this.call('hardhat', 'compile', configFilePath)
+ }
this.fileManager.saveCurrentFile()
- this.miscApi.clearAnnotations()
+ this.call('editor', 'clearAnnotations')
var currentFile = this.config.get('currentFile')
return this.compileFile(currentFile)
} catch (err) {
diff --git a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js b/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
index 88880bff22..73d0802bf4 100644
--- a/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
+++ b/apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
@@ -15,6 +15,7 @@ class CompilerContainer {
this.editor = editor
this.config = config
this.queryParams = queryParams
+ this.hhCompilation = false
this.data = {
hideWarnings: config.get('hideWarnings') || false,
@@ -183,6 +184,10 @@ class CompilerContainer {
}
})
+ this.hardhatCompilation = yo`
+ this.updatehhCompilation(e)} id="enableHardhat" type="checkbox" title="Enable Hardhat Compilation">
+
+
`
this._view.warnCompilationSlow = yo``
this._view.compileIcon = yo``
this._view.autoCompile = yo` this.updateAutoCompile()} data-id="compilerContainerAutoCompile" id="autoCompile" type="checkbox" title="Auto compile">`
@@ -299,6 +304,7 @@ class CompilerContainer {
+ ${this.hardhatCompilation}
${this._view.compilationButton}
@@ -326,12 +332,16 @@ class CompilerContainer {
this.config.set('autoCompile', this._view.autoCompile.checked)
}
+ updatehhCompilation (event) {
+ this.hhCompilation = event.target.checked
+ }
+
compile (event) {
const currentFile = this.config.get('currentFile')
if (!this.isSolFileSelected()) return
this._setCompilerVersionFromPragma(currentFile)
- this.compileTabLogic.runCompiler()
+ this.compileTabLogic.runCompiler(this.hhCompilation)
}
compileIfAutoCompileOn () {
diff --git a/apps/remix-ide/src/app/tabs/network-module.js b/apps/remix-ide/src/app/tabs/network-module.js
index fdae172c15..c1cc1e12b5 100644
--- a/apps/remix-ide/src/app/tabs/network-module.js
+++ b/apps/remix-ide/src/app/tabs/network-module.js
@@ -22,18 +22,6 @@ export class NetworkModule extends Plugin {
this.blockchain.event.register('contextChanged', (provider) => {
this.emit('providerChanged', provider)
})
- /*
- // Events that could be implemented later
- executionContext.event.register('removeProvider', (provider) => {
- this.events.emit('networkRemoved', provider)
- })
- executionContext.event.register('addProvider', (provider) => {
- this.events.emit('networkAdded', provider)
- })
- executionContext.event.register('web3EndpointChanged', (provider) => {
- this.events.emit('web3EndpointChanged', provider)
- })
- */
}
/** Return the current network provider (web3, vm, injected) */
diff --git a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
index 126b354927..f9a5d9aa78 100644
--- a/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
+++ b/apps/remix-ide/src/app/tabs/runTab/model/recorder.js
@@ -63,10 +63,10 @@ class Recorder {
}
})
- this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload, rawAddress) => {
+ this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload) => {
if (error) return console.log(error)
if (call) return
-
+ const rawAddress = txResult.receipt.contractAddress
if (!rawAddress) return // not a contract creation
const address = helper.addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses
diff --git a/apps/remix-ide/src/app/tabs/test-tab.js b/apps/remix-ide/src/app/tabs/test-tab.js
index eb6edd4efb..652be0b9c9 100644
--- a/apps/remix-ide/src/app/tabs/test-tab.js
+++ b/apps/remix-ide/src/app/tabs/test-tab.js
@@ -52,7 +52,7 @@ module.exports = class TestTab extends ViewPlugin {
}
listenToEvents () {
- this.filePanel.event.register('newTestFileCreated', file => {
+ this.on('filePanel', 'newTestFileCreated', file => {
var testList = this._view.el.querySelector("[class^='testList']")
var test = this.createSingleTest(file)
testList.appendChild(test)
diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js
index 84062f7ac4..973e267683 100644
--- a/apps/remix-ide/src/blockchain/blockchain.js
+++ b/apps/remix-ide/src/blockchain/blockchain.js
@@ -3,16 +3,17 @@ const txFormat = remixLib.execution.txFormat
const txExecution = remixLib.execution.txExecution
const typeConversion = remixLib.execution.typeConversion
const Txlistener = remixLib.execution.txListener
-const TxRunner = remixLib.execution.txRunner
+const TxRunner = remixLib.execution.TxRunner
+const TxRunnerWeb3 = remixLib.execution.TxRunnerWeb3
const txHelper = remixLib.execution.txHelper
const EventManager = remixLib.EventManager
-const executionContext = remixLib.execution.executionContext
+const { ExecutionContext } = require('./execution-context')
const Web3 = require('web3')
const async = require('async')
const { EventEmitter } = require('events')
-const { resultToRemixTx } = require('./txResultHelper')
+const { resultToRemixTx } = remixLib.helpers.txResultHelper
const VMProvider = require('./providers/vm.js')
const InjectedProvider = require('./providers/injected.js')
@@ -22,12 +23,11 @@ class Blockchain {
// NOTE: the config object will need to be refactored out in remix-lib
constructor (config) {
this.event = new EventManager()
- this.executionContext = executionContext
+ this.executionContext = new ExecutionContext()
this.events = new EventEmitter()
this.config = config
-
- this.txRunner = new TxRunner({}, {
+ const web3Runner = new TxRunnerWeb3({
config: config,
detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
@@ -35,7 +35,9 @@ class Blockchain {
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}
- }, this.executionContext)
+ }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit())
+ this.txRunner = new TxRunner(web3Runner, { runAsync: true })
+
this.executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
this.networkcallid = 0
@@ -123,7 +125,7 @@ class Blockchain {
if (error) {
return finalCb(`creation of ${selectedContract.name} errored: ${(error.message ? error.message : error)}`)
}
- if (txResult.result.status && txResult.result.status === '0x0') {
+ if (txResult.receipt.status === false || txResult.receipt.status === '0x0') {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
}
finalCb(null, selectedContract, address)
@@ -309,18 +311,17 @@ class Blockchain {
resetEnvironment () {
this.getCurrentProvider().resetEnvironment()
// TODO: most params here can be refactored away in txRunner
- // this.txRunner = new TxRunner(this.providers.vm.accounts, {
- this.txRunner = new TxRunner(this.providers.vm.RemixSimulatorProvider.Accounts.accounts, {
- // TODO: only used to check value of doNotShowTransactionConfirmationAgain property
+ const web3Runner = new TxRunnerWeb3({
config: this.config,
- // TODO: to refactor, TxRunner already has access to executionContext
detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb)
},
personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
}
- }, this.executionContext)
+ }, _ => this.executionContext.web3(), _ => this.executionContext.currentblockGasLimit())
+
+ this.txRunner = new TxRunner(web3Runner, { runAsync: true })
this.txRunner.event.register('transactionBroadcasted', (txhash) => {
this.executionContext.detectNetwork((error, network) => {
if (error || !network) return
@@ -372,10 +373,11 @@ class Blockchain {
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
(error, continueTxExecution, cancelCb) => { if (error) { reject(error) } else { continueTxExecution() } },
(okCb, cancelCb) => { okCb() },
- (error, result) => {
+ async (error, result) => {
if (error) return reject(error)
try {
- resolve(resultToRemixTx(result))
+ const execResult = await this.web3().eth.getExecutionResultFromSimulator(result.transactionHash)
+ resolve(resultToRemixTx(result, execResult))
} catch (e) {
reject(e)
}
@@ -429,19 +431,24 @@ class Blockchain {
function runTransaction (fromAddress, value, gasLimit, next) {
const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
- let timestamp = Date.now()
- if (tx.timestamp) {
- timestamp = tx.timestamp
- }
+ if (!tx.timestamp) tx.timestamp = Date.now()
+ const timestamp = tx.timestamp
self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
- function (error, result) {
+ async (error, result) => {
if (error) return next(error)
- const rawAddress = self.executionContext.isVM() ? (result.result.createdAddress && result.result.createdAddress.toBuffer()) : result.result.contractAddress
+ const isVM = self.executionContext.isVM()
+ if (isVM && tx.useCall) {
+ try {
+ result.transactionHash = await self.web3().eth.getHashFromTagBySimulator(timestamp)
+ } catch (e) {
+ console.log('unable to retrieve back the "call" hash', e)
+ }
+ }
const eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
- self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad, rawAddress])
+ self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
if (error && (typeof (error) !== 'string')) {
if (error.message) error = error.message
@@ -454,25 +461,29 @@ class Blockchain {
)
}
],
- (error, txResult) => {
+ async (error, txResult) => {
if (error) {
return cb(error)
}
const isVM = this.executionContext.isVM()
+ let execResult
+ let returnValue = null
if (isVM) {
- const vmError = txExecution.checkVMError(txResult)
- if (vmError.error) {
- return cb(vmError.message)
+ execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
+ if (execResult) {
+ // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
+ returnValue = (execResult && isVM) ? execResult.returnValue : txResult
+ const vmError = txExecution.checkVMError(execResult)
+ if (vmError.error) {
+ return cb(vmError.message)
+ }
}
}
let address = null
- let returnValue = null
- if (txResult && txResult.result) {
- address = isVM ? (txResult.result.createdAddress && txResult.result.createdAddress.toBuffer()) : txResult.result.contractAddress
- // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
- returnValue = (txResult.result.execResult && isVM) ? txResult.result.execResult.returnValue : txResult.result
+ if (txResult && txResult.receipt) {
+ address = txResult.receipt.contractAddress
}
cb(error, txResult, address, returnValue)
diff --git a/libs/remix-lib/src/execution/execution-context.ts b/apps/remix-ide/src/blockchain/execution-context.js
similarity index 60%
rename from libs/remix-lib/src/execution/execution-context.ts
rename to apps/remix-ide/src/blockchain/execution-context.js
index 31212412ff..41588e3708 100644
--- a/libs/remix-lib/src/execution/execution-context.ts
+++ b/apps/remix-ide/src/blockchain/execution-context.js
@@ -1,139 +1,33 @@
/* global ethereum */
'use strict'
import Web3 from 'web3'
-import { EventManager } from '../eventManager'
-import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
-import { Web3VmProvider } from '../web3Provider/web3VmProvider'
-import { LogsManager } from './logsManager'
-import VM from '@ethereumjs/vm'
-import Common from '@ethereumjs/common'
-import StateManager from '@ethereumjs/vm/dist/state/stateManager'
-import { StorageDump } from '@ethereumjs/vm/dist/state/interface'
+import EventManager from '../lib/events'
-declare let ethereum: any
let web3
-if (typeof window !== 'undefined' && typeof window['ethereum'] !== 'undefined') {
- var injectedProvider = window['ethereum']
+if (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined') {
+ var injectedProvider = window.ethereum
web3 = new Web3(injectedProvider)
} else {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
}
-/*
- extend vm state manager and instanciate VM
-*/
-
-class StateManagerCommonStorageDump extends StateManager {
- /*
- * dictionary containing keccak(b) as key and b as value. used to get the initial value from its hash
- */
- keyHashes: { [key: string]: string }
- constructor () {
- super()
- this.keyHashes = {}
- }
-
- putContractStorage (address, key, value) {
- this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key)
- return super.putContractStorage(address, key, value)
- }
-
- async dumpStorage (address) {
- let trie
- try {
- trie = await this._getStorageTrie(address)
- } catch (e) {
- console.log(e)
- throw e
- }
- return new Promise((resolve, reject) => {
- try {
- const storage = {}
- const stream = trie.createReadStream()
- stream.on('data', (val) => {
- const value = rlp.decode(val.value)
- storage['0x' + val.key.toString('hex')] = {
- key: this.keyHashes[val.key.toString('hex')],
- value: '0x' + value.toString('hex')
- }
- })
- stream.on('end', function () {
- resolve(storage)
- })
- } catch (e) {
- reject(e)
- }
- })
- }
-
- async getStateRoot (force: boolean = false): Promise {
- await this._cache.flush()
-
- const stateRoot = this._trie.root
- return stateRoot
- }
-
- async setStateRoot (stateRoot: Buffer): Promise {
- await this._cache.flush()
-
- if (stateRoot === this._trie.EMPTY_TRIE_ROOT) {
- this._trie.root = stateRoot
- this._cache.clear()
- this._storageTries = {}
- return
- }
-
- const hasRoot = await this._trie.checkRoot(stateRoot)
- if (!hasRoot) {
- throw new Error('State trie does not contain state root')
- }
-
- this._trie.root = stateRoot
- this._cache.clear()
- this._storageTries = {}
- }
-}
-
/*
trigger contextChanged, web3EndpointChanged
*/
export class ExecutionContext {
- event
- logsManager
- blockGasLimitDefault
- blockGasLimit
- customNetWorks
- blocks
- latestBlockNumber
- txs
- executionContext
- listenOnLastBlockId
- currentFork: string
- vms
- mainNetGenesisHash: string
-
constructor () {
this.event = new EventManager()
- this.logsManager = new LogsManager()
this.executionContext = null
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.mainNetGenesisHash = '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
this.customNetWorks = {}
this.blocks = {}
this.latestBlockNumber = 0
this.txs = {}
+ this.customWeb3 = {} // mapping between a context name and a web3.js instance
}
init (config) {
@@ -145,20 +39,6 @@ export class ExecutionContext {
}
}
- createVm (hardfork) {
- const stateManager = new StateManagerCommonStorageDump()
- const common = new Common({ chain: 'mainnet', hardfork })
- const vm = new VM({
- common,
- activatePrecompiles: true,
- stateManager: stateManager
- })
-
- const web3vm = new Web3VmProvider()
- web3vm.setVM(vm)
- return { vm, web3vm, stateManager, common }
- }
-
askPermission () {
// metamask
if (ethereum && typeof ethereum.enable === 'function') ethereum.enable()
@@ -172,7 +52,12 @@ export class ExecutionContext {
return this.executionContext === 'vm'
}
+ setWeb3 (context, web3) {
+ this.customWeb3[context] = web3
+ }
+
web3 () {
+ if (this.customWeb3[this.executionContext]) return this.customWeb3[this.executionContext]
return this.isVM() ? this.vms[this.currentFork].web3vm : web3
}
@@ -228,14 +113,6 @@ export class ExecutionContext {
return new Web3()
}
- vm () {
- return this.vms[this.currentFork].vm
- }
-
- vmObject () {
- return this.vms[this.currentFork]
- }
-
setContext (context, endPointUrl, confirmCb, infoCb) {
this.executionContext = context
this.executionContextChange(context, endPointUrl, confirmCb, infoCb, null)
@@ -340,22 +217,4 @@ export class ExecutionContext {
return transactionDetailsLinks[network] + hash
}
}
-
- addBlock (block) {
- let blockNumber = '0x' + block.header.number.toString('hex')
- if (blockNumber === '0x') {
- blockNumber = '0x0'
- }
- blockNumber = web3.utils.toHex(web3.utils.toBN(blockNumber))
-
- this.blocks['0x' + block.hash().toString('hex')] = block
- this.blocks[blockNumber] = block
- this.latestBlockNumber = blockNumber
-
- this.logsManager.checkBlock(blockNumber, block, this.web3())
- }
-
- trackTx (tx, block) {
- this.txs[tx] = block
- }
}
diff --git a/apps/remix-ide/src/blockchain/providers/vm.js b/apps/remix-ide/src/blockchain/providers/vm.js
index 8ec6583ff7..e01b7b08d2 100644
--- a/apps/remix-ide/src/blockchain/providers/vm.js
+++ b/apps/remix-ide/src/blockchain/providers/vm.js
@@ -1,14 +1,16 @@
const Web3 = require('web3')
const { BN, privateToAddress, hashPersonalMessage } = require('ethereumjs-util')
-const RemixSimulator = require('@remix-project/remix-simulator')
+const { Provider, extend } = require('@remix-project/remix-simulator')
class VMProvider {
constructor (executionContext) {
this.executionContext = executionContext
- this.RemixSimulatorProvider = new RemixSimulator.Provider({ executionContext: this.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) {
diff --git a/apps/remix-ide/src/blockchain/txResultHelper.js b/apps/remix-ide/src/blockchain/txResultHelper.js
deleted file mode 100644
index f608324062..0000000000
--- a/apps/remix-ide/src/blockchain/txResultHelper.js
+++ /dev/null
@@ -1,46 +0,0 @@
-'use strict'
-const { bufferToHex, isHexString } = require('ethereumjs-util')
-
-function convertToPrefixedHex (input) {
- if (input === undefined || input === null || isHexString(input)) {
- return input
- } else if (Buffer.isBuffer(input)) {
- return bufferToHex(input)
- }
- return '0x' + input.toString(16)
-}
-
-/*
- txResult.result can be 3 different things:
- - VM call or tx: ethereumjs-vm result object
- - Node transaction: object returned from eth.getTransactionReceipt()
- - Node call: return value from function call (not an object)
-
- Also, VM results use BN and Buffers, Node results use hex strings/ints,
- So we need to normalize the values to prefixed hex strings
-*/
-function resultToRemixTx (txResult) {
- const { result, transactionHash } = txResult
- const { status, execResult, gasUsed, createdAddress, contractAddress } = result
- let returnValue, errorMessage
-
- if (isHexString(result)) {
- returnValue = result
- } else if (execResult !== undefined) {
- returnValue = execResult.returnValue
- errorMessage = execResult.exceptionError
- }
-
- return {
- transactionHash,
- status,
- gasUsed: convertToPrefixedHex(gasUsed),
- error: errorMessage,
- return: convertToPrefixedHex(returnValue),
- createdAddress: convertToPrefixedHex(createdAddress || contractAddress)
- }
-}
-
-module.exports = {
- resultToRemixTx
-}
diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js
index b495f53c81..7e7f9cd10f 100644
--- a/apps/remix-ide/src/remixAppManager.js
+++ b/apps/remix-ide/src/remixAppManager.js
@@ -11,10 +11,10 @@ const requiredModules = [ // services + layout views + system views
'fileManager', 'contentImport', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp']
-const dependentModules = ['git'] // module which shouldn't be manually activated (e.g git is activated by remixd)
+const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) {
- const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons']
+ const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
diff --git a/jest.config.js b/jest.config.js
index 30b91f3cbe..4b90409756 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -5,5 +5,18 @@ module.exports = {
},
resolver: '@nrwl/jest/plugins/resolver',
moduleFileExtensions: ['ts', 'js', 'html'],
- coverageReporters: ['html']
+ coverageReporters: ['html'],
+ moduleNameMapper:{
+ "@remix-project/remix-analyzer": "/../../dist/libs/remix-analyzer/index.js",
+ "@remix-project/remix-astwalker": "/../../dist/libs/remix-astwalker/index.js",
+ "@remix-project/remix-debug": "/../../dist/libs/remix-debug/src/index.js",
+ "@remix-project/remix-lib": "/../../dist/libs/remix-lib/src/index.js",
+ "@remix-project/remix-simulator": "/../../dist/libs/remix-simulator/src/index.js",
+ "@remix-project/remix-solidity": "/../../dist/libs/remix-solidity/index.js",
+ "@remix-project/remix-tests": "/../../dist/libs/remix-tests/src/index.js",
+ "@remix-project/remix-url-resolver":
+ "/../../dist/libs/remix-url-resolver/index.js"
+ ,
+ "@remix-project/remixd": "/../../dist/libs/remixd/index.js"
+ }
};
diff --git a/libs/remix-lib/src/execution/txExecution.ts b/libs/remix-lib/src/execution/txExecution.ts
index 84edfc1f8e..8eb1c927dd 100644
--- a/libs/remix-lib/src/execution/txExecution.ts
+++ b/libs/remix-lib/src/execution/txExecution.ts
@@ -53,10 +53,10 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner,
/**
* check if the vm has errored
*
- * @param {Object} txResult - the value returned by the vm
+ * @param {Object} execResult - execution result given by the VM
* @return {Object} - { error: true/false, message: DOMNode }
*/
-export function checkVMError (txResult) {
+export function checkVMError (execResult) {
const errorCode = {
OUT_OF_GAS: 'out of gas',
STACK_UNDERFLOW: 'stack underflow',
@@ -74,10 +74,10 @@ export function checkVMError (txResult) {
error: false,
message: ''
}
- if (!txResult.result.execResult.exceptionError) {
+ if (!execResult.exceptionError) {
return ret
}
- const exceptionError = txResult.result.execResult.exceptionError.error || ''
+ const exceptionError = execResult.exceptionError.error || ''
const error = `VM error: ${exceptionError}.\n`
let msg
if (exceptionError === errorCode.INVALID_OPCODE) {
@@ -87,7 +87,7 @@ export function checkVMError (txResult) {
msg = '\tThe transaction ran out of gas. Please increase the Gas Limit.\n'
ret.error = true
} else if (exceptionError === errorCode.REVERT) {
- const returnData = txResult.result.execResult.returnValue
+ const returnData = execResult.returnValue
// It is the hash of Error(string)
if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) {
const abiCoder = new ethers.utils.AbiCoder()
diff --git a/libs/remix-lib/src/execution/txFormat.ts b/libs/remix-lib/src/execution/txFormat.ts
index 7d3188a1d0..62f90be1a8 100644
--- a/libs/remix-lib/src/execution/txFormat.ts
+++ b/libs/remix-lib/src/execution/txFormat.ts
@@ -314,7 +314,7 @@ export function deployLibrary (libraryName, libraryShortName, library, contracts
if (err) {
return callback(err)
}
- const address = txResult.result.createdAddress || txResult.result.contractAddress
+ const address = txResult.receipt.contractAddress
library.address = address
callback(err, address)
})
diff --git a/libs/remix-lib/src/execution/txListener.ts b/libs/remix-lib/src/execution/txListener.ts
index fea5cdf270..2ec85c3fc9 100644
--- a/libs/remix-lib/src/execution/txListener.ts
+++ b/libs/remix-lib/src/execution/txListener.ts
@@ -4,17 +4,16 @@ import { ethers } from 'ethers'
import { toBuffer } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
import { compareByteCode } from '../util'
-import { ExecutionContext } from './execution-context'
import { decodeResponse } from './txFormat'
import { getFunction, getReceiveInterface, getConstructorInterface, visitContracts, makeFullTypeDefinition } from './txHelper'
-function addExecutionCosts (txResult, tx) {
- if (txResult && txResult.result) {
- if (txResult.result.execResult) {
- tx.returnValue = txResult.result.execResult.returnValue
- if (txResult.result.execResult.gasUsed) tx.executionCost = txResult.result.execResult.gasUsed.toString(10)
+function addExecutionCosts (txResult, tx, execResult) {
+ if (txResult) {
+ if (execResult) {
+ tx.returnValue = execResult.returnValue
+ if (execResult.gasUsed) tx.executionCost = execResult.gasUsed.toString(10)
}
- if (txResult.result.gasUsed) tx.transactionCost = txResult.result.gasUsed.toString(10)
+ if (txResult.receipt && txResult.receipt.gasUsed) tx.transactionCost = txResult.receipt.gasUsed.toString(10)
}
}
@@ -40,7 +39,7 @@ export class TxListener {
constructor (opt, executionContext) {
this.event = new EventManager()
// has a default for now for backwards compatability
- this.executionContext = executionContext || new ExecutionContext()
+ this.executionContext = executionContext
this._api = opt.api
this._resolvedTransactions = {}
this._resolvedContracts = {}
@@ -55,7 +54,7 @@ export class TxListener {
}
})
- opt.event.udapp.register('callExecuted', (error, from, to, data, lookupOnly, txResult) => {
+ opt.event.udapp.register('callExecuted', async (error, from, to, data, lookupOnly, txResult) => {
if (error) return
// we go for that case if
// in VM mode
@@ -63,17 +62,25 @@ export class TxListener {
if (!this._isListening) return // we don't listen
if (this._loopId && this.executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network
+ let returnValue
+ let execResult
+ if (this.executionContext.isVM()) {
+ execResult = await this.executionContext.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
+ returnValue = execResult.returnValue
+ } else {
+ returnValue = toBuffer(txResult.result)
+ }
const call = {
from: from,
to: to,
input: data,
hash: txResult.transactionHash ? txResult.transactionHash : 'call' + (from || '') + to + data,
isCall: true,
- returnValue: this.executionContext.isVM() ? txResult.result.execResult.returnValue : toBuffer(txResult.result),
+ returnValue,
envMode: this.executionContext.getProvider()
}
- addExecutionCosts(txResult, call)
+ addExecutionCosts(txResult, call, execResult)
this._resolveTx(call, call, (error, resolvedData) => {
if (!error) {
this.event.trigger('newCall', [call])
@@ -89,12 +96,17 @@ export class TxListener {
// in web3 mode && listen remix txs only
if (!this._isListening) return // we don't listen
if (this._loopId && this.executionContext.getProvider() !== 'vm') return // we seems to already listen on a "web3" network
- this.executionContext.web3().eth.getTransaction(txResult.transactionHash, (error, tx) => {
+ this.executionContext.web3().eth.getTransaction(txResult.transactionHash, async (error, tx) => {
if (error) return console.log(error)
- addExecutionCosts(txResult, tx)
+ let execResult
+ if (this.executionContext.isVM()) {
+ execResult = await this.executionContext.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
+ }
+
+ addExecutionCosts(txResult, tx, execResult)
tx.envMode = this.executionContext.getProvider()
- tx.status = txResult.result.status // 0x0 or 0x1
+ tx.status = txResult.receipt.status // 0x0 or 0x1
this._resolve([tx], () => {
})
})
diff --git a/libs/remix-lib/src/execution/txRunner.ts b/libs/remix-lib/src/execution/txRunner.ts
index 498f6ece11..e58ac62d5e 100644
--- a/libs/remix-lib/src/execution/txRunner.ts
+++ b/libs/remix-lib/src/execution/txRunner.ts
@@ -1,91 +1,26 @@
'use strict'
-import { Transaction } from '@ethereumjs/tx'
-import { Block } from '@ethereumjs/block'
-import { BN, bufferToHex, Address } from 'ethereumjs-util'
-import { ExecutionContext } from './execution-context'
import { EventManager } from '../eventManager'
export class TxRunner {
event
- executionContext
- _api
- blockNumber
runAsync
pendingTxs
- vmaccounts
queusTxs
- blocks
- commonContext
-
- constructor (vmaccounts, api, executionContext) {
+ opt
+ internalRunner
+ constructor (internalRunner, opt) {
+ this.opt = opt || {}
+ this.internalRunner = internalRunner
this.event = new EventManager()
- // has a default for now for backwards compatability
- this.executionContext = executionContext || new ExecutionContext()
- this.commonContext = this.executionContext.vmObject().common
- this._api = api
- this.blockNumber = 0
- this.runAsync = true
- if (this.executionContext.isVM()) {
- // this.blockNumber = 1150000 // The VM is running in Homestead mode, which started at this block.
- this.blockNumber = 0 // The VM is running in Homestead mode, which started at this block.
- this.runAsync = false // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time.
- }
+
+ this.runAsync = this.opt.runAsync || true // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time.
+
this.pendingTxs = {}
- this.vmaccounts = vmaccounts
this.queusTxs = []
- this.blocks = []
}
rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) {
- let timestamp = Date.now()
- if (args.timestamp) {
- timestamp = args.timestamp
- }
- run(this, args, timestamp, confirmationCb, gasEstimationForceSend, promptCb, cb)
- }
-
- _executeTx (tx, gasPrice, api, promptCb, callback) {
- if (gasPrice) tx.gasPrice = this.executionContext.web3().utils.toHex(gasPrice)
- if (api.personalMode()) {
- promptCb(
- (value) => {
- this._sendTransaction(this.executionContext.web3().personal.sendTransaction, tx, value, callback)
- },
- () => {
- return callback('Canceled by user.')
- }
- )
- } else {
- this._sendTransaction(this.executionContext.web3().eth.sendTransaction, tx, null, callback)
- }
- }
-
- _sendTransaction (sendTx, tx, pass, callback) {
- const cb = (err, resp) => {
- if (err) {
- return callback(err, resp)
- }
- this.event.trigger('transactionBroadcasted', [resp])
- var listenOnResponse = () => {
- // eslint-disable-next-line no-async-promise-executor
- return new Promise(async (resolve, reject) => {
- const result = await tryTillReceiptAvailable(resp, this.executionContext)
- tx = await tryTillTxAvailable(resp, this.executionContext)
- resolve({
- result,
- tx,
- transactionHash: result ? result['transactionHash'] : null
- })
- })
- }
- listenOnResponse().then((txData) => { callback(null, txData) }).catch((error) => { callback(error) })
- }
- const args = pass !== null ? [tx, pass, cb] : [tx, cb]
- try {
- sendTx.apply({}, args)
- } catch (e) {
- return callback(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
- }
+ run(this, args, args.timestamp || Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb)
}
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
@@ -93,185 +28,10 @@ export class TxRunner {
if (data.slice(0, 2) !== '0x') {
data = '0x' + data
}
-
- if (!this.executionContext.isVM()) {
- return this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback)
- }
- try {
- this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback)
- } catch (e) {
- callback(e, null)
- }
- }
-
- runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
- const self = this
- const account = self.vmaccounts[from]
- if (!account) {
- return callback('Invalid account selected')
- }
-
- if (Number.isInteger(gasLimit)) {
- gasLimit = '0x' + gasLimit.toString(16)
- }
-
- this.executionContext.vm().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
- 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 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)
-
- const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
- const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
-
- var block = Block.fromBlockData({
- header: {
- timestamp: timestamp || (new Date().getTime() / 1000 | 0),
- number: self.blockNumber,
- coinbase: coinbases[self.blockNumber % coinbases.length],
- difficulty: difficulties[self.blockNumber % difficulties.length],
- gasLimit: new BN(gasLimit.replace('0x', ''), 16).imuln(2)
- },
- transactions: [tx]
- }, { common: this.commonContext })
-
- if (!useCall) {
- ++self.blockNumber
- this.runBlockInVm(tx, block, callback)
- } else {
- this.executionContext.vm().stateManager.checkpoint().then(() => {
- this.runBlockInVm(tx, block, (err, result) => {
- this.executionContext.vm().stateManager.revert().then(() => {
- callback(err, result)
- })
- })
- })
- }
- }).catch((e) => {
- callback(e)
- })
- }
-
- runBlockInVm (tx, block, callback) {
- this.executionContext.vm().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => {
- const result = results.results[0]
- if (result) {
- const status = result.execResult.exceptionError ? 0 : 1
- result.status = `0x${status}`
- }
- this.executionContext.addBlock(block)
- this.executionContext.trackTx('0x' + tx.hash().toString('hex'), block)
- callback(null, {
- result: result,
- transactionHash: bufferToHex(Buffer.from(tx.hash()))
- })
- }).catch((err) => {
- callback(err)
- })
- }
-
- runInNode (from, to, data, value, gasLimit, useCall, confirmCb, gasEstimationForceSend, promptCb, callback) {
- const tx = { from: from, to: to, data: data, value: value }
-
- if (useCall) {
- tx['gas'] = gasLimit
- return this.executionContext.web3().eth.call(tx, function (error, result) {
- callback(error, {
- result: result,
- transactionHash: result ? result.transactionHash : null
- })
- })
- }
- this.executionContext.web3().eth.estimateGas(tx, (err, gasEstimation) => {
- if (err && err.message.indexOf('Invalid JSON RPC response') !== -1) {
- // // @todo(#378) this should be removed when https://github.com/WalletConnect/walletconnect-monorepo/issues/334 is fixed
- err = 'Gas estimation failed because of an unknown internal error. This may indicated that the transaction will fail.'
- }
- gasEstimationForceSend(err, () => {
- // callback is called whenever no error
- tx['gas'] = !gasEstimation ? gasLimit : gasEstimation
-
- if (this._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) {
- return this._executeTx(tx, null, this._api, promptCb, callback)
- }
-
- this._api.detectNetwork((err, network) => {
- if (err) {
- console.log(err)
- return
- }
-
- confirmCb(network, tx, tx['gas'], (gasPrice) => {
- return this._executeTx(tx, gasPrice, this._api, promptCb, callback)
- }, (error) => {
- callback(error)
- })
- })
- }, () => {
- const blockGasLimit = this.executionContext.currentblockGasLimit()
- // NOTE: estimateGas very likely will return a large limit if execution of the code failed
- // we want to be able to run the code in order to debug and find the cause for the failure
- if (err) return callback(err)
-
- let warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). '
- warnEstimation += ' ' + err
-
- if (gasEstimation > gasLimit) {
- return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
- }
- if (gasEstimation > blockGasLimit) {
- return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation)
- }
- })
- })
+ this.internalRunner.execute(args, confirmationCb, gasEstimationForceSend, promptCb, callback)
}
}
-async function tryTillReceiptAvailable (txhash, executionContext) {
- return new Promise((resolve, reject) => {
- executionContext.web3().eth.getTransactionReceipt(txhash, async (err, receipt) => {
- if (err || !receipt) {
- // Try again with a bit of delay if error or if result still null
- await pause()
- return resolve(await tryTillReceiptAvailable(txhash, executionContext))
- }
- return resolve(receipt)
- })
- })
-}
-
-async function tryTillTxAvailable (txhash, executionContext) {
- return new Promise((resolve, reject) => {
- executionContext.web3().eth.getTransaction(txhash, async (err, tx) => {
- if (err || !tx) {
- // Try again with a bit of delay if error or if result still null
- await pause()
- return resolve(await tryTillTxAvailable(txhash, executionContext))
- }
- return resolve(tx)
- })
- })
-}
-
-async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) }
-
function run (self, tx, stamp, confirmationCb, gasEstimationForceSend = null, promptCb = null, callback = null) {
if (!self.runAsync && Object.keys(self.pendingTxs).length) {
return self.queusTxs.push({ tx, stamp, callback })
diff --git a/libs/remix-lib/src/execution/txRunnerVM.ts b/libs/remix-lib/src/execution/txRunnerVM.ts
new file mode 100644
index 0000000000..ba021a7d0e
--- /dev/null
+++ b/libs/remix-lib/src/execution/txRunnerVM.ts
@@ -0,0 +1,121 @@
+'use strict'
+import { Transaction } from '@ethereumjs/tx'
+import { Block } from '@ethereumjs/block'
+import { BN, bufferToHex, Address } from 'ethereumjs-util'
+import { EventManager } from '../eventManager'
+import { LogsManager } from './logsManager'
+
+export class TxRunnerVM {
+ event
+ blockNumber
+ runAsync
+ pendingTxs
+ vmaccounts
+ queusTxs
+ blocks
+ txs
+ logsManager
+ commonContext
+ getVMObject: () => any
+
+ constructor (vmaccounts, api, getVMObject) {
+ this.event = new EventManager()
+ this.logsManager = new LogsManager()
+ // has a default for now for backwards compatability
+ this.getVMObject = getVMObject
+ this.commonContext = this.getVMObject().common
+ this.blockNumber = 0
+ this.runAsync = true
+ this.blockNumber = 0 // The VM is running in Homestead mode, which started at this block.
+ this.runAsync = false // We have to run like this cause the VM Event Manager does not support running multiple txs at the same time.
+ this.pendingTxs = {}
+ this.vmaccounts = vmaccounts
+ this.queusTxs = []
+ this.blocks = []
+ }
+
+ execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
+ let data = args.data
+ if (data.slice(0, 2) !== '0x') {
+ data = '0x' + data
+ }
+
+ try {
+ this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback)
+ } catch (e) {
+ callback(e, null)
+ }
+ }
+
+ runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
+ const self = this
+ const account = self.vmaccounts[from]
+ if (!account) {
+ return callback('Invalid account selected')
+ }
+ if (Number.isInteger(gasLimit)) {
+ gasLimit = '0x' + gasLimit.toString(16)
+ }
+
+ 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)
+
+ const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
+ const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
+
+ var block = Block.fromBlockData({
+ header: {
+ timestamp: timestamp || (new Date().getTime() / 1000 | 0),
+ number: self.blockNumber,
+ coinbase: coinbases[self.blockNumber % coinbases.length],
+ difficulty: difficulties[self.blockNumber % difficulties.length],
+ gasLimit: new BN(gasLimit.replace('0x', ''), 16).imuln(2)
+ },
+ transactions: [tx]
+ }, { common: this.commonContext })
+
+ if (!useCall) {
+ ++self.blockNumber
+ this.runBlockInVm(tx, block, callback)
+ } else {
+ this.getVMObject().stateManager.checkpoint().then(() => {
+ this.runBlockInVm(tx, block, (err, result) => {
+ this.getVMObject().stateManager.revert().then(() => {
+ callback(err, result)
+ })
+ })
+ })
+ }
+ }).catch((e) => {
+ callback(e)
+ })
+ }
+
+ runBlockInVm (tx, block, callback) {
+ this.getVMObject().vm.runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then((results) => {
+ const result = results.results[0]
+ if (result) {
+ const status = result.execResult.exceptionError ? 0 : 1
+ result.status = `0x${status}`
+ }
+ callback(null, {
+ result: result,
+ transactionHash: bufferToHex(Buffer.from(tx.hash())),
+ block,
+ tx
+ })
+ }).catch(function (err) {
+ callback(err)
+ })
+ }
+}
diff --git a/libs/remix-lib/src/execution/txRunnerWeb3.ts b/libs/remix-lib/src/execution/txRunnerWeb3.ts
new file mode 100644
index 0000000000..650593dfbc
--- /dev/null
+++ b/libs/remix-lib/src/execution/txRunnerWeb3.ts
@@ -0,0 +1,147 @@
+'use strict'
+import { EventManager } from '../eventManager'
+import Web3 from 'web3'
+
+export class TxRunnerWeb3 {
+ event
+ _api
+ getWeb3: () => Web3
+ currentblockGasLimit: () => number
+
+ constructor (api, getWeb3, currentblockGasLimit) {
+ this.event = new EventManager()
+ this.getWeb3 = getWeb3
+ this.currentblockGasLimit = currentblockGasLimit
+ this._api = api
+ }
+
+ _executeTx (tx, gasPrice, api, promptCb, callback) {
+ if (gasPrice) tx.gasPrice = this.getWeb3().utils.toHex(gasPrice)
+ if (api.personalMode()) {
+ promptCb(
+ (value) => {
+ this._sendTransaction((this.getWeb3() as any).personal.sendTransaction, tx, value, callback)
+ },
+ () => {
+ return callback('Canceled by user.')
+ }
+ )
+ } else {
+ this._sendTransaction(this.getWeb3().eth.sendTransaction, tx, null, callback)
+ }
+ }
+
+ _sendTransaction (sendTx, tx, pass, callback) {
+ const cb = (err, resp) => {
+ if (err) {
+ return callback(err, resp)
+ }
+ this.event.trigger('transactionBroadcasted', [resp])
+ var listenOnResponse = () => {
+ // eslint-disable-next-line no-async-promise-executor
+ return new Promise(async (resolve, reject) => {
+ const receipt = await tryTillReceiptAvailable(resp, this.getWeb3())
+ tx = await tryTillTxAvailable(resp, this.getWeb3())
+ resolve({
+ receipt,
+ tx,
+ transactionHash: receipt ? receipt['transactionHash'] : null
+ })
+ })
+ }
+ listenOnResponse().then((txData) => { callback(null, txData) }).catch((error) => { callback(error) })
+ }
+ const args = pass !== null ? [tx, pass, cb] : [tx, cb]
+ try {
+ sendTx.apply({}, args)
+ } catch (e) {
+ return callback(`Send transaction failed: ${e.message} . if you use an injected provider, please check it is properly unlocked. `)
+ }
+ }
+
+ execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
+ let data = args.data
+ if (data.slice(0, 2) !== '0x') {
+ data = '0x' + data
+ }
+
+ return this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, confirmationCb, gasEstimationForceSend, promptCb, callback)
+ }
+
+ runInNode (from, to, data, value, gasLimit, useCall, timestamp, confirmCb, gasEstimationForceSend, promptCb, callback) {
+ 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
+ return this.getWeb3().eth.call(tx, function (error, result: any) {
+ if (error) return callback(error)
+ callback(null, {
+ result: result
+ })
+ })
+ }
+ this.getWeb3().eth.estimateGas(tx, (err, gasEstimation) => {
+ if (err && err.message.indexOf('Invalid JSON RPC response') !== -1) {
+ // // @todo(#378) this should be removed when https://github.com/WalletConnect/walletconnect-monorepo/issues/334 is fixed
+ callback(new Error('Gas estimation failed because of an unknown internal error. This may indicated that the transaction will fail.'))
+ }
+ gasEstimationForceSend(err, () => {
+ // callback is called whenever no error
+ tx['gas'] = !gasEstimation ? gasLimit : gasEstimation
+
+ if (this._api.config.getUnpersistedProperty('doNotShowTransactionConfirmationAgain')) {
+ return this._executeTx(tx, null, this._api, promptCb, callback)
+ }
+
+ this._api.detectNetwork((err, network) => {
+ if (err) {
+ console.log(err)
+ return
+ }
+
+ confirmCb(network, tx, tx['gas'], (gasPrice) => {
+ return this._executeTx(tx, gasPrice, this._api, promptCb, callback)
+ }, (error) => {
+ callback(error)
+ })
+ })
+ }, () => {
+ const blockGasLimit = this.currentblockGasLimit()
+ // NOTE: estimateGas very likely will return a large limit if execution of the code failed
+ // we want to be able to run the code in order to debug and find the cause for the failure
+ if (err) return callback(err)
+
+ let warnEstimation = ' An important gas estimation might also be the sign of a problem in the contract code. Please check loops and be sure you did not sent value to a non payable function (that\'s also the reason of strong gas estimation). '
+ warnEstimation += ' ' + err
+
+ if (gasEstimation > gasLimit) {
+ return callback('Gas required exceeds limit: ' + gasLimit + '. ' + warnEstimation)
+ }
+ if (gasEstimation > blockGasLimit) {
+ return callback('Gas required exceeds block gas limit: ' + gasLimit + '. ' + warnEstimation)
+ }
+ })
+ })
+ }
+}
+
+async function tryTillReceiptAvailable (txhash, web3) {
+ try {
+ const receipt = await web3.eth.getTransactionReceipt(txhash)
+ if (receipt) return receipt
+ } catch (e) {}
+ await pause()
+ return await tryTillReceiptAvailable(txhash, web3)
+}
+
+async function tryTillTxAvailable (txhash, web3) {
+ try {
+ const tx = await web3.eth.getTransaction(txhash)
+ if (tx) return tx
+ } catch (e) {}
+ return await tryTillTxAvailable(txhash, web3)
+}
+
+async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) }
diff --git a/libs/remix-lib/src/helpers/txResultHelper.ts b/libs/remix-lib/src/helpers/txResultHelper.ts
index 7aa7967f0d..3264e5b771 100644
--- a/libs/remix-lib/src/helpers/txResultHelper.ts
+++ b/libs/remix-lib/src/helpers/txResultHelper.ts
@@ -20,9 +20,9 @@ function convertToPrefixedHex (input) {
Also, VM results use BN and Buffers, Node results use hex strings/ints,
So we need to normalize the values to prefixed hex strings
*/
-export function resultToRemixTx (txResult) {
- const { result, transactionHash } = txResult
- const { status, execResult, gasUsed, createdAddress, contractAddress } = result
+export function resultToRemixTx (txResult, execResult) {
+ const { receipt, transactionHash, result } = txResult
+ const { status, gasUsed, contractAddress } = receipt
let returnValue, errorMessage
if (isHexString(result)) {
@@ -38,6 +38,6 @@ export function resultToRemixTx (txResult) {
gasUsed: convertToPrefixedHex(gasUsed),
error: errorMessage,
return: convertToPrefixedHex(returnValue),
- createdAddress: convertToPrefixedHex(createdAddress || contractAddress)
+ createdAddress: convertToPrefixedHex(contractAddress)
}
}
diff --git a/libs/remix-lib/src/index.ts b/libs/remix-lib/src/index.ts
index 5b1108ff70..ae3864a8c9 100644
--- a/libs/remix-lib/src/index.ts
+++ b/libs/remix-lib/src/index.ts
@@ -12,9 +12,11 @@ import * as txHelper from './execution/txHelper'
import * as txFormat from './execution/txFormat'
import { TxListener } from './execution/txListener'
import { TxRunner } from './execution/txRunner'
-import { ExecutionContext } from './execution/execution-context'
+import { LogsManager } from './execution/logsManager'
import * as typeConversion from './execution/typeConversion'
-import { UniversalDApp } from './universalDapp'
+import { TxRunnerVM } from './execution/txRunnerVM'
+import { TxRunnerWeb3 } from './execution/txRunnerWeb3'
+import * as txResultHelper from './helpers/txResultHelper'
export = modules()
@@ -23,7 +25,8 @@ function modules () {
EventManager: EventManager,
helpers: {
ui: uiHelper,
- compiler: compilerHelper
+ compiler: compilerHelper,
+ txResultHelper
},
vm: {
Web3Providers: Web3Providers,
@@ -36,12 +39,13 @@ function modules () {
EventsDecoder: EventsDecoder,
txExecution: txExecution,
txHelper: txHelper,
- executionContext: new ExecutionContext(),
txFormat: txFormat,
txListener: TxListener,
- txRunner: TxRunner,
- typeConversion: typeConversion
- },
- UniversalDApp: UniversalDApp
+ TxRunner: TxRunner,
+ TxRunnerWeb3: TxRunnerWeb3,
+ TxRunnerVM: TxRunnerVM,
+ typeConversion: typeConversion,
+ LogsManager
+ }
}
}
diff --git a/libs/remix-lib/src/universalDapp.ts b/libs/remix-lib/src/universalDapp.ts
deleted file mode 100644
index 030a8da811..0000000000
--- a/libs/remix-lib/src/universalDapp.ts
+++ /dev/null
@@ -1,379 +0,0 @@
-import { waterfall } from 'async'
-import { BN, privateToAddress, isValidPrivate, toChecksumAddress, Address } from 'ethereumjs-util'
-import { randomBytes } from 'crypto'
-import { EventEmitter } from 'events'
-import { TxRunner } from './execution/txRunner'
-import { sortAbiFunction, getFallbackInterface, getReceiveInterface, inputParametersDeclarationToString } from './execution/txHelper'
-import { EventManager } from './eventManager'
-import { ExecutionContext } from './execution/execution-context'
-import { resultToRemixTx } from './helpers/txResultHelper'
-
-export class UniversalDApp {
- events
- event
- executionContext
- config
- txRunner
- accounts
- transactionContextAPI
-
- constructor (config, executionContext) {
- this.events = new EventEmitter()
- this.event = new EventManager()
- // has a default for now for backwards compatability
- this.executionContext = executionContext || new ExecutionContext()
- this.config = config
-
- this.txRunner = new TxRunner({}, {
- config: config,
- detectNetwork: (cb) => {
- this.executionContext.detectNetwork(cb)
- },
- personalMode: () => {
- return this.executionContext.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
- }
- }, this.executionContext)
- this.accounts = {}
- this.executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
- }
-
- // TODO : event should be triggered by Udapp instead of TxListener
- /** Listen on New Transaction. (Cannot be done inside constructor because txlistener doesn't exist yet) */
- startListening (txlistener) {
- txlistener.event.register('newTransaction', (tx) => {
- this.events.emit('newTransaction', tx)
- })
- }
-
- resetEnvironment () {
- this.accounts = {}
- if (this.executionContext.isVM()) {
- this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511', '0x56BC75E2D63100000')
- this._addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c', '0x56BC75E2D63100000')
- this._addAccount('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', '0x56BC75E2D63100000')
- this._addAccount('d74aa6d18aa79a05f3473dd030a97d3305737cbc8337d940344345c1f6b72eea', '0x56BC75E2D63100000')
- this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
- }
- // TODO: most params here can be refactored away in txRunner
- this.txRunner = new TxRunner(this.accounts, {
- // TODO: only used to check value of doNotShowTransactionConfirmationAgain property
- config: this.config,
- // TODO: to refactor, TxRunner already has access to executionContext
- detectNetwork: (cb) => {
- this.executionContext.detectNetwork(cb)
- },
- personalMode: () => {
- return this.executionContext.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
- }
- }, this.executionContext)
- this.txRunner.event.register('transactionBroadcasted', (txhash) => {
- this.executionContext.detectNetwork((error, network) => {
- if (error || !network) return
- this.event.trigger('transactionBroadcasted', [txhash, network.name])
- })
- })
- }
-
- resetAPI (transactionContextAPI) {
- this.transactionContextAPI = transactionContextAPI
- }
-
- /**
- * Create a VM Account
- * @param {{privateKey: string, balance: string}} newAccount The new account to create
- */
- createVMAccount (newAccount) {
- const { privateKey, balance } = newAccount
- if (this.executionContext.getProvider() !== 'vm') {
- throw new Error('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
- }
- this._addAccount(privateKey, balance)
- const privKey = Buffer.from(privateKey, 'hex')
- return '0x' + privateToAddress(privKey).toString('hex')
- }
-
- newAccount (password, passwordPromptCb, cb) {
- if (!this.executionContext.isVM()) {
- if (!this.config.get('settings/personal-mode')) {
- return cb('Not running in personal mode')
- }
- return passwordPromptCb((passphrase) => {
- this.executionContext.web3().personal.newAccount(passphrase, cb)
- })
- }
- let privateKey
- do {
- privateKey = randomBytes(32)
- } while (!isValidPrivate(privateKey))
- this._addAccount(privateKey, '0x56BC75E2D63100000')
- cb(null, '0x' + privateToAddress(privateKey).toString('hex'))
- }
-
- /** Add an account to the list of account (only for Javascript VM) */
- _addAccount (privateKey, balance) {
- if (!this.executionContext.isVM()) {
- throw new Error('_addAccount() cannot be called in non-VM mode')
- }
-
- if (!this.accounts) {
- return
- }
- privateKey = Buffer.from(privateKey, 'hex')
- const address = privateToAddress(privateKey)
-
- // FIXME: we don't care about the callback, but we should still make this proper
- const stateManager = this.executionContext.vm().stateManager
- stateManager.getAccount(address).then((account) => {
- account.balance = new BN(balance.replace('0x', '') || 'f00000000000000001', 16)
- stateManager.putAccount(address, account).catch((error) => {
- console.log(error)
- })
- }).catch((error) => {
- console.log(error)
- })
-
- this.accounts[toChecksumAddress('0x' + address.toString('hex'))] = { privateKey, nonce: 0 }
- }
-
- /** Return the list of accounts */
- getAccounts (cb) {
- return new Promise((resolve, reject) => {
- const provider = this.executionContext.getProvider()
- switch (provider) {
- case 'vm':
- if (!this.accounts) {
- if (cb) cb('No accounts?')
- reject(new Error('No accounts?'))
- return
- }
- if (cb) cb(null, Object.keys(this.accounts))
- resolve(Object.keys(this.accounts))
- break
- case 'web3':
- if (this.config.get('settings/personal-mode')) {
- return this.executionContext.web3().personal.getListAccounts((error, accounts) => {
- if (cb) cb(error, accounts)
- if (error) return reject(error)
- resolve(accounts)
- })
- } else {
- this.executionContext.web3().eth.getAccounts((error, accounts) => {
- if (cb) cb(error, accounts)
- if (error) return reject(error)
- resolve(accounts)
- })
- }
- break
- case 'injected': {
- this.executionContext.web3().eth.getAccounts((error, accounts) => {
- if (cb) cb(error, accounts)
- if (error) return reject(error)
- resolve(accounts)
- })
- }
- }
- })
- }
-
- /** Get the balance of an address */
- getBalance (address, cb) {
- if (!this.executionContext.isVM()) {
- return this.executionContext.web3().eth.getBalance(address, (err, res) => {
- if (err) {
- return cb(err)
- }
- cb(null, res.toString(10))
- })
- }
- if (!this.accounts) {
- return cb('No accounts?')
- }
-
- this.executionContext.vm().stateManager.getAccount(Address.fromString(address)).then((res) => {
- cb(null, new BN(res.balance).toString(10))
- }).catch(() => {
- cb('Account not found')
- })
- }
-
- /** Get the balance of an address, and convert wei to ether */
- getBalanceInEther (address, callback) {
- this.getBalance(address, (error, balance) => {
- if (error) {
- return callback(error)
- }
- callback(null, this.executionContext.web3().utils.fromWei(balance, 'ether'))
- })
- }
-
- pendingTransactionsCount () {
- return Object.keys(this.txRunner.pendingTxs).length
- }
-
- /**
- * deploy the given contract
- *
- * @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
- * @param {Function} callback - callback.
- */
- createContract (data, confirmationCb, continueCb, promptCb, callback) {
- this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb, callback)
- }
-
- /**
- * call the current given contract
- *
- * @param {String} to - address of the contract to call.
- * @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
- * @param {Object} funAbi - abi definition of the function to call.
- * @param {Function} callback - callback.
- */
- callFunction (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) {
- const useCall = funAbi.stateMutability === 'view' || funAbi.stateMutability === 'pure'
- this.runTx({ to, data, useCall }, confirmationCb, continueCb, promptCb, callback)
- }
-
- /**
- * call the current given contract
- *
- * @param {String} to - address of the contract to call.
- * @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
- * @param {Function} callback - callback.
- */
- sendRawTransaction (to, data, confirmationCb, continueCb, promptCb, callback) {
- this.runTx({ to, data, useCall: false }, confirmationCb, continueCb, promptCb, callback)
- }
-
- context () {
- return (this.executionContext.isVM() ? 'memory' : 'blockchain')
- }
-
- getABI (contract) {
- return sortAbiFunction(contract.abi)
- }
-
- getFallbackInterface (contractABI) {
- return getFallbackInterface(contractABI)
- }
-
- getReceiveInterface (contractABI) {
- return getReceiveInterface(contractABI)
- }
-
- getInputs (funABI) {
- if (!funABI.inputs) {
- return ''
- }
- return inputParametersDeclarationToString(funABI.inputs)
- }
-
- /**
- * This function send a tx only to javascript VM or testnet, will return an error for the mainnet
- * SHOULD BE TAKEN CAREFULLY!
- *
- * @param {Object} tx - transaction.
- */
- sendTransaction (tx) {
- return new Promise((resolve, reject) => {
- this.executionContext.detectNetwork((error, network) => {
- if (error) return reject(error)
- if (network.name === 'Main' && network.id === '1') {
- return reject(new Error('It is not allowed to make this action against mainnet'))
- }
- this.silentRunTx(tx, (error, result) => {
- if (error) return reject(error)
- try {
- resolve(resultToRemixTx(result))
- } catch (e) {
- reject(e)
- }
- })
- })
- })
- }
-
- /**
- * This function send a tx without alerting the user (if mainnet or if gas estimation too high).
- * SHOULD BE TAKEN CAREFULLY!
- *
- * @param {Object} tx - transaction.
- * @param {Function} callback - callback.
- */
- silentRunTx (tx, cb) {
- this.txRunner.rawRun(
- tx,
- (network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
- (error, continueTxExecution, cancelCb) => { if (error) { cb(error) } else { continueTxExecution() } },
- (okCb, cancelCb) => { okCb() },
- cb
- )
- }
-
- runTx (args, confirmationCb, continueCb, promptCb, cb) {
- const self = this
- waterfall([
- function getGasLimit (next) {
- if (self.transactionContextAPI.getGasLimit) {
- return self.transactionContextAPI.getGasLimit(next)
- }
- next(null, 3000000)
- },
- function queryValue (gasLimit, next) {
- if (args.value) {
- return next(null, args.value, gasLimit)
- }
- if (args.useCall || !self.transactionContextAPI.getValue) {
- return next(null, 0, gasLimit)
- }
- self.transactionContextAPI.getValue(function (err, value) {
- next(err, value, gasLimit)
- })
- },
- function getAccount (value, gasLimit, next) {
- if (args.from) {
- return next(null, args.from, value, gasLimit)
- }
- if (self.transactionContextAPI.getAddress) {
- return self.transactionContextAPI.getAddress(function (err, address) {
- next(err, address, value, gasLimit)
- })
- }
- self.getAccounts(function (err, accounts) {
- const address = accounts[0]
-
- if (err) return next(err)
- if (!address) return next('No accounts available')
- if (self.executionContext.isVM() && !self.accounts[address]) {
- return next('Invalid account selected')
- }
- next(null, address, value, gasLimit)
- })
- },
- function runTransaction (fromAddress, value, gasLimit, next) {
- const tx = { to: args.to, data: args.data.dataHex, useCall: args.useCall, from: fromAddress, value: value, gasLimit: gasLimit, timestamp: args.data.timestamp }
- const payLoad = { funAbi: args.data.funAbi, funArgs: args.data.funArgs, contractBytecode: args.data.contractBytecode, contractName: args.data.contractName, contractABI: args.data.contractABI, linkReferences: args.data.linkReferences }
- let timestamp = Date.now()
- if (tx.timestamp) {
- timestamp = tx.timestamp
- }
-
- self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
- self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
- function (error, result) {
- const eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
- self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])
-
- if (error && (typeof (error) !== 'string')) {
- if (error.message) error = error.message
- else {
- // eslint-disable-next-line no-empty
- try { error = 'error: ' + JSON.stringify(error) } catch (e) {}
- }
- }
- next(error, result)
- }
- )
- }
- ], cb)
- }
-}
diff --git a/libs/remix-lib/src/web3Provider/web3VmProvider.ts b/libs/remix-lib/src/web3Provider/web3VmProvider.ts
index 367370f928..59b0e7e909 100644
--- a/libs/remix-lib/src/web3Provider/web3VmProvider.ts
+++ b/libs/remix-lib/src/web3Provider/web3VmProvider.ts
@@ -31,6 +31,9 @@ export class Web3VmProvider {
toBigNumber
isAddress
utils
+ txsMapBlock
+ blocks
+ latestBlockNumber
constructor () {
this.web3 = new Web3()
@@ -69,6 +72,9 @@ export class Web3VmProvider {
this.toBigNumber = (...args) => this.web3.utils.toBN(...args)
this.isAddress = (...args) => this.web3.utils.isAddress(...args)
this.utils = Web3.utils || []
+ this.txsMapBlock = {}
+ this.blocks = {}
+ this.latestBlockNumber = 0
}
setVM (vm) {
diff --git a/libs/remix-lib/test/txFormat.ts b/libs/remix-lib/test/txFormat.ts
index d33760f9f9..8bff069fc2 100644
--- a/libs/remix-lib/test/txFormat.ts
+++ b/libs/remix-lib/test/txFormat.ts
@@ -5,8 +5,6 @@ import * as txHelper from '../src/execution/txHelper'
import { hexToIntArray } from '../src/util'
let compiler = require('solc')
import { compilerInput } from '../src/helpers/compilerHelper'
-import { ExecutionContext } from '../src/execution/execution-context'
-const executionContext = new ExecutionContext()
const solidityVersion = 'v0.6.0+commit.26b70077'
/* tape *********************************************************** */
@@ -151,7 +149,6 @@ function testInvalidTupleInput (st, params) {
/* tape *********************************************************** */
tape('ContractParameters - (TxFormat.buildData) - link Libraries', function (t) {
- executionContext.setContext('vm', null, null, null)
const compileData = compiler.compile(compilerInput(deploySimpleLib))
const fakeDeployedContracts = {
@@ -161,8 +158,8 @@ tape('ContractParameters - (TxFormat.buildData) - link Libraries', function (t)
}
const callbackDeployLibraries = (param, callback) => {
callback(null, {
- result: {
- createdAddress: fakeDeployedContracts[param.data.contractName]
+ receipt: {
+ contractAddress: fakeDeployedContracts[param.data.contractName]
}
})
} // fake
diff --git a/libs/remix-lib/test/txResultHelper.ts b/libs/remix-lib/test/txResultHelper.ts
index 8fa2e5cdf2..67a136dc84 100644
--- a/libs/remix-lib/test/txResultHelper.ts
+++ b/libs/remix-lib/test/txResultHelper.ts
@@ -18,12 +18,13 @@ const GAS_USED_INT = 75427
const GAS_USED_HEX = '0x126a3'
const NODE_CALL_RESULT = {
+ receipt: {},
result: RETURN_VALUE_HEX,
transactionHash: undefined
}
const NODE_TX_RESULT = {
- result: {
+ receipt: {
blockHash: '0x380485a4e6372a42e36489783c7f7cb66257612133cd245859c206fd476e9c44',
blockNumber: 5994,
contractAddress: CONTRACT_ADDRESS_HEX,
@@ -39,26 +40,31 @@ const NODE_TX_RESULT = {
}
const VM_RESULT = {
- result: {
+ receipt: {
amountSpent: new BN(1),
- createdAddress: CONTRACT_ADDRESS_BUFFER,
+ contractAddress: CONTRACT_ADDRESS_BUFFER,
gasRefund: new BN(0),
gasUsed: new BN(GAS_USED_INT),
status: STATUS_OK,
- execResult: {
- exceptionError: null,
- gasRefund: new BN(0),
- gasUsed: new BN(GAS_USED_INT),
- returnValue: RETURN_VALUE_BUFFER
- }
},
transactionHash: TRANSACTION_HASH
}
+const EXEC_RESULT = {
+ exceptionError: null,
+ gasRefund: new BN(0),
+ gasUsed: new BN(GAS_USED_INT),
+ returnValue: RETURN_VALUE_BUFFER
+}
+
+const EXEC_RESULT_ERROR = {
+ exceptionError: 'this is an error'
+}
+
tape('converts node transaction result to RemixTx', function (t) {
// contract creation
let txResult = { ...NODE_TX_RESULT }
- let remixTx = resultToRemixTx(txResult)
+ let remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.transactionHash, TRANSACTION_HASH)
t.equal(remixTx.createdAddress, CONTRACT_ADDRESS_HEX)
@@ -68,8 +74,8 @@ tape('converts node transaction result to RemixTx', function (t) {
t.equal(remixTx.error, undefined)
// contract method tx
- txResult.result.contractAddress = null
- remixTx = resultToRemixTx(txResult)
+ txResult.receipt.contractAddress = null
+ remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.createdAddress, null)
t.end()
@@ -77,7 +83,7 @@ tape('converts node transaction result to RemixTx', function (t) {
tape('converts node call result to RemixTx', function (t) {
let txResult = { ...NODE_CALL_RESULT }
- let remixTx = resultToRemixTx(txResult)
+ let remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.transactionHash, undefined)
t.equal(remixTx.createdAddress, undefined)
@@ -91,7 +97,7 @@ tape('converts node call result to RemixTx', function (t) {
tape('converts VM result to RemixTx', function (t) {
let txResult = { ...VM_RESULT }
- let remixTx = resultToRemixTx(txResult)
+ let remixTx = resultToRemixTx(txResult, EXEC_RESULT)
t.equal(remixTx.transactionHash,
TRANSACTION_HASH)
@@ -101,8 +107,7 @@ tape('converts VM result to RemixTx', function (t) {
t.equal(remixTx.return, RETURN_VALUE_HEX)
t.equal(remixTx.error, null)
- txResult.result.execResult.exceptionError = 'this is an error'
- remixTx = resultToRemixTx(txResult)
+ remixTx = resultToRemixTx(VM_RESULT, EXEC_RESULT_ERROR)
t.equal(remixTx.error, 'this is an error')
t.end()
diff --git a/libs/remix-simulator/package.json b/libs/remix-simulator/package.json
index eb09b20106..b39deedf5a 100644
--- a/libs/remix-simulator/package.json
+++ b/libs/remix-simulator/package.json
@@ -14,7 +14,7 @@
],
"main": "src/index.js",
"dependencies": {
- "@remix-project/remix-lib": "^0.4.34",
+ "@remix-project/remix-lib": "../remix-lib",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
diff --git a/libs/remix-simulator/src/genesis.ts b/libs/remix-simulator/src/genesis.ts
index f3d7d0509e..839a0c4cb6 100644
--- a/libs/remix-simulator/src/genesis.ts
+++ b/libs/remix-simulator/src/genesis.ts
@@ -1,7 +1,7 @@
import { Block } from '@ethereumjs/block'
import { BN } from 'ethereumjs-util'
-export function generateBlock (executionContext) {
+export function generateBlock (vmContext) {
return new Promise((resolve, reject) => {
const block: Block = Block.fromBlockData({
header: {
@@ -11,10 +11,10 @@ export function generateBlock (executionContext) {
difficulty: new BN('69762765929000', 10),
gasLimit: new BN('8000000').imuln(1)
}
- }, { common: executionContext.vmObject().common })
+ }, { common: vmContext.vmObject().common })
- executionContext.vm().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then(() => {
- executionContext.addBlock(block)
+ vmContext.vm().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then(() => {
+ vmContext.addBlock(block)
resolve({})
}).catch((e) => reject(e))
})
diff --git a/libs/remix-simulator/src/index.ts b/libs/remix-simulator/src/index.ts
index fd73199808..19ca3ab802 100644
--- a/libs/remix-simulator/src/index.ts
+++ b/libs/remix-simulator/src/index.ts
@@ -1 +1 @@
-export { Provider } from './provider'
+export { Provider, extend } from './provider'
diff --git a/libs/remix-simulator/src/methods/accounts.ts b/libs/remix-simulator/src/methods/accounts.ts
index 6fd359594b..8d18498afc 100644
--- a/libs/remix-simulator/src/methods/accounts.ts
+++ b/libs/remix-simulator/src/methods/accounts.ts
@@ -6,22 +6,20 @@ export class Accounts {
web3
accounts: Record
accountsKeys: Record
- executionContext
+ vmContext
- constructor (executionContext) {
+ constructor (vmContext) {
this.web3 = new Web3()
- this.executionContext = executionContext
+ this.vmContext = vmContext
// TODO: make it random and/or use remix-libs
this.accounts = {}
this.accountsKeys = {}
- this.executionContext.init({ get: () => { return true } })
}
async resetAccounts (): Promise {
- // TODO: setting this to {} breaks the app currently, unclear why still
- // this.accounts = {}
- // this.accountsKeys = {}
+ this.accounts = {}
+ this.accountsKeys = {}
await this._addAccount('503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb', '0x56BC75E2D63100000')
await this._addAccount('7e5bfb82febc4c2c8529167104271ceec190eafdca277314912eaabdb67c6e5f', '0x56BC75E2D63100000')
await this._addAccount('cc6d63f85de8fef05446ebdd3c537c72152d0fc437fd7aa62b3019b79bd1fdd4', '0x56BC75E2D63100000')
@@ -47,7 +45,7 @@ export class Accounts {
this.accounts[addressStr] = { privateKey, nonce: 0 }
this.accountsKeys[addressStr] = '0x' + privateKey.toString('hex')
- const stateManager = this.executionContext.vm().stateManager
+ const stateManager = this.vmContext.vm().stateManager
stateManager.getAccount(Address.fromString(addressStr)).then((account) => {
account.balance = new BN(balance.replace('0x', '') || 'f00000000000000001', 16)
stateManager.putAccount(Address.fromString(addressStr), account).catch((error) => {
@@ -85,7 +83,7 @@ export class Accounts {
eth_getBalance (payload, cb) {
const address = payload.params[0]
- this.executionContext.vm().stateManager.getAccount(Address.fromString(address)).then((account) => {
+ this.vmContext.vm().stateManager.getAccount(Address.fromString(address)).then((account) => {
cb(null, new BN(account.balance).toString(10))
}).catch((error) => {
cb(error)
diff --git a/libs/remix-simulator/src/methods/blocks.ts b/libs/remix-simulator/src/methods/blocks.ts
index bfed840284..7ac7bc179f 100644
--- a/libs/remix-simulator/src/methods/blocks.ts
+++ b/libs/remix-simulator/src/methods/blocks.ts
@@ -1,10 +1,10 @@
export class Blocks {
- executionContext
+ vmContext
coinbase: string
blockNumber: number
- constructor (executionContext, _options) {
- this.executionContext = executionContext
+ constructor (vmContext, _options) {
+ this.vmContext = vmContext
const options = _options || {}
this.coinbase = options.coinbase || '0x0000000000000000000000000000000000000000'
this.blockNumber = 0
@@ -28,13 +28,13 @@ export class Blocks {
eth_getBlockByNumber (payload, cb) {
let blockIndex = payload.params[0]
if (blockIndex === 'latest') {
- blockIndex = this.executionContext.latestBlockNumber
+ blockIndex = this.vmContext.latestBlockNumber
}
if (Number.isInteger(blockIndex)) {
blockIndex = '0x' + blockIndex.toString(16)
}
- const block = this.executionContext.blocks[blockIndex]
+ const block = this.vmContext.blocks[blockIndex]
if (!block) {
return cb(new Error('block not found'))
@@ -70,7 +70,7 @@ export class Blocks {
}
eth_getBlockByHash (payload, cb) {
- const block = this.executionContext.blocks[payload.params[0]]
+ const block = this.vmContext.blocks[payload.params[0]]
const b = {
number: this.toHex(block.header.number),
@@ -109,13 +109,13 @@ export class Blocks {
}
eth_getBlockTransactionCountByHash (payload, cb) {
- const block = this.executionContext.blocks[payload.params[0]]
+ const block = this.vmContext.blocks[payload.params[0]]
cb(null, block.transactions.length)
}
eth_getBlockTransactionCountByNumber (payload, cb) {
- const block = this.executionContext.blocks[payload.params[0]]
+ const block = this.vmContext.blocks[payload.params[0]]
cb(null, block.transactions.length)
}
@@ -131,7 +131,7 @@ export class Blocks {
eth_getStorageAt (payload, cb) {
const [address, position, blockNumber] = payload.params
- this.executionContext.web3().debug.storageRangeAt(blockNumber, 'latest', address.toLowerCase(), position, 1, (err, result) => {
+ this.vmContext.web3().debug.storageRangeAt(blockNumber, 'latest', address.toLowerCase(), position, 1, (err, result) => {
if (err || (result.storage && Object.values(result.storage).length === 0)) {
return cb(err, '')
}
diff --git a/libs/remix-simulator/src/methods/debug.ts b/libs/remix-simulator/src/methods/debug.ts
index ae7f3067b8..b9bf886e50 100644
--- a/libs/remix-simulator/src/methods/debug.ts
+++ b/libs/remix-simulator/src/methods/debug.ts
@@ -1,8 +1,8 @@
export class Debug {
- executionContext
+ vmContext
- constructor (executionContext) {
- this.executionContext = executionContext
+ constructor (vmContext) {
+ this.vmContext = vmContext
}
methods () {
@@ -14,15 +14,15 @@ export class Debug {
}
debug_traceTransaction (payload, cb) {
- this.executionContext.web3().debug.traceTransaction(payload.params[0], {}, cb)
+ this.vmContext.web3().debug.traceTransaction(payload.params[0], {}, cb)
}
debug_preimage (payload, cb) {
- this.executionContext.web3().debug.preimage(payload.params[0], cb)
+ this.vmContext.web3().debug.preimage(payload.params[0], cb)
}
debug_storageRangeAt (payload, cb) {
- this.executionContext.web3().debug.storageRangeAt(
+ this.vmContext.web3().debug.storageRangeAt(
payload.params[0],
payload.params[1],
payload.params[2],
diff --git a/libs/remix-simulator/src/methods/filters.ts b/libs/remix-simulator/src/methods/filters.ts
index c27cc49bd6..43404a1c31 100644
--- a/libs/remix-simulator/src/methods/filters.ts
+++ b/libs/remix-simulator/src/methods/filters.ts
@@ -1,8 +1,8 @@
export class Filters {
- executionContext
+ vmContext
- constructor (executionContext) {
- this.executionContext = executionContext
+ constructor (vmContext) {
+ this.vmContext = vmContext
}
methods () {
@@ -14,49 +14,49 @@ export class Filters {
}
eth_getLogs (payload, cb) {
- const results = this.executionContext.logsManager.getLogsFor(payload.params[0])
+ const results = this.vmContext.logsManager.getLogsFor(payload.params[0])
cb(null, results)
}
eth_subscribe (payload, cb) {
- const subscriptionId = this.executionContext.logsManager.subscribe(payload.params)
+ const subscriptionId = this.vmContext.logsManager.subscribe(payload.params)
cb(null, subscriptionId)
}
eth_unsubscribe (payload, cb) {
- this.executionContext.logsManager.unsubscribe(payload.params[0])
+ this.vmContext.logsManager.unsubscribe(payload.params[0])
cb(null, true)
}
eth_newFilter (payload, cb) {
- const filterId = this.executionContext.logsManager.newFilter('filter', payload.params[0])
+ const filterId = this.vmContext.logsManager.newFilter('filter', payload.params[0])
cb(null, filterId)
}
eth_newBlockFilter (payload, cb) {
- const filterId = this.executionContext.logsManager.newFilter('block')
+ const filterId = this.vmContext.logsManager.newFilter('block')
cb(null, filterId)
}
eth_newPendingTransactionFilter (payload, cb) {
- const filterId = this.executionContext.logsManager.newFilter('pendingTransactions')
+ const filterId = this.vmContext.logsManager.newFilter('pendingTransactions')
cb(null, filterId)
}
eth_uninstallfilter (payload, cb) {
- const result = this.executionContext.logsManager.uninstallFilter(payload.params[0])
+ const result = this.vmContext.logsManager.uninstallFilter(payload.params[0])
cb(null, result)
}
eth_getFilterChanges (payload, cb) {
const filterId = payload.params[0]
- const results = this.executionContext.logsManager.getLogsForFilter(filterId)
+ const results = this.vmContext.logsManager.getLogsForFilter(filterId)
cb(null, results)
}
eth_getFilterLogs (payload, cb) {
const filterId = payload.params[0]
- const results = this.executionContext.logsManager.getLogsForFilter(filterId, true)
+ const results = this.vmContext.logsManager.getLogsForFilter(filterId, true)
cb(null, results)
}
}
diff --git a/libs/remix-simulator/src/methods/transactions.ts b/libs/remix-simulator/src/methods/transactions.ts
index f3e8ca3f43..c3a43f5bc4 100644
--- a/libs/remix-simulator/src/methods/transactions.ts
+++ b/libs/remix-simulator/src/methods/transactions.ts
@@ -1,17 +1,48 @@
import Web3 from 'web3'
import { toChecksumAddress, BN, Address } from 'ethereumjs-util'
import { processTx } from './txProcess'
+import { execution } from '@remix-project/remix-lib'
+const TxRunnerVM = execution.TxRunnerVM
+const TxRunner = execution.TxRunner
export class Transactions {
- executionContext
+ vmContext
accounts
+ tags
+ txRunnerVMInstance
+ txRunnerInstance
- constructor (executionContext) {
- this.executionContext = executionContext
+ constructor (vmContext) {
+ this.vmContext = vmContext
+ this.tags = {}
}
init (accounts) {
this.accounts = accounts
+ const api = {
+ logMessage: (msg) => {
+ },
+ logHtmlMessage: (msg) => {
+ },
+ config: {
+ getUnpersistedProperty: (key) => {
+ return true
+ },
+ get: () => {
+ return true
+ }
+ },
+ detectNetwork: (cb) => {
+ cb()
+ },
+ personalMode: () => {
+ return false
+ }
+ }
+
+ this.txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => this.vmContext.vmObject())
+ this.txRunnerInstance = new TxRunner(this.txRunnerVMInstance, { runAsync: false })
+ this.txRunnerInstance.vmaccounts = accounts
}
methods () {
@@ -24,7 +55,9 @@ export class Transactions {
eth_getTransactionCount: this.eth_getTransactionCount.bind(this),
eth_getTransactionByHash: this.eth_getTransactionByHash.bind(this),
eth_getTransactionByBlockHashAndIndex: this.eth_getTransactionByBlockHashAndIndex.bind(this),
- eth_getTransactionByBlockNumberAndIndex: this.eth_getTransactionByBlockNumberAndIndex.bind(this)
+ eth_getTransactionByBlockNumberAndIndex: this.eth_getTransactionByBlockNumberAndIndex.bind(this),
+ eth_getExecutionResultFromSimulator: this.eth_getExecutionResultFromSimulator.bind(this),
+ eth_getHashFromTagBySimulator: this.eth_getHashFromTagBySimulator.bind(this)
}
}
@@ -33,16 +66,30 @@ export class Transactions {
if (payload.params && payload.params.length > 0 && payload.params[0].from) {
payload.params[0].from = toChecksumAddress(payload.params[0].from)
}
- processTx(this.executionContext, this.accounts, payload, false, cb)
+ processTx(this.txRunnerInstance, payload, false, (error, result) => {
+ if (!error && result) {
+ this.vmContext.addBlock(result.block)
+ const hash = '0x' + result.tx.hash().toString('hex')
+ this.vmContext.trackTx(hash, result.block)
+ this.vmContext.trackExecResult(hash, result.result.execResult)
+ return cb(null, result.transactionHash)
+ }
+ cb(error)
+ })
+ }
+
+ eth_getExecutionResultFromSimulator (payload, cb) {
+ const txHash = payload.params[0]
+ cb(null, this.vmContext.exeResults[txHash])
}
eth_getTransactionReceipt (payload, cb) {
- this.executionContext.web3().eth.getTransactionReceipt(payload.params[0], (error, receipt) => {
+ this.vmContext.web3().eth.getTransactionReceipt(payload.params[0], (error, receipt) => {
if (error) {
return cb(error)
}
- const txBlock = this.executionContext.txs[receipt.hash]
+ const txBlock = this.vmContext.txs[receipt.hash]
const r: Record = {
transactionHash: receipt.hash,
@@ -72,7 +119,7 @@ export class Transactions {
eth_getCode (payload, cb) {
const address = payload.params[0]
- this.executionContext.web3().eth.getCode(address, (error, result) => {
+ this.vmContext.web3().eth.getCode(address, (error, result) => {
if (error) {
console.dir('error getting code')
console.dir(error)
@@ -92,13 +139,31 @@ export class Transactions {
payload.params[0].value = undefined
- processTx(this.executionContext, this.accounts, payload, true, cb)
+ const tag = payload.params[0].timestamp // e2e reference
+
+ processTx(this.txRunnerInstance, payload, true, (error, result) => {
+ if (!error && result) {
+ this.vmContext.addBlock(result.block)
+ const hash = '0x' + result.tx.hash().toString('hex')
+ this.vmContext.trackTx(hash, result.block)
+ this.vmContext.trackExecResult(hash, result.result.execResult)
+ this.tags[tag] = result.transactionHash
+ // calls are not supposed to return a transaction hash. we do this for keeping track of it and allowing debugging calls.
+ const returnValue = `0x${result.result.execResult.returnValue.toString('hex') || '0'}`
+ return cb(null, returnValue)
+ }
+ cb(error)
+ })
+ }
+
+ eth_getHashFromTagBySimulator (payload, cb) {
+ return cb(null, this.tags[payload.params[0]])
}
eth_getTransactionCount (payload, cb) {
const address = payload.params[0]
- this.executionContext.vm().stateManager.getAccount(Address.fromString(address)).then((account) => {
+ this.vmContext.vm().stateManager.getAccount(Address.fromString(address)).then((account) => {
const nonce = new BN(account.nonce).toString(10)
cb(null, nonce)
}).catch((error) => {
@@ -109,12 +174,12 @@ export class Transactions {
eth_getTransactionByHash (payload, cb) {
const address = payload.params[0]
- this.executionContext.web3().eth.getTransactionReceipt(address, (error, receipt) => {
+ this.vmContext.web3().eth.getTransactionReceipt(address, (error, receipt) => {
if (error) {
return cb(error)
}
- const txBlock = this.executionContext.txs[receipt.transactionHash]
+ const txBlock = this.vmContext.txs[receipt.transactionHash]
// TODO: params to add later
const r: Record = {
@@ -154,10 +219,10 @@ export class Transactions {
eth_getTransactionByBlockHashAndIndex (payload, cb) {
const txIndex = payload.params[1]
- const txBlock = this.executionContext.blocks[payload.params[0]]
+ const txBlock = this.vmContext.blocks[payload.params[0]]
const txHash = '0x' + txBlock.transactions[Web3.utils.toDecimal(txIndex)].hash().toString('hex')
- this.executionContext.web3().eth.getTransactionReceipt(txHash, (error, receipt) => {
+ this.vmContext.web3().eth.getTransactionReceipt(txHash, (error, receipt) => {
if (error) {
return cb(error)
}
@@ -196,10 +261,10 @@ export class Transactions {
eth_getTransactionByBlockNumberAndIndex (payload, cb) {
const txIndex = payload.params[1]
- const txBlock = this.executionContext.blocks[payload.params[0]]
+ const txBlock = this.vmContext.blocks[payload.params[0]]
const txHash = '0x' + txBlock.transactions[Web3.utils.toDecimal(txIndex)].hash().toString('hex')
- this.executionContext.web3().eth.getTransactionReceipt(txHash, (error, receipt) => {
+ this.vmContext.web3().eth.getTransactionReceipt(txHash, (error, receipt) => {
if (error) {
return cb(error)
}
diff --git a/libs/remix-simulator/src/methods/txProcess.ts b/libs/remix-simulator/src/methods/txProcess.ts
index 8f8acb5b2f..75173af309 100644
--- a/libs/remix-simulator/src/methods/txProcess.ts
+++ b/libs/remix-simulator/src/methods/txProcess.ts
@@ -1,15 +1,12 @@
import { execution } from '@remix-project/remix-lib'
const TxExecution = execution.txExecution
-const TxRunner = execution.txRunner
function runCall (payload, from, to, data, value, gasLimit, txRunner, callbacks, callback) {
const finalCallback = function (err, result) {
if (err) {
return callback(err)
}
- const returnValue = result.result.execResult.returnValue.toString('hex')
- const toReturn = `0x${returnValue || '0'}`
- return callback(null, toReturn)
+ return callback(null, result)
}
TxExecution.callFunction(from, to, data, value, gasLimit, { constant: true }, txRunner, callbacks, finalCallback)
@@ -20,7 +17,7 @@ function runTx (payload, from, to, data, value, gasLimit, txRunner, callbacks, c
if (err) {
return callback(err)
}
- callback(null, result.transactionHash)
+ callback(null, result)
}
TxExecution.callFunction(from, to, data, value, gasLimit, { constant: false }, txRunner, callbacks, finalCallback)
@@ -31,43 +28,13 @@ function createContract (payload, from, data, value, gasLimit, txRunner, callbac
if (err) {
return callback(err)
}
- callback(null, result.transactionHash)
+ callback(null, result)
}
TxExecution.createContract(from, data, value, gasLimit, txRunner, callbacks, finalCallback)
}
-let txRunnerInstance
-
-export function processTx (executionContext, accounts, payload, isCall, callback) {
- const api = {
- logMessage: (msg) => {
- },
- logHtmlMessage: (msg) => {
- },
- config: {
- getUnpersistedProperty: (key) => {
- return true
- },
- get: () => {
- return true
- }
- },
- detectNetwork: (cb) => {
- cb()
- },
- personalMode: () => {
- return false
- }
- }
-
- executionContext.init(api.config)
-
- // let txRunner = new TxRunner(accounts, api)
- if (!txRunnerInstance) {
- txRunnerInstance = new TxRunner(accounts, api, executionContext)
- }
- txRunnerInstance.vmaccounts = accounts
+export function processTx (txRunnerInstance, payload, isCall, callback) {
let { from, to, data, value, gas } = payload.params[0]
gas = gas || 3000000
diff --git a/libs/remix-simulator/src/provider.ts b/libs/remix-simulator/src/provider.ts
index 8fdb5b5aba..276b66ca15 100644
--- a/libs/remix-simulator/src/provider.ts
+++ b/libs/remix-simulator/src/provider.ts
@@ -1,5 +1,4 @@
import { Blocks } from './methods/blocks'
-import { execution } from '@remix-project/remix-lib'
import { info } from './utils/logs'
import merge from 'merge'
@@ -11,11 +10,11 @@ import { methods as netMethods } from './methods/net'
import { Transactions } from './methods/transactions'
import { Debug } from './methods/debug'
import { generateBlock } from './genesis'
-const { executionContext } = execution
+import { VMContext } from './vm-context'
export class Provider {
options: Record
- executionContext
+ vmContext
Accounts
Transactions
methods
@@ -26,23 +25,23 @@ export class Provider {
this.options = options
this.host = host
this.connected = true
- // TODO: init executionContext here
- this.executionContext = executionContext
- this.Accounts = new Accounts(this.executionContext)
- this.Transactions = new Transactions(this.executionContext)
+ this.vmContext = new VMContext()
+
+ this.Accounts = new Accounts(this.vmContext)
+ this.Transactions = new Transactions(this.vmContext)
this.methods = {}
this.methods = merge(this.methods, this.Accounts.methods())
- this.methods = merge(this.methods, (new Blocks(this.executionContext, options)).methods())
+ this.methods = merge(this.methods, (new Blocks(this.vmContext, options)).methods())
this.methods = merge(this.methods, miscMethods())
- this.methods = merge(this.methods, (new Filters(this.executionContext)).methods())
+ this.methods = merge(this.methods, (new Filters(this.vmContext)).methods())
this.methods = merge(this.methods, netMethods())
this.methods = merge(this.methods, this.Transactions.methods())
- this.methods = merge(this.methods, (new Debug(this.executionContext)).methods())
+ this.methods = merge(this.methods, (new Debug(this.vmContext)).methods())
}
async init () {
- await generateBlock(this.executionContext)
+ await generateBlock(this.vmContext)
await this.Accounts.resetAccounts()
this.Transactions.init(this.Accounts.accounts)
}
@@ -87,6 +86,39 @@ export class Provider {
};
on (type, cb) {
- this.executionContext.logsManager.addListener(type, cb)
+ this.vmContext.logsManager.addListener(type, cb)
+ }
+}
+
+export function extend (web3) {
+ if (!web3.extend) {
+ return
+ }
+ // DEBUG
+ const methods = []
+ if (!(web3.eth && web3.eth.getExecutionResultFromSimulator)) {
+ methods.push(new web3.extend.Method({
+ name: 'getExecutionResultFromSimulator',
+ call: 'eth_getExecutionResultFromSimulator',
+ inputFormatter: [null],
+ params: 1
+ }))
+ }
+
+ if (!(web3.eth && web3.eth.getHashFromTagBySimulator)) {
+ methods.push(new web3.extend.Method({
+ name: 'getHashFromTagBySimulator',
+ call: 'eth_getHashFromTagBySimulator',
+ inputFormatter: [null],
+ params: 1
+ }))
+ }
+
+ if (methods.length > 0) {
+ web3.extend({
+ property: 'eth',
+ methods: methods,
+ properties: []
+ })
}
}
diff --git a/libs/remix-simulator/src/vm-context.ts b/libs/remix-simulator/src/vm-context.ts
new file mode 100644
index 0000000000..a72be89d1e
--- /dev/null
+++ b/libs/remix-simulator/src/vm-context.ts
@@ -0,0 +1,169 @@
+/* global ethereum */
+'use strict'
+import Web3 from 'web3'
+import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
+import { vm as remixLibVm, execution } from '@remix-project/remix-lib'
+import VM from '@ethereumjs/vm'
+import Common from '@ethereumjs/common'
+import StateManager from '@ethereumjs/vm/dist/state/stateManager'
+import { StorageDump } from '@ethereumjs/vm/dist/state/interface'
+
+/*
+ extend vm state manager and instanciate VM
+*/
+
+class StateManagerCommonStorageDump extends StateManager {
+ keyHashes: { [key: string]: string }
+ constructor () {
+ super()
+ this.keyHashes = {}
+ }
+
+ putContractStorage (address, key, value) {
+ this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key)
+ return super.putContractStorage(address, key, value)
+ }
+
+ async dumpStorage (address) {
+ let trie
+ try {
+ trie = await this._getStorageTrie(address)
+ } catch (e) {
+ console.log(e)
+ throw e
+ }
+ return new Promise((resolve, reject) => {
+ try {
+ const storage = {}
+ const stream = trie.createReadStream()
+ stream.on('data', (val) => {
+ const value = rlp.decode(val.value)
+ storage['0x' + val.key.toString('hex')] = {
+ key: this.keyHashes[val.key.toString('hex')],
+ value: '0x' + value.toString('hex')
+ }
+ })
+ stream.on('end', function () {
+ resolve(storage)
+ })
+ } catch (e) {
+ reject(e)
+ }
+ })
+ }
+
+ async getStateRoot (force = false) {
+ await this._cache.flush()
+
+ const stateRoot = this._trie.root
+ return stateRoot
+ }
+
+ async setStateRoot (stateRoot) {
+ await this._cache.flush()
+
+ if (stateRoot === this._trie.EMPTY_TRIE_ROOT) {
+ this._trie.root = stateRoot
+ this._cache.clear()
+ this._storageTries = {}
+ return
+ }
+
+ const hasRoot = await this._trie.checkRoot(stateRoot)
+ if (!hasRoot) {
+ throw new Error('State trie does not contain state root')
+ }
+
+ this._trie.root = stateRoot
+ this._cache.clear()
+ this._storageTries = {}
+ }
+}
+
+/*
+ trigger contextChanged, web3EndpointChanged
+*/
+export class VMContext {
+ currentFork: string
+ blockGasLimitDefault: number
+ blockGasLimit: number
+ customNetWorks
+ blocks
+ latestBlockNumber
+ txs
+ vms
+ web3vm
+ logsManager
+ exeResults
+
+ constructor () {
+ 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.blocks = {}
+ this.latestBlockNumber = 0
+ this.txs = {}
+ this.exeResults = {}
+ this.logsManager = new execution.LogsManager()
+ }
+
+ createVm (hardfork) {
+ const stateManager = new StateManagerCommonStorageDump()
+ const common = new Common({ chain: 'mainnet', hardfork })
+ const vm = new VM({
+ common,
+ activatePrecompiles: true,
+ stateManager: stateManager
+ })
+
+ const web3vm = new remixLibVm.Web3VMProvider()
+ web3vm.setVM(vm)
+ return { vm, web3vm, stateManager, common }
+ }
+
+ web3 () {
+ return this.vms[this.currentFork].web3vm
+ }
+
+ blankWeb3 () {
+ return new Web3()
+ }
+
+ vm () {
+ return this.vms[this.currentFork].vm
+ }
+
+ vmObject () {
+ return this.vms[this.currentFork]
+ }
+
+ addBlock (block) {
+ let blockNumber = '0x' + block.header.number.toString('hex')
+ if (blockNumber === '0x') {
+ blockNumber = '0x0'
+ }
+
+ this.blocks['0x' + block.hash().toString('hex')] = block
+ this.blocks[blockNumber] = block
+ this.latestBlockNumber = blockNumber
+
+ this.logsManager.checkBlock(blockNumber, block, this.web3())
+ }
+
+ trackTx (tx, block) {
+ this.txs[tx] = block
+ }
+
+ trackExecResult (tx, execReult) {
+ this.exeResults[tx] = execReult
+ }
+}
diff --git a/libs/remix-solidity/package.json b/libs/remix-solidity/package.json
index ddb834e951..7a54cfe8d7 100644
--- a/libs/remix-solidity/package.json
+++ b/libs/remix-solidity/package.json
@@ -15,7 +15,7 @@
}
],
"dependencies": {
- "@remix-project/remix-lib": "^0.4.34",
+ "@remix-project/remix-lib": "../remix-lib",
"eslint-scope": "^5.0.0",
"@ethereumjs/vm": "^5.3.2",
"@ethereumjs/block": "^3.2.1",
diff --git a/libs/remix-tests/jest.config.js b/libs/remix-tests/jest.config.js
index 7923ddf044..0f08d413b7 100644
--- a/libs/remix-tests/jest.config.js
+++ b/libs/remix-tests/jest.config.js
@@ -6,6 +6,7 @@ module.exports = {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
+ transformIgnorePatterns: ["/node_modules/", "\\.pnp\\.[^\\\/]+$"],
rootDir: "./",
testTimeout: 40000,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'],
@@ -18,6 +19,6 @@ module.exports = {
"!src/types.ts",
"!src/logger.ts"
],
- coverageDirectory: '../../coverage/libs/remix-tests',
+ coverageDirectory: '../../coverage/libs/remix-tests'
};
\ No newline at end of file
diff --git a/libs/remix-tests/package.json b/libs/remix-tests/package.json
index a2480bfa46..9adf364e2d 100644
--- a/libs/remix-tests/package.json
+++ b/libs/remix-tests/package.json
@@ -35,9 +35,9 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-tests#readme",
"dependencies": {
- "@remix-project/remix-lib": "^0.4.34",
- "@remix-project/remix-simulator": "^0.1.10-beta.0",
- "@remix-project/remix-solidity": "^0.3.35",
+ "@remix-project/remix-lib": "../remix-lib",
+ "@remix-project/remix-simulator": "../remix-simulator",
+ "@remix-project/remix-solidity": "../remix-solidity",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
diff --git a/libs/remix-tests/src/deployer.ts b/libs/remix-tests/src/deployer.ts
index c6e00456fa..1e3fcb8d88 100644
--- a/libs/remix-tests/src/deployer.ts
+++ b/libs/remix-tests/src/deployer.ts
@@ -79,7 +79,7 @@ export function deployAll (compileResult: compilationInterface, web3: Web3, with
contracts[contractName] = contractObject
contracts[contractName].filename = filename
- callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
+ callback(null, { receipt: { contractAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM
}).on('error', function (err) {
console.error(err)
callback(err)
diff --git a/libs/remix-tests/tests/testRunner.cli.spec.ts b/libs/remix-tests/tests/testRunner.cli.spec.ts
index 3d906095f3..91174355af 100644
--- a/libs/remix-tests/tests/testRunner.cli.spec.ts
+++ b/libs/remix-tests/tests/testRunner.cli.spec.ts
@@ -3,13 +3,16 @@ import { resolve } from 'path'
describe('testRunner: remix-tests CLI', () => {
// remix-tests binary, after build, is used as executable
+
const executablePath = resolve(__dirname + '/../../../dist/libs/remix-tests/bin/remix-tests')
+
const result = spawnSync('ls', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') })
if(result) {
const dirContent = result.stdout.toString()
// Install dependencies if 'node_modules' is not already present
if(!dirContent.includes('node_modules')) execSync('npm install', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') })
}
+
describe('test various CLI options', () => {
test('remix-tests version', () => {
diff --git a/libs/remix-tests/tsconfig.json b/libs/remix-tests/tsconfig.json
index 4422abf942..cf7076c1d1 100644
--- a/libs/remix-tests/tsconfig.json
+++ b/libs/remix-tests/tsconfig.json
@@ -3,9 +3,9 @@
"compilerOptions": {
"types": ["node", "jest"],
"module": "commonjs",
+ "esModuleInterop": true,
"allowJs": true,
"rootDir": "./",
- "esModuleInterop": true
},
"include": ["**/*.ts"]
}
\ No newline at end of file
diff --git a/libs/remix-tests/tsconfig.lib.json b/libs/remix-tests/tsconfig.lib.json
index ac117282b1..7b4b23c716 100644
--- a/libs/remix-tests/tsconfig.lib.json
+++ b/libs/remix-tests/tsconfig.lib.json
@@ -1,16 +1,15 @@
{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "module": "commonjs",
- "outDir": "../../dist/out-tsc",
- "declaration": true,
- "rootDir": "./",
- "types": ["node"]
- },
- "exclude": [
- "**/*.spec.ts",
- "tests/"
- ],
- "include": ["**/*.ts"]
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "module": "commonjs",
+ "outDir": "../../dist/out-tsc",
+ "declaration": true,
+ "rootDir": "./",
+ "types": ["node"]
+ },
+ "exclude": [
+ "**/*.spec.ts",
+ "tests/"
+ ],
+ "include": ["**/*.ts"]
}
-
\ No newline at end of file
diff --git a/libs/remix-tests/tsconfig.spec.json b/libs/remix-tests/tsconfig.spec.json
index 118a64f08e..559410b96a 100644
--- a/libs/remix-tests/tsconfig.spec.json
+++ b/libs/remix-tests/tsconfig.spec.json
@@ -1,16 +1,15 @@
{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "**/*.spec.ts",
- "**/*.spec.tsx",
- "**/*.spec.js",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
- }
-
\ No newline at end of file
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "include": [
+ "**/*.spec.ts",
+ "**/*.spec.tsx",
+ "**/*.spec.js",
+ "**/*.spec.jsx",
+ "**/*.d.ts"
+ ]
+}
diff --git a/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts b/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
index 8e3bc9f1c4..8a397cfd91 100644
--- a/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
+++ b/libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
@@ -181,35 +181,35 @@ export const fileRenamedSuccess = (path: string, removePath: string, files) => {
export const init = (provider, workspaceName: string, plugin, registry) => (dispatch: React.Dispatch) => {
if (provider) {
- provider.event.register('fileAdded', async (filePath) => {
+ provider.event.on('fileAdded', async (filePath) => {
if (extractParentFromKey(filePath) === '/.workspaces') return
const path = extractParentFromKey(filePath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(fileAddedSuccess(path, data))
if (filePath.includes('_test.sol')) {
- plugin.event.trigger('newTestFileCreated', [filePath])
+ plugin.emit('newTestFileCreated', filePath)
}
})
- provider.event.register('folderAdded', async (folderPath) => {
+ provider.event.on('folderAdded', async (folderPath) => {
if (extractParentFromKey(folderPath) === '/.workspaces') return
const path = extractParentFromKey(folderPath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(folderAddedSuccess(path, data))
})
- provider.event.register('fileRemoved', async (removePath) => {
+ provider.event.on('fileRemoved', async (removePath) => {
const path = extractParentFromKey(removePath) || provider.workspace || provider.type || ''
dispatch(fileRemovedSuccess(path, removePath))
})
- provider.event.register('fileRenamed', async (oldPath) => {
+ provider.event.on('fileRenamed', async (oldPath) => {
const path = extractParentFromKey(oldPath) || provider.workspace || provider.type || ''
const data = await fetchDirectoryContent(provider, path)
dispatch(fileRenamedSuccess(path, oldPath, data))
})
- provider.event.register('fileExternallyChanged', async (path: string, file: { content: string }) => {
+ provider.event.on('fileExternallyChanged', async (path: string, file: { content: string }) => {
const config = registry.get('config').api
const editor = registry.get('editor').api
@@ -225,10 +225,10 @@ export const init = (provider, workspaceName: string, plugin, registry) => (disp
))
}
})
- provider.event.register('fileRenamedError', async () => {
+ provider.event.on('fileRenamedError', async () => {
dispatch(displayNotification('File Renamed Failed', '', 'Ok', 'Cancel'))
})
- provider.event.register('rootFolderChanged', async () => {
+ provider.event.on('rootFolderChanged', async () => {
workspaceName = provider.workspace || provider.type || ''
fetchDirectory(provider, workspaceName)(dispatch)
})
diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
index fc8c89450a..c471449ac4 100644
--- a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
+++ b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
@@ -88,20 +88,17 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide: true,
title: '',
message: '',
- ok: {
- label: '',
- fn: () => {}
- },
- cancel: {
- label: '',
- fn: () => {}
- },
+ okLabel: '',
+ okFn: () => {},
+ cancelLabel: '',
+ cancelFn: () => {},
handleHide: null
},
modals: [],
toasterMsg: '',
mouseOverElement: null,
- showContextMenu: false
+ showContextMenu: false,
+ reservedKeywords: [name, 'gist-']
})
const [fileSystem, dispatch] = useReducer(fileSystemReducer, fileSystemInitialState)
const editRef = useRef(null)
@@ -122,13 +119,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
useEffect(() => {
if (fileSystem.notification.message) {
- modal(fileSystem.notification.title, fileSystem.notification.message, {
- label: fileSystem.notification.labelOk,
- fn: fileSystem.notification.actionOk
- }, {
- label: fileSystem.notification.labelCancel,
- fn: fileSystem.notification.actionCancel
- })
+ modal(fileSystem.notification.title, fileSystem.notification.message, fileSystem.notification.labelOk, fileSystem.notification.actionOk, fileSystem.notification.labelCancel, fileSystem.notification.actionCancel)
}
}, [fileSystem.notification.message])
@@ -201,8 +192,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
hide: false,
title: prevState.modals[0].title,
message: prevState.modals[0].message,
- ok: prevState.modals[0].ok,
- cancel: prevState.modals[0].cancel,
+ okLabel: prevState.modals[0].okLabel,
+ okFn: prevState.modals[0].okFn,
+ cancelLabel: prevState.modals[0].cancelLabel,
+ cancelFn: prevState.modals[0].cancelFn,
handleHide: prevState.modals[0].handleHide
}
@@ -230,6 +223,11 @@ export const FileExplorer = (props: FileExplorerProps) => {
return keyPath.join('/')
}
+ const hasReservedKeyword = (content: string): boolean => {
+ if (state.reservedKeywords.findIndex(value => content.startsWith(value)) !== -1) return true
+ else return false
+ }
+
const createNewFile = async (newFilePath: string) => {
const fileManager = state.fileManager
@@ -248,10 +246,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
} catch (error) {
- return modal('File Creation Failed', typeof error === 'string' ? error : error.message, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ return modal('File Creation Failed', typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
@@ -263,20 +258,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(dirName)
if (exists) {
- return modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, {
- label: 'Close',
- fn: () => {}
- }, null)
+ return modal('Rename File Failed', `A file or folder ${extractNameFromKey(newFolderPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
}
await fileManager.mkdir(dirName)
setState(prevState => {
return { ...prevState, focusElement: [{ key: newFolderPath, type: 'folder' }] }
})
} catch (e) {
- return modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ return modal('Folder Creation Failed', typeof e === 'string' ? e : e.message, 'Close', async () => {})
}
}
@@ -288,21 +277,15 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const isDir = state.fileManager.isDirectory(path)
- modal(`Delete ${isDir ? 'folder' : 'file'}`, `Are you sure you want to delete ${path} ${isDir ? 'folder' : 'file'}?`, {
- label: 'OK',
- fn: async () => {
- try {
- const fileManager = state.fileManager
+ 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}.`)
- }
+ await fileManager.remove(path)
+ } catch (e) {
+ toast(`Failed to remove ${isDir ? 'folder' : 'file'} ${path}.`)
}
- }, {
- label: 'Cancel',
- fn: () => {}
- })
+ }, 'Cancel', () => {})
}
const renamePath = async (oldPath: string, newPath: string) => {
@@ -311,18 +294,12 @@ export const FileExplorer = (props: FileExplorerProps) => {
const exists = await fileManager.exists(newPath)
if (exists) {
- modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, {
- label: 'Close',
- fn: () => {}
- }, null)
+ modal('Rename File Failed', `A file or folder ${extractNameFromKey(newPath)} already exists at this location. Please choose a different name.`, 'Close', () => {})
} else {
await fileManager.rename(oldPath, newPath)
}
} catch (error) {
- modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('Rename File Failed', 'Unexpected error while renaming: ' + typeof error === 'string' ? error : error.message, 'Close', async () => {})
}
}
@@ -345,19 +322,13 @@ export const FileExplorer = (props: FileExplorerProps) => {
fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) {
- modal('File Upload Failed', 'Special characters are not allowed', {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('File Upload Failed', 'Special characters are not allowed', 'Close', async () => {})
return
}
const success = await filesProvider.set(name, event.target.result)
if (!success) {
- return modal('File Upload Failed', 'Failed to create file ' + name, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ return modal('File Upload Failed', 'Failed to create file ' + name, 'Close', async () => {})
}
const config = registry.get('config').api
const editor = registry.get('editor').api
@@ -374,15 +345,9 @@ export const FileExplorer = (props: FileExplorerProps) => {
if (!exist) {
loadFile(name)
} else {
- modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, {
- label: 'OK',
- fn: () => {
- loadFile(name)
- }
- }, {
- label: 'Cancel',
- fn: () => {}
- })
+ modal('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', () => {
+ loadFile(name)
+ }, 'Cancel', () => {})
}
}).catch(error => {
if (error) console.log(error)
@@ -391,41 +356,23 @@ export const FileExplorer = (props: FileExplorerProps) => {
}
const publishToGist = () => {
- modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, {
- label: 'OK',
- fn: toGist
- }, {
- label: 'Cancel',
- fn: () => {}
- })
+ modal('Create a public gist', `Are you sure you want to anonymously publish all your files in the ${name} workspace as a public gist on github.com?`, 'OK', toGist, 'Cancel', () => {})
}
const toGist = (id?: string) => {
const filesProvider = fileSystem.provider.provider
const proccedResult = function (error, data) {
if (error) {
- modal('Publish to gist Failed', 'Failed to manage gist: ' + error, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('Publish to gist Failed', 'Failed to manage gist: ' + error, 'Close', () => {})
} else {
if (data.html_url) {
- modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, {
- label: 'OK',
- fn: () => {
- window.open(data.html_url, '_blank')
- }
- }, {
- label: 'Cancel',
- fn: () => {}
- })
+ modal('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, 'OK', () => {
+ window.open(data.html_url, '_blank')
+ }, 'Cancel', () => {})
} else {
const error = JSON.stringify(data.errors, null, '\t') || ''
const message = data.message === 'Not Found' ? data.message + '. Please make sure the API token has right to create a gist.' : data.message
- modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('Publish to gist Failed', message + ' ' + data.documentation_url + ' ' + error, 'Close', () => {})
}
}
}
@@ -451,20 +398,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
packageFiles(filesProvider, folder, async (error, packaged) => {
if (error) {
console.log(error)
- modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('Publish to gist Failed', 'Failed to create gist: ' + error.message, 'Close', async () => {})
} else {
// check for token
const config = registry.get('config').api
const accessToken = config.get('settings/gist-access-token')
if (!accessToken) {
- modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', {
- label: 'Close',
- fn: async () => {}
- }, null)
+ modal('Authorize Token', 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.', 'Close', () => {})
} else {
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' +
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist='
@@ -536,7 +477,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
- const modal = (title: string, message: string, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
+ const modal = (title: string, message: string, okLabel: string, okFn: () => void, cancelLabel?: string, cancelFn?: () => void) => {
setState(prevState => {
return {
...prevState,
@@ -544,8 +485,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
{
message,
title,
- ok,
- cancel,
+ okLabel,
+ okFn,
+ cancelLabel,
+ cancelFn,
handleHide: handleHideModal
}]
}
@@ -644,21 +587,28 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
}
if (helper.checkSpecialChars(content)) {
- modal('Validation Error', 'Special characters are not allowed', {
- label: 'OK',
- fn: () => {}
- }, null)
+ modal('Validation Error', 'Special characters are not allowed', 'OK', () => {})
} else {
if (state.focusEdit.isNew) {
- state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content))
- removeInputField(parentFolder)(dispatch)
+ if (hasReservedKeyword(content)) {
+ removeInputField(parentFolder)(dispatch)
+ modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
+ } else {
+ state.focusEdit.type === 'file' ? createNewFile(joinPath(parentFolder, content)) : createNewFolder(joinPath(parentFolder, content))
+ removeInputField(parentFolder)(dispatch)
+ }
} else {
- const oldPath: string = state.focusEdit.element
- const oldName = extractNameFromKey(oldPath)
- const newPath = oldPath.replace(oldName, content)
+ if (hasReservedKeyword(content)) {
+ editRef.current.textContent = state.focusEdit.lastEdit
+ modal('Reserved Keyword', `File name contains remix reserved keywords. '${content}'`, 'Close', () => {})
+ } else {
+ const oldPath: string = state.focusEdit.element
+ const oldName = extractNameFromKey(oldPath)
+ const newPath = oldPath.replace(oldName, content)
- editRef.current.textContent = extractNameFromKey(oldPath)
- renamePath(oldPath, newPath)
+ editRef.current.textContent = extractNameFromKey(oldPath)
+ renamePath(oldPath, newPath)
+ }
}
setState(prevState => {
return { ...prevState, focusEdit: { element: null, isNew: false, type: '', lastEdit: '' } }
@@ -865,8 +815,10 @@ export const FileExplorer = (props: FileExplorerProps) => {
title={ state.focusModal.title }
message={ state.focusModal.message }
hide={ state.focusModal.hide }
- ok={ state.focusModal.ok }
- cancel={ state.focusModal.cancel }
+ okLabel={ state.focusModal.okLabel }
+ okFn={ state.focusModal.okFn }
+ cancelLabel={ state.focusModal.cancelLabel }
+ cancelFn={ state.focusModal.cancelFn }
handleHide={ handleHideModal }
/>
}
diff --git a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
index 2caa41219d..ec016068e9 100644
--- a/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
+++ b/libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
@@ -18,7 +18,7 @@ export const ModalDialog = (props: ModalDialogProps) => {
const modalKeyEvent = (keyCode) => {
if (keyCode === 27) { // Esc
- if (props.cancel && props.cancel.fn) props.cancel.fn()
+ if (props.cancelFn) props.cancelFn()
handleHide()
} else if (keyCode === 13) { // Enter
enterHandler()
@@ -33,9 +33,9 @@ export const ModalDialog = (props: ModalDialogProps) => {
const enterHandler = () => {
if (state.toggleBtn) {
- if (props.ok && props.ok.fn) props.ok.fn()
+ if (props.okFn) props.okFn()
} else {
- if (props.cancel && props.cancel.fn) props.cancel.fn()
+ if (props.cancelFn) props.cancelFn()
}
handleHide()
}
@@ -79,29 +79,29 @@ export const ModalDialog = (props: ModalDialogProps) => {
{/* todo add autofocus ^^ */}
- { props.ok &&
+ { props.okLabel &&
{
- if (props.ok.fn) props.ok.fn()
+ if (props.okFn) props.okFn()
handleHide()
}}
>
- { props.ok.label ? props.ok.label : 'OK' }
+ { props.okLabel ? props.okLabel : 'OK' }
}
- { props.cancel &&
+ { props.cancelLabel &&
{
- if (props.cancel.fn) props.cancel.fn()
+ if (props.cancelFn) props.cancelFn()
handleHide()
}}
>
- { props.cancel.label ? props.cancel.label : 'Cancel' }
+ { props.cancelLabel ? props.cancelLabel : 'Cancel' }
}
diff --git a/libs/remix-ui/modal-dialog/src/lib/types/index.ts b/libs/remix-ui/modal-dialog/src/lib/types/index.ts
index 29c4d39505..58d15cdb87 100644
--- a/libs/remix-ui/modal-dialog/src/lib/types/index.ts
+++ b/libs/remix-ui/modal-dialog/src/lib/types/index.ts
@@ -2,8 +2,10 @@ export interface ModalDialogProps {
id?: string
title?: string,
message?: string,
- ok?: { label: string, fn: () => void },
- cancel: { label: string, fn: () => void },
+ okLabel?: string,
+ okFn?: () => void,
+ cancelLabel?: string,
+ cancelFn?: () => void,
modalClass?: string,
showCancelIcon?: boolean,
hide: boolean,
diff --git a/libs/remix-ui/toaster/src/lib/toaster.tsx b/libs/remix-ui/toaster/src/lib/toaster.tsx
index b7573f72cc..d3cd160c5c 100644
--- a/libs/remix-ui/toaster/src/lib/toaster.tsx
+++ b/libs/remix-ui/toaster/src/lib/toaster.tsx
@@ -91,10 +91,8 @@ export const Toaster = (props: ToasterProps) => {
<>
{}
- }}
+ cancelLabel='Close'
+ cancelFn={() => {}}
hide={!state.showModal}
handleHide={hideFullMessage}
/>
diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
index 1baca04eb7..1c7e063aa3 100644
--- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
+++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
@@ -91,25 +91,30 @@ export const Workspace = (props: WorkspaceProps) => {
const localhostDisconnect = () => {
if (state.currentWorkspace === LOCALHOST) setWorkspace(props.workspaces.length > 0 ? props.workspaces[0] : NO_WORKSPACE)
+ // This should be removed some time after refactoring: https://github.com/ethereum/remix-project/issues/1197
+ else {
+ setWorkspace(state.currentWorkspace) // Useful to switch to last selcted workspace when remixd is disconnected
+ props.fileManager.setMode('browser')
+ }
}
- props.localhost.event.unregister('disconnected', localhostDisconnect)
- props.localhost.event.register('disconnected', localhostDisconnect)
+ props.localhost.event.off('disconnected', localhostDisconnect)
+ props.localhost.event.on('disconnected', localhostDisconnect)
useEffect(() => {
- props.localhost.event.register('connected', () => {
+ props.localhost.event.on('connected', () => {
remixdExplorer.show()
setWorkspace(LOCALHOST)
})
- props.localhost.event.register('disconnected', () => {
+ props.localhost.event.on('disconnected', () => {
remixdExplorer.hide()
})
- props.localhost.event.register('loading', () => {
+ props.localhost.event.on('loading', () => {
remixdExplorer.loading()
})
- props.workspace.event.register('createWorkspace', (name) => {
+ props.workspace.event.on('createWorkspace', (name) => {
createNewWorkspace(name)
})
@@ -145,14 +150,10 @@ export const Workspace = (props: WorkspaceProps) => {
hide: true,
title: '',
message: null,
- ok: {
- label: '',
- fn: () => {}
- },
- cancel: {
- label: '',
- fn: () => {}
- },
+ okLabel: '',
+ okFn: () => {},
+ cancelLabel: '',
+ cancelFn: () => {},
handleHide: null
},
loadingLocalhost: false,
@@ -168,41 +169,20 @@ export const Workspace = (props: WorkspaceProps) => {
/* workspace creation, renaming and deletion */
const renameCurrentWorkspace = () => {
- modal('Rename Current Workspace', renameModalMessage(), {
- label: 'OK',
- fn: onFinishRenameWorkspace
- }, {
- label: '',
- fn: () => {}
- })
+ modal('Rename Current Workspace', renameModalMessage(), 'OK', onFinishRenameWorkspace, '', () => {})
}
const createWorkspace = () => {
- modal('Create Workspace', createModalMessage(), {
- label: 'OK',
- fn: onFinishCreateWorkspace
- }, {
- label: '',
- fn: () => {}
- })
+ modal('Create Workspace', createModalMessage(), 'OK', onFinishCreateWorkspace, '', () => {})
}
const deleteCurrentWorkspace = () => {
- modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', {
- label: 'OK',
- fn: onFinishDeleteWorkspace
- }, {
- label: '',
- fn: () => {}
- })
+ modal('Delete Current Workspace', 'Are you sure to delete the current workspace?', 'OK', onFinishDeleteWorkspace, '', () => {})
}
const modalMessage = (title: string, body: string) => {
setTimeout(() => { // wait for any previous modal a chance to close
- modal(title, body, {
- label: 'OK',
- fn: () => {}
- }, null)
+ modal(title, body, 'OK', () => {}, '', null)
}, 200)
}
@@ -272,11 +252,19 @@ export const Workspace = (props: WorkspaceProps) => {
const remixdExplorer = {
hide: async () => {
- await setWorkspace(NO_WORKSPACE)
- props.fileManager.setMode('browser')
- setState(prevState => {
- return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
- })
+ // If 'connect to localhost' is clicked from home tab, mode is not 'localhost'
+ if (props.fileManager.mode === 'localhost') {
+ await setWorkspace(NO_WORKSPACE)
+ props.fileManager.setMode('browser')
+ setState(prevState => {
+ return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
+ })
+ } else {
+ // Hide spinner in file explorer
+ setState(prevState => {
+ return { ...prevState, loadingLocalhost: false }
+ })
+ }
},
show: () => {
props.fileManager.setMode('localhost')
@@ -297,7 +285,7 @@ export const Workspace = (props: WorkspaceProps) => {
})
}
- const modal = async (title: string, message: string | JSX.Element, ok: { label: string, fn: () => void }, cancel: { label: string, fn: () => void }) => {
+ const modal = async (title: string, message: string | JSX.Element, okLabel: string, okFn: () => void, cancelLabel: string, cancelFn: () => void) => {
await setState(prevState => {
return {
...prevState,
@@ -306,8 +294,10 @@ export const Workspace = (props: WorkspaceProps) => {
hide: false,
message,
title,
- ok,
- cancel,
+ okLabel,
+ okFn,
+ cancelLabel,
+ cancelFn,
handleHide: handleHideModal
}
}
@@ -339,8 +329,10 @@ export const Workspace = (props: WorkspaceProps) => {
title={ state.modal.title }
message={ state.modal.message }
hide={ state.modal.hide }
- ok={ state.modal.ok }
- cancel={ state.modal.cancel }
+ okLabel={ state.modal.okLabel }
+ okFn={ state.modal.okFn }
+ cancelLabel={ state.modal.cancelLabel }
+ cancelFn={ state.modal.cancelFn }
handleHide={ handleHideModal }>
{ (typeof state.modal.message !== 'string') && state.modal.message }
diff --git a/libs/remixd/src/bin/remixd.ts b/libs/remixd/src/bin/remixd.ts
index 5b7076fd0d..8d2ebe9bb2 100644
--- a/libs/remixd/src/bin/remixd.ts
+++ b/libs/remixd/src/bin/remixd.ts
@@ -24,16 +24,18 @@ async function warnLatestVersion () {
const services = {
git: (readOnly: boolean) => new servicesList.GitClient(readOnly),
+ hardhat: (readOnly: boolean) => new servicesList.HardhatClient(readOnly),
folder: (readOnly: boolean) => new servicesList.Sharedfolder(readOnly)
}
const ports = {
git: 65521,
+ hardhat: 65522,
folder: 65520
}
const killCallBack: Array = []
-function startService (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
+function startService (service: S, callback: (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => void) {
const socket = new WebSocket(ports[service], { remixIdeUrl: program.remixIde }, () => services[service](program.readOnly || false))
socket.start(callback)
killCallBack.push(socket.close.bind(socket))
@@ -78,6 +80,10 @@ function startService (service: S, callback: (ws: WS
sharedFolderClient.setupNotifications(program.sharedFolder)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
+ startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
+ sharedFolderClient.setWebSocket(ws)
+ sharedFolderClient.sharedFolder(program.sharedFolder)
+ })
/*
startService('git', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)
diff --git a/libs/remixd/src/index.ts b/libs/remixd/src/index.ts
index 04725c14f2..849f35b6fa 100644
--- a/libs/remixd/src/index.ts
+++ b/libs/remixd/src/index.ts
@@ -1,6 +1,7 @@
'use strict'
import { RemixdClient as sharedFolder } from './services/remixdClient'
import { GitClient } from './services/gitClient'
+import { HardhatClient } from './services/hardhatClient'
import Websocket from './websocket'
import * as utils from './utils'
@@ -9,6 +10,7 @@ module.exports = {
utils,
services: {
sharedFolder,
- GitClient
+ GitClient,
+ HardhatClient
}
}
diff --git a/libs/remixd/src/serviceList.ts b/libs/remixd/src/serviceList.ts
index 5db445ee66..19d613b7c2 100644
--- a/libs/remixd/src/serviceList.ts
+++ b/libs/remixd/src/serviceList.ts
@@ -1,2 +1,3 @@
export { RemixdClient as Sharedfolder } from './services/remixdClient'
export { GitClient } from './services/gitClient'
+export { HardhatClient } from './services/hardhatClient'
diff --git a/libs/remixd/src/services/hardhatClient.ts b/libs/remixd/src/services/hardhatClient.ts
new file mode 100644
index 0000000000..7c2efd463a
--- /dev/null
+++ b/libs/remixd/src/services/hardhatClient.ts
@@ -0,0 +1,49 @@
+import * as WS from 'ws' // eslint-disable-line
+import { PluginClient } from '@remixproject/plugin'
+const { spawn } = require('child_process')
+
+export class HardhatClient extends PluginClient {
+ methods: Array
+ websocket: WS
+ currentSharedFolder: string
+
+ constructor (private readOnly = false) {
+ super()
+ this.methods = ['compile']
+ }
+
+ setWebSocket (websocket: WS): void {
+ this.websocket = websocket
+ }
+
+ sharedFolder (currentSharedFolder: string): void {
+ this.currentSharedFolder = currentSharedFolder
+ }
+
+ compile (configPath: string) {
+ return new Promise((resolve, reject) => {
+ if (this.readOnly) {
+ const errMsg = '[Hardhat Compilation]: Cannot compile in read-only mode'
+ console.log('\x1b[31m%s\x1b[0m', `${errMsg}`)
+ return reject(new Error(errMsg))
+ }
+ const cmd = `npx hardhat compile --config ${configPath}`
+ const options = { cwd: this.currentSharedFolder, shell: true }
+ const child = spawn(cmd, options)
+ let result = ''
+ let error = ''
+ child.stdout.on('data', (data) => {
+ console.log('\x1b[32m%s\x1b[0m', `[Hardhat Compilation]: ${data.toString()}`)
+ result += data.toString()
+ })
+ child.stderr.on('data', (err) => {
+ console.log('\x1b[31m%s\x1b[0m', `[Hardhat Compilation]: ${err.toString()}`)
+ error += err.toString()
+ })
+ child.on('close', () => {
+ if (error) reject(error)
+ else resolve(result)
+ })
+ })
+ }
+}