refactor_send_txs

pull/747/head
yann300 4 years ago
parent 9c42776f27
commit 44eac57b93
  1. 12
      apps/remix-ide/src/app/tabs/network-module.js
  2. 4
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  3. 65
      apps/remix-ide/src/blockchain/blockchain.js
  4. 8
      apps/remix-ide/src/blockchain/providers/vm.js
  5. 46
      apps/remix-ide/src/blockchain/txResultHelper.js
  6. 15
      jest.config.js
  7. 34
      libs/remix-lib/src/execution/execution-context.ts
  8. 10
      libs/remix-lib/src/execution/txExecution.ts
  9. 2
      libs/remix-lib/src/execution/txFormat.ts
  10. 37
      libs/remix-lib/src/execution/txListener.ts
  11. 250
      libs/remix-lib/src/execution/txRunner.ts
  12. 120
      libs/remix-lib/src/execution/txRunnerVM.ts
  13. 156
      libs/remix-lib/src/execution/txRunnerWeb3.ts
  14. 8
      libs/remix-lib/src/helpers/txResultHelper.ts
  15. 18
      libs/remix-lib/src/index.ts
  16. 379
      libs/remix-lib/src/universalDapp.ts
  17. 6
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  18. 4
      libs/remix-lib/test/txFormat.ts
  19. 31
      libs/remix-lib/test/txResultHelper.ts
  20. 8
      libs/remix-simulator/src/genesis.ts
  21. 2
      libs/remix-simulator/src/index.ts
  22. 16
      libs/remix-simulator/src/methods/accounts.ts
  23. 18
      libs/remix-simulator/src/methods/blocks.ts
  24. 12
      libs/remix-simulator/src/methods/debug.ts
  25. 24
      libs/remix-simulator/src/methods/filters.ts
  26. 69
      libs/remix-simulator/src/methods/transactions.ts
  27. 23
      libs/remix-simulator/src/methods/txProcess.ts
  28. 57
      libs/remix-simulator/src/provider.ts
  29. 147
      libs/remix-simulator/src/vm-context.ts
  30. 4
      libs/remix-tests/jest.config.js
  31. 2
      libs/remix-tests/src/deployer.ts
  32. 8
      libs/remix-tests/tests/testRunner.cli.spec.ts
  33. 2
      libs/remix-tests/tsconfig.json
  34. 3
      libs/remix-tests/tsconfig.lib.json
  35. 1
      libs/remix-tests/tsconfig.spec.json

@ -22,18 +22,6 @@ export class NetworkModule extends Plugin {
this.blockchain.event.register('contextChanged', (provider) => { this.blockchain.event.register('contextChanged', (provider) => {
this.emit('providerChanged', 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) */ /** Return the current network provider (web3, vm, injected) */

@ -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 (error) return console.log(error)
if (call) return if (call) return
const rawAddress = txResult.receipt.contractAddress
if (!rawAddress) return // not a contract creation if (!rawAddress) return // not a contract creation
const address = helper.addressToString(rawAddress) const address = helper.addressToString(rawAddress)
// save back created addresses for the convertion from tokens to real adresses // save back created addresses for the convertion from tokens to real adresses

@ -3,7 +3,8 @@ const txFormat = remixLib.execution.txFormat
const txExecution = remixLib.execution.txExecution const txExecution = remixLib.execution.txExecution
const typeConversion = remixLib.execution.typeConversion const typeConversion = remixLib.execution.typeConversion
const Txlistener = remixLib.execution.txListener 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 txHelper = remixLib.execution.txHelper
const EventManager = remixLib.EventManager const EventManager = remixLib.EventManager
const executionContext = remixLib.execution.executionContext const executionContext = remixLib.execution.executionContext
@ -12,7 +13,7 @@ const Web3 = require('web3')
const async = require('async') const async = require('async')
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const { resultToRemixTx } = require('./txResultHelper') const { resultToRemixTx } = remixLib.helpers.txResultHelper
const VMProvider = require('./providers/vm.js') const VMProvider = require('./providers/vm.js')
const InjectedProvider = require('./providers/injected.js') const InjectedProvider = require('./providers/injected.js')
@ -26,8 +27,7 @@ class Blockchain {
this.events = new EventEmitter() this.events = new EventEmitter()
this.config = config this.config = config
const web3Runner = new TxRunnerWeb3({
this.txRunner = new TxRunner({}, {
config: config, config: config,
detectNetwork: (cb) => { detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb) this.executionContext.detectNetwork(cb)
@ -35,7 +35,9 @@ class Blockchain {
personalMode: () => { personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false 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.executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
this.networkcallid = 0 this.networkcallid = 0
@ -123,7 +125,7 @@ class Blockchain {
if (error) { if (error) {
return finalCb(`creation of ${selectedContract.name} errored: ${(error.message ? error.message : 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`) return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
} }
finalCb(null, selectedContract, address) finalCb(null, selectedContract, address)
@ -309,18 +311,17 @@ class Blockchain {
resetEnvironment () { resetEnvironment () {
this.getCurrentProvider().resetEnvironment() this.getCurrentProvider().resetEnvironment()
// TODO: most params here can be refactored away in txRunner // TODO: most params here can be refactored away in txRunner
// this.txRunner = new TxRunner(this.providers.vm.accounts, { const web3Runner = new TxRunnerWeb3({
this.txRunner = new TxRunner(this.providers.vm.RemixSimulatorProvider.Accounts.accounts, {
// TODO: only used to check value of doNotShowTransactionConfirmationAgain property
config: this.config, config: this.config,
// TODO: to refactor, TxRunner already has access to executionContext
detectNetwork: (cb) => { detectNetwork: (cb) => {
this.executionContext.detectNetwork(cb) this.executionContext.detectNetwork(cb)
}, },
personalMode: () => { personalMode: () => {
return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false 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.txRunner.event.register('transactionBroadcasted', (txhash) => {
this.executionContext.detectNetwork((error, network) => { this.executionContext.detectNetwork((error, network) => {
if (error || !network) return if (error || !network) return
@ -372,10 +373,11 @@ class Blockchain {
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() }, (network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
(error, continueTxExecution, cancelCb) => { if (error) { reject(error) } else { continueTxExecution() } }, (error, continueTxExecution, cancelCb) => { if (error) { reject(error) } else { continueTxExecution() } },
(okCb, cancelCb) => { okCb() }, (okCb, cancelCb) => { okCb() },
(error, result) => { async (error, result) => {
if (error) return reject(error) if (error) return reject(error)
try { try {
resolve(resultToRemixTx(result)) const execResult = await this.web3().eth.getExecutionResultFromSimulator(result.transactionHash)
resolve(resultToRemixTx(result, execResult))
} catch (e) { } catch (e) {
reject(e) reject(e)
} }
@ -429,19 +431,24 @@ class Blockchain {
function runTransaction (fromAddress, value, gasLimit, next) { 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 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 } 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) tx.timestamp = Date.now()
if (tx.timestamp) {
timestamp = tx.timestamp
}
const timestamp = tx.timestamp
self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad]) self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb, self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
function (error, result) { async (error, result) => {
if (error) return next(error) 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') 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 && (typeof (error) !== 'string')) {
if (error.message) error = error.message if (error.message) error = error.message
@ -454,25 +461,29 @@ class Blockchain {
) )
} }
], ],
(error, txResult) => { async (error, txResult) => {
if (error) { if (error) {
return cb(error) return cb(error)
} }
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
let execResult
let returnValue = null
if (isVM) { if (isVM) {
const vmError = txExecution.checkVMError(txResult) 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) { if (vmError.error) {
return cb(vmError.message) return cb(vmError.message)
} }
} }
}
let address = null let address = null
let returnValue = null if (txResult && txResult.receipt) {
if (txResult && txResult.result) { address = txResult.receipt.contractAddress
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
} }
cb(error, txResult, address, returnValue) cb(error, txResult, address, returnValue)

@ -1,14 +1,16 @@
const Web3 = require('web3') const Web3 = require('web3')
const { BN, privateToAddress, hashPersonalMessage } = require('ethereumjs-util') const { BN, privateToAddress, stripHexPrefix, hashPersonalMessage } = require('ethereumjs-util')
const RemixSimulator = require('@remix-project/remix-simulator') const { Provider, extend } = require('@remix-project/remix-simulator')
class VMProvider { class VMProvider {
constructor (executionContext) { constructor (executionContext) {
this.executionContext = executionContext this.executionContext = executionContext
this.RemixSimulatorProvider = new RemixSimulator.Provider({ executionContext: this.executionContext }) this.RemixSimulatorProvider = new Provider({})
this.RemixSimulatorProvider.init() this.RemixSimulatorProvider.init()
this.web3 = new Web3(this.RemixSimulatorProvider) this.web3 = new Web3(this.RemixSimulatorProvider)
extend(this.web3)
this.accounts = {} this.accounts = {}
this.executionContext.setWeb3('vm', this.web3)
} }
getAccounts (cb) { getAccounts (cb) {

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

@ -5,5 +5,18 @@ module.exports = {
}, },
resolver: '@nrwl/jest/plugins/resolver', resolver: '@nrwl/jest/plugins/resolver',
moduleFileExtensions: ['ts', 'js', 'html'], moduleFileExtensions: ['ts', 'js', 'html'],
coverageReporters: ['html'] coverageReporters: ['html'],
moduleNameMapper:{
"@remix-project/remix-analyzer": "<rootDir>/../../dist/libs/remix-analyzer/index.js",
"@remix-project/remix-astwalker": "<rootDir>/../../dist/libs/remix-astwalker/index.js",
"@remix-project/remix-debug": "<rootDir>/../../dist/libs/remix-debug/src/index.js",
"@remix-project/remix-lib": "<rootDir>/../../dist/libs/remix-lib/src/index.js",
"@remix-project/remix-simulator": "<rootDir>/../../dist/libs/remix-simulator/src/index.js",
"@remix-project/remix-solidity": "<rootDir>/../../dist/libs/remix-solidity/index.js",
"@remix-project/remix-tests": "<rootDir>/../../dist/libs/remix-tests/src/index.js",
"@remix-project/remix-url-resolver":
"<rootDir>/../../dist/libs/remix-url-resolver/index.js"
,
"@remix-project/remixd": "<rootDir>/../../dist/libs/remixd/index.js"
}
}; };

@ -4,7 +4,6 @@ import Web3 from 'web3'
import { EventManager } from '../eventManager' import { EventManager } from '../eventManager'
import { rlp, keccak, bufferToHex } from 'ethereumjs-util' import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
import { Web3VmProvider } from '../web3Provider/web3VmProvider' import { Web3VmProvider } from '../web3Provider/web3VmProvider'
import { LogsManager } from './logsManager'
import VM from '@ethereumjs/vm' import VM from '@ethereumjs/vm'
import Common from '@ethereumjs/common' import Common from '@ethereumjs/common'
import StateManager from '@ethereumjs/vm/dist/state/stateManager' import StateManager from '@ethereumjs/vm/dist/state/stateManager'
@ -100,22 +99,21 @@ class StateManagerCommonStorageDump extends StateManager {
*/ */
export class ExecutionContext { export class ExecutionContext {
event event
logsManager blockGasLimitDefault: number
blockGasLimitDefault blockGasLimit: number
blockGasLimit
customNetWorks customNetWorks
blocks blocks
latestBlockNumber latestBlockNumber
txs txs
executionContext executionContext: string
listenOnLastBlockId listenOnLastBlockId
currentFork: string currentFork: string
vms vms
mainNetGenesisHash: string mainNetGenesisHash: string
customWeb3: { [key: string]: Web3 }
constructor () { constructor () {
this.event = new EventManager() this.event = new EventManager()
this.logsManager = new LogsManager()
this.executionContext = null this.executionContext = null
this.blockGasLimitDefault = 4300000 this.blockGasLimitDefault = 4300000
this.blockGasLimit = this.blockGasLimitDefault this.blockGasLimit = this.blockGasLimitDefault
@ -134,6 +132,7 @@ export class ExecutionContext {
this.blocks = {} this.blocks = {}
this.latestBlockNumber = 0 this.latestBlockNumber = 0
this.txs = {} this.txs = {}
this.customWeb3 = {} // mapping between a context name and a web3.js instance
} }
init (config) { init (config) {
@ -172,7 +171,12 @@ export class ExecutionContext {
return this.executionContext === 'vm' return this.executionContext === 'vm'
} }
setWeb3 (context: string, web3: Web3) {
this.customWeb3[context] = web3
}
web3 () { web3 () {
if (this.customWeb3[this.executionContext]) return this.customWeb3[this.executionContext]
return this.isVM() ? this.vms[this.currentFork].web3vm : web3 return this.isVM() ? this.vms[this.currentFork].web3vm : web3
} }
@ -340,22 +344,4 @@ export class ExecutionContext {
return transactionDetailsLinks[network] + hash 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
}
} }

@ -53,10 +53,10 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner,
/** /**
* check if the vm has errored * 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 } * @return {Object} - { error: true/false, message: DOMNode }
*/ */
export function checkVMError (txResult) { export function checkVMError (execResult) {
const errorCode = { const errorCode = {
OUT_OF_GAS: 'out of gas', OUT_OF_GAS: 'out of gas',
STACK_UNDERFLOW: 'stack underflow', STACK_UNDERFLOW: 'stack underflow',
@ -74,10 +74,10 @@ export function checkVMError (txResult) {
error: false, error: false,
message: '' message: ''
} }
if (!txResult.result.execResult.exceptionError) { if (!execResult.exceptionError) {
return ret return ret
} }
const exceptionError = txResult.result.execResult.exceptionError.error || '' const exceptionError = execResult.exceptionError.error || ''
const error = `VM error: ${exceptionError}.\n` const error = `VM error: ${exceptionError}.\n`
let msg let msg
if (exceptionError === errorCode.INVALID_OPCODE) { 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' msg = '\tThe transaction ran out of gas. Please increase the Gas Limit.\n'
ret.error = true ret.error = true
} else if (exceptionError === errorCode.REVERT) { } else if (exceptionError === errorCode.REVERT) {
const returnData = txResult.result.execResult.returnValue const returnData = execResult.returnValue
// It is the hash of Error(string) // It is the hash of Error(string)
if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) { if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) {
const abiCoder = new ethers.utils.AbiCoder() const abiCoder = new ethers.utils.AbiCoder()

@ -314,7 +314,7 @@ export function deployLibrary (libraryName, libraryShortName, library, contracts
if (err) { if (err) {
return callback(err) return callback(err)
} }
const address = txResult.result.createdAddress || txResult.result.contractAddress const address = txResult.receipt.contractAddress
library.address = address library.address = address
callback(err, address) callback(err, address)
}) })

@ -8,13 +8,13 @@ import { ExecutionContext } from './execution-context'
import { decodeResponse } from './txFormat' import { decodeResponse } from './txFormat'
import { getFunction, getReceiveInterface, getConstructorInterface, visitContracts, makeFullTypeDefinition } from './txHelper' import { getFunction, getReceiveInterface, getConstructorInterface, visitContracts, makeFullTypeDefinition } from './txHelper'
function addExecutionCosts (txResult, tx) { function addExecutionCosts (txResult, tx, execResult) {
if (txResult && txResult.result) { if (txResult) {
if (txResult.result.execResult) { if (execResult) {
tx.returnValue = txResult.result.execResult.returnValue tx.returnValue = execResult.returnValue
if (txResult.result.execResult.gasUsed) tx.executionCost = txResult.result.execResult.gasUsed.toString(10) 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)
} }
} }
@ -55,7 +55,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 if (error) return
// we go for that case if // we go for that case if
// in VM mode // in VM mode
@ -63,17 +63,25 @@ export class TxListener {
if (!this._isListening) return // we don't listen 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 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 = { const call = {
from: from, from: from,
to: to, to: to,
input: data, input: data,
hash: txResult.transactionHash ? txResult.transactionHash : 'call' + (from || '') + to + data, hash: txResult.transactionHash ? txResult.transactionHash : 'call' + (from || '') + to + data,
isCall: true, isCall: true,
returnValue: this.executionContext.isVM() ? txResult.result.execResult.returnValue : toBuffer(txResult.result), returnValue,
envMode: this.executionContext.getProvider() envMode: this.executionContext.getProvider()
} }
addExecutionCosts(txResult, call) addExecutionCosts(txResult, call, execResult)
this._resolveTx(call, call, (error, resolvedData) => { this._resolveTx(call, call, (error, resolvedData) => {
if (!error) { if (!error) {
this.event.trigger('newCall', [call]) this.event.trigger('newCall', [call])
@ -89,12 +97,17 @@ export class TxListener {
// in web3 mode && listen remix txs only // in web3 mode && listen remix txs only
if (!this._isListening) return // we don't listen 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 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) 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.envMode = this.executionContext.getProvider()
tx.status = txResult.result.status // 0x0 or 0x1 tx.status = txResult.receipt.status // 0x0 or 0x1
this._resolve([tx], () => { this._resolve([tx], () => {
}) })
}) })

@ -1,91 +1,26 @@
'use strict' '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' import { EventManager } from '../eventManager'
export class TxRunner { export class TxRunner {
event event
executionContext
_api
blockNumber
runAsync runAsync
pendingTxs pendingTxs
vmaccounts
queusTxs queusTxs
blocks opt
commonContext internalRunner
constructor (internalRunner, opt) {
constructor (vmaccounts, api, executionContext) { this.opt = opt || {}
this.internalRunner = internalRunner
this.event = new EventManager() this.event = new EventManager()
// has a default for now for backwards compatability
this.executionContext = executionContext || new ExecutionContext() 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.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.pendingTxs = {} this.pendingTxs = {}
this.vmaccounts = vmaccounts
this.queusTxs = [] this.queusTxs = []
this.blocks = []
} }
rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) { rawRun (args, confirmationCb, gasEstimationForceSend, promptCb, cb) {
let timestamp = Date.now() run(this, args, args.timestamp || Date.now(), confirmationCb, gasEstimationForceSend, promptCb, cb)
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. `)
}
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
@ -93,174 +28,9 @@ export class TxRunner {
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data
} }
this.internalRunner.execute(args, confirmationCb, gasEstimationForceSend, promptCb, callback)
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
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.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)
}
})
})
}
}
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) { function run (self, tx, stamp, confirmationCb, gasEstimationForceSend = null, promptCb = null, callback = null) {
if (!self.runAsync && Object.keys(self.pendingTxs).length) { if (!self.runAsync && Object.keys(self.pendingTxs).length) {

@ -0,0 +1,120 @@
'use strict'
import { Transaction } from 'ethereumjs-tx'
import Block from 'ethereumjs-block'
import { BN, bufferToHex } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
import { LogsManager } from './logsManager'
export class TxRunnerVM {
event
_api
blockNumber
runAsync
pendingTxs
vmaccounts
queusTxs
blocks
txs
logsManager
getVM: () => any
constructor (vmaccounts, api, getVM) {
this.event = new EventManager()
this.logsManager = new LogsManager()
// has a default for now for backwards compatability
this.getVM = getVM
this._api = api
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')
}
this.getVM().stateManager.getAccount(Buffer.from(from.replace('0x', ''), 'hex'), (err, res) => {
if (err) {
callback('Account not found')
} else {
// 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 = new Transaction({
nonce: new BN(res.nonce),
gasPrice: '0x1',
gasLimit: gasLimit,
to: to,
value: value,
data: Buffer.from(data.slice(2), 'hex')
})
tx.sign(account.privateKey)
const coinbases = ['0x0e9281e9c6a0808672eaba6bd1220e144c9bb07a', '0x8945a1288dc78a6d8952a92c77aee6730b414778', '0x94d76e24f818426ae84aa404140e8d5f60e10e7e']
const difficulties = [new BN('69762765929000', 10), new BN('70762765929000', 10), new BN('71762765929000', 10)]
const block = new Block({
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, 10).imuln(2)
},
transactions: [tx],
uncleHeaders: []
})
if (!useCall) {
++self.blockNumber
this.runBlockInVm(tx, block, callback)
} else {
this.getVM().stateManager.checkpoint(() => {
this.runBlockInVm(tx, block, (err, result) => {
this.getVM().stateManager.revert(() => {
callback(err, result)
})
})
})
}
}
})
}
runBlockInVm (tx, block, callback) {
this.getVM().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)
})
}
}

@ -0,0 +1,156 @@
'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
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) {
return new Promise((resolve, reject) => {
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, web3))
}
return resolve(receipt)
})
})
}
async function tryTillTxAvailable (txhash, web3) {
return new Promise((resolve, reject) => {
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, web3))
}
return resolve(tx)
})
})
}
async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) }

@ -20,9 +20,9 @@ function convertToPrefixedHex (input) {
Also, VM results use BN and Buffers, Node results use hex strings/ints, Also, VM results use BN and Buffers, Node results use hex strings/ints,
So we need to normalize the values to prefixed hex strings So we need to normalize the values to prefixed hex strings
*/ */
export function resultToRemixTx (txResult) { export function resultToRemixTx (txResult, execResult) {
const { result, transactionHash } = txResult const { receipt, transactionHash, result } = txResult
const { status, execResult, gasUsed, createdAddress, contractAddress } = result const { status, gasUsed, contractAddress } = receipt
let returnValue, errorMessage let returnValue, errorMessage
if (isHexString(result)) { if (isHexString(result)) {
@ -38,6 +38,6 @@ export function resultToRemixTx (txResult) {
gasUsed: convertToPrefixedHex(gasUsed), gasUsed: convertToPrefixedHex(gasUsed),
error: errorMessage, error: errorMessage,
return: convertToPrefixedHex(returnValue), return: convertToPrefixedHex(returnValue),
createdAddress: convertToPrefixedHex(createdAddress || contractAddress) createdAddress: convertToPrefixedHex(contractAddress)
} }
} }

@ -12,9 +12,12 @@ import * as txHelper from './execution/txHelper'
import * as txFormat from './execution/txFormat' import * as txFormat from './execution/txFormat'
import { TxListener } from './execution/txListener' import { TxListener } from './execution/txListener'
import { TxRunner } from './execution/txRunner' import { TxRunner } from './execution/txRunner'
import { LogsManager } from './execution/logsManager'
import { ExecutionContext } from './execution/execution-context' import { ExecutionContext } from './execution/execution-context'
import * as typeConversion from './execution/typeConversion' 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() export = modules()
@ -23,7 +26,8 @@ function modules () {
EventManager: EventManager, EventManager: EventManager,
helpers: { helpers: {
ui: uiHelper, ui: uiHelper,
compiler: compilerHelper compiler: compilerHelper,
txResultHelper
}, },
vm: { vm: {
Web3Providers: Web3Providers, Web3Providers: Web3Providers,
@ -39,9 +43,11 @@ function modules () {
executionContext: new ExecutionContext(), executionContext: new ExecutionContext(),
txFormat: txFormat, txFormat: txFormat,
txListener: TxListener, txListener: TxListener,
txRunner: TxRunner, TxRunner: TxRunner,
typeConversion: typeConversion TxRunnerWeb3: TxRunnerWeb3,
}, TxRunnerVM: TxRunnerVM,
UniversalDApp: UniversalDApp typeConversion: typeConversion,
LogsManager
}
} }
} }

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

@ -31,6 +31,9 @@ export class Web3VmProvider {
toBigNumber toBigNumber
isAddress isAddress
utils utils
txsMapBlock
blocks
latestBlockNumber
constructor () { constructor () {
this.web3 = new Web3() this.web3 = new Web3()
@ -69,6 +72,9 @@ export class Web3VmProvider {
this.toBigNumber = (...args) => this.web3.utils.toBN(...args) this.toBigNumber = (...args) => this.web3.utils.toBN(...args)
this.isAddress = (...args) => this.web3.utils.isAddress(...args) this.isAddress = (...args) => this.web3.utils.isAddress(...args)
this.utils = Web3.utils || [] this.utils = Web3.utils || []
this.txsMapBlock = {}
this.blocks = {}
this.latestBlockNumber
} }
setVM (vm) { setVM (vm) {

@ -161,8 +161,8 @@ tape('ContractParameters - (TxFormat.buildData) - link Libraries', function (t)
} }
const callbackDeployLibraries = (param, callback) => { const callbackDeployLibraries = (param, callback) => {
callback(null, { callback(null, {
result: { receipt: {
createdAddress: fakeDeployedContracts[param.data.contractName] contractAddress: fakeDeployedContracts[param.data.contractName]
} }
}) })
} // fake } // fake

@ -18,12 +18,13 @@ const GAS_USED_INT = 75427
const GAS_USED_HEX = '0x126a3' const GAS_USED_HEX = '0x126a3'
const NODE_CALL_RESULT = { const NODE_CALL_RESULT = {
receipt: {},
result: RETURN_VALUE_HEX, result: RETURN_VALUE_HEX,
transactionHash: undefined transactionHash: undefined
} }
const NODE_TX_RESULT = { const NODE_TX_RESULT = {
result: { receipt: {
blockHash: '0x380485a4e6372a42e36489783c7f7cb66257612133cd245859c206fd476e9c44', blockHash: '0x380485a4e6372a42e36489783c7f7cb66257612133cd245859c206fd476e9c44',
blockNumber: 5994, blockNumber: 5994,
contractAddress: CONTRACT_ADDRESS_HEX, contractAddress: CONTRACT_ADDRESS_HEX,
@ -39,26 +40,31 @@ const NODE_TX_RESULT = {
} }
const VM_RESULT = { const VM_RESULT = {
result: { receipt: {
amountSpent: new BN(1), amountSpent: new BN(1),
createdAddress: CONTRACT_ADDRESS_BUFFER, contractAddress: CONTRACT_ADDRESS_BUFFER,
gasRefund: new BN(0), gasRefund: new BN(0),
gasUsed: new BN(GAS_USED_INT), gasUsed: new BN(GAS_USED_INT),
status: STATUS_OK, status: STATUS_OK,
execResult: { },
transactionHash: TRANSACTION_HASH
}
const EXEC_RESULT = {
exceptionError: null, exceptionError: null,
gasRefund: new BN(0), gasRefund: new BN(0),
gasUsed: new BN(GAS_USED_INT), gasUsed: new BN(GAS_USED_INT),
returnValue: RETURN_VALUE_BUFFER returnValue: RETURN_VALUE_BUFFER
} }
},
transactionHash: TRANSACTION_HASH const EXEC_RESULT_ERROR = {
exceptionError: 'this is an error'
} }
tape('converts node transaction result to RemixTx', function (t) { tape('converts node transaction result to RemixTx', function (t) {
// contract creation // contract creation
let txResult = { ...NODE_TX_RESULT } let txResult = { ...NODE_TX_RESULT }
let remixTx = resultToRemixTx(txResult) let remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.transactionHash, TRANSACTION_HASH) t.equal(remixTx.transactionHash, TRANSACTION_HASH)
t.equal(remixTx.createdAddress, CONTRACT_ADDRESS_HEX) 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) t.equal(remixTx.error, undefined)
// contract method tx // contract method tx
txResult.result.contractAddress = null txResult.receipt.contractAddress = null
remixTx = resultToRemixTx(txResult) remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.createdAddress, null) t.equal(remixTx.createdAddress, null)
t.end() t.end()
@ -77,7 +83,7 @@ tape('converts node transaction result to RemixTx', function (t) {
tape('converts node call result to RemixTx', function (t) { tape('converts node call result to RemixTx', function (t) {
let txResult = { ...NODE_CALL_RESULT } let txResult = { ...NODE_CALL_RESULT }
let remixTx = resultToRemixTx(txResult) let remixTx = resultToRemixTx(txResult, {})
t.equal(remixTx.transactionHash, undefined) t.equal(remixTx.transactionHash, undefined)
t.equal(remixTx.createdAddress, 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) { tape('converts VM result to RemixTx', function (t) {
let txResult = { ...VM_RESULT } let txResult = { ...VM_RESULT }
let remixTx = resultToRemixTx(txResult) let remixTx = resultToRemixTx(txResult, EXEC_RESULT)
t.equal(remixTx.transactionHash, t.equal(remixTx.transactionHash,
TRANSACTION_HASH) TRANSACTION_HASH)
@ -101,8 +107,7 @@ tape('converts VM result to RemixTx', function (t) {
t.equal(remixTx.return, RETURN_VALUE_HEX) t.equal(remixTx.return, RETURN_VALUE_HEX)
t.equal(remixTx.error, null) t.equal(remixTx.error, null)
txResult.result.execResult.exceptionError = 'this is an error' remixTx = resultToRemixTx(VM_RESULT, EXEC_RESULT_ERROR)
remixTx = resultToRemixTx(txResult)
t.equal(remixTx.error, 'this is an error') t.equal(remixTx.error, 'this is an error')
t.end() t.end()

@ -1,7 +1,7 @@
import { Block } from '@ethereumjs/block' import { Block } from '@ethereumjs/block'
import { BN } from 'ethereumjs-util' import { BN } from 'ethereumjs-util'
export function generateBlock (executionContext) { export function generateBlock (vmContext) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const block: Block = Block.fromBlockData({ const block: Block = Block.fromBlockData({
header: { header: {
@ -11,10 +11,10 @@ export function generateBlock (executionContext) {
difficulty: new BN('69762765929000', 10), difficulty: new BN('69762765929000', 10),
gasLimit: new BN('8000000').imuln(1) 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(() => { vmContext.vm().runBlock({ block: block, generate: true, skipBlockValidation: true, skipBalance: false }).then(() => {
executionContext.addBlock(block) vmContext.addBlock(block)
resolve({}) resolve({})
}).catch((e) => reject(e)) }).catch((e) => reject(e))
}) })

@ -1 +1 @@
export { Provider } from './provider' export { Provider, extend } from './provider'

@ -6,22 +6,20 @@ export class Accounts {
web3 web3
accounts: Record<string, unknown> accounts: Record<string, unknown>
accountsKeys: Record<string, unknown> accountsKeys: Record<string, unknown>
executionContext vmContext
constructor (executionContext) { constructor (vmContext) {
this.web3 = new Web3() this.web3 = new Web3()
this.executionContext = executionContext this.vmContext = vmContext
// TODO: make it random and/or use remix-libs // TODO: make it random and/or use remix-libs
this.accounts = {} this.accounts = {}
this.accountsKeys = {} this.accountsKeys = {}
this.executionContext.init({ get: () => { return true } })
} }
async resetAccounts (): Promise<void> { async resetAccounts (): Promise<void> {
// TODO: setting this to {} breaks the app currently, unclear why still this.accounts = {}
// this.accounts = {} this.accountsKeys = {}
// this.accountsKeys = {}
await this._addAccount('503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb', '0x56BC75E2D63100000') await this._addAccount('503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb', '0x56BC75E2D63100000')
await this._addAccount('7e5bfb82febc4c2c8529167104271ceec190eafdca277314912eaabdb67c6e5f', '0x56BC75E2D63100000') await this._addAccount('7e5bfb82febc4c2c8529167104271ceec190eafdca277314912eaabdb67c6e5f', '0x56BC75E2D63100000')
await this._addAccount('cc6d63f85de8fef05446ebdd3c537c72152d0fc437fd7aa62b3019b79bd1fdd4', '0x56BC75E2D63100000') await this._addAccount('cc6d63f85de8fef05446ebdd3c537c72152d0fc437fd7aa62b3019b79bd1fdd4', '0x56BC75E2D63100000')
@ -47,7 +45,7 @@ export class Accounts {
this.accounts[addressStr] = { privateKey, nonce: 0 } this.accounts[addressStr] = { privateKey, nonce: 0 }
this.accountsKeys[addressStr] = '0x' + privateKey.toString('hex') 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) => { stateManager.getAccount(Address.fromString(addressStr)).then((account) => {
account.balance = new BN(balance.replace('0x', '') || 'f00000000000000001', 16) account.balance = new BN(balance.replace('0x', '') || 'f00000000000000001', 16)
stateManager.putAccount(Address.fromString(addressStr), account).catch((error) => { stateManager.putAccount(Address.fromString(addressStr), account).catch((error) => {
@ -85,7 +83,7 @@ export class Accounts {
eth_getBalance (payload, cb) { eth_getBalance (payload, cb) {
const address = payload.params[0] 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)) cb(null, new BN(account.balance).toString(10))
}).catch((error) => { }).catch((error) => {
cb(error) cb(error)

@ -1,10 +1,10 @@
export class Blocks { export class Blocks {
executionContext vmContext
coinbase: string coinbase: string
blockNumber: number blockNumber: number
constructor (executionContext, _options) { constructor (vmContext, _options) {
this.executionContext = executionContext this.vmContext = vmContext
const options = _options || {} const options = _options || {}
this.coinbase = options.coinbase || '0x0000000000000000000000000000000000000000' this.coinbase = options.coinbase || '0x0000000000000000000000000000000000000000'
this.blockNumber = 0 this.blockNumber = 0
@ -28,13 +28,13 @@ export class Blocks {
eth_getBlockByNumber (payload, cb) { eth_getBlockByNumber (payload, cb) {
let blockIndex = payload.params[0] let blockIndex = payload.params[0]
if (blockIndex === 'latest') { if (blockIndex === 'latest') {
blockIndex = this.executionContext.latestBlockNumber blockIndex = this.vmContext.latestBlockNumber
} }
if (Number.isInteger(blockIndex)) { if (Number.isInteger(blockIndex)) {
blockIndex = '0x' + blockIndex.toString(16) blockIndex = '0x' + blockIndex.toString(16)
} }
const block = this.executionContext.blocks[blockIndex] const block = this.vmContext.blocks[blockIndex]
if (!block) { if (!block) {
return cb(new Error('block not found')) return cb(new Error('block not found'))
@ -70,7 +70,7 @@ export class Blocks {
} }
eth_getBlockByHash (payload, cb) { eth_getBlockByHash (payload, cb) {
const block = this.executionContext.blocks[payload.params[0]] const block = this.vmContext.blocks[payload.params[0]]
const b = { const b = {
number: this.toHex(block.header.number), number: this.toHex(block.header.number),
@ -109,13 +109,13 @@ export class Blocks {
} }
eth_getBlockTransactionCountByHash (payload, cb) { 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) cb(null, block.transactions.length)
} }
eth_getBlockTransactionCountByNumber (payload, cb) { 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) cb(null, block.transactions.length)
} }
@ -131,7 +131,7 @@ export class Blocks {
eth_getStorageAt (payload, cb) { eth_getStorageAt (payload, cb) {
const [address, position, blockNumber] = payload.params 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)) { if (err || (result.storage && Object.values(result.storage).length === 0)) {
return cb(err, '') return cb(err, '')
} }

@ -1,8 +1,8 @@
export class Debug { export class Debug {
executionContext vmContext
constructor (executionContext) { constructor (vmContext) {
this.executionContext = executionContext this.vmContext = vmContext
} }
methods () { methods () {
@ -14,15 +14,15 @@ export class Debug {
} }
debug_traceTransaction (payload, cb) { 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) { 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) { debug_storageRangeAt (payload, cb) {
this.executionContext.web3().debug.storageRangeAt( this.vmContext.web3().debug.storageRangeAt(
payload.params[0], payload.params[0],
payload.params[1], payload.params[1],
payload.params[2], payload.params[2],

@ -1,8 +1,8 @@
export class Filters { export class Filters {
executionContext vmContext
constructor (executionContext) { constructor (vmContext) {
this.executionContext = executionContext this.vmContext = vmContext
} }
methods () { methods () {
@ -14,49 +14,49 @@ export class Filters {
} }
eth_getLogs (payload, cb) { 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) cb(null, results)
} }
eth_subscribe (payload, cb) { eth_subscribe (payload, cb) {
const subscriptionId = this.executionContext.logsManager.subscribe(payload.params) const subscriptionId = this.vmContext.logsManager.subscribe(payload.params)
cb(null, subscriptionId) cb(null, subscriptionId)
} }
eth_unsubscribe (payload, cb) { eth_unsubscribe (payload, cb) {
this.executionContext.logsManager.unsubscribe(payload.params[0]) this.vmContext.logsManager.unsubscribe(payload.params[0])
cb(null, true) cb(null, true)
} }
eth_newFilter (payload, cb) { 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) cb(null, filterId)
} }
eth_newBlockFilter (payload, cb) { eth_newBlockFilter (payload, cb) {
const filterId = this.executionContext.logsManager.newFilter('block') const filterId = this.vmContext.logsManager.newFilter('block')
cb(null, filterId) cb(null, filterId)
} }
eth_newPendingTransactionFilter (payload, cb) { eth_newPendingTransactionFilter (payload, cb) {
const filterId = this.executionContext.logsManager.newFilter('pendingTransactions') const filterId = this.vmContext.logsManager.newFilter('pendingTransactions')
cb(null, filterId) cb(null, filterId)
} }
eth_uninstallfilter (payload, cb) { 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) cb(null, result)
} }
eth_getFilterChanges (payload, cb) { eth_getFilterChanges (payload, cb) {
const filterId = payload.params[0] const filterId = payload.params[0]
const results = this.executionContext.logsManager.getLogsForFilter(filterId) const results = this.vmContext.logsManager.getLogsForFilter(filterId)
cb(null, results) cb(null, results)
} }
eth_getFilterLogs (payload, cb) { eth_getFilterLogs (payload, cb) {
const filterId = payload.params[0] const filterId = payload.params[0]
const results = this.executionContext.logsManager.getLogsForFilter(filterId, true) const results = this.vmContext.logsManager.getLogsForFilter(filterId, true)
cb(null, results) cb(null, results)
} }
} }

@ -3,11 +3,14 @@ import { toChecksumAddress, BN, Address } from 'ethereumjs-util'
import { processTx } from './txProcess' import { processTx } from './txProcess'
export class Transactions { export class Transactions {
executionContext vmContext
accounts accounts
tags
constructor (executionContext) {
this.executionContext = executionContext constructor (vmContext) {
this.vmContext = vmContext
this.tags = {}
} }
init (accounts) { init (accounts) {
@ -24,7 +27,9 @@ export class Transactions {
eth_getTransactionCount: this.eth_getTransactionCount.bind(this), eth_getTransactionCount: this.eth_getTransactionCount.bind(this),
eth_getTransactionByHash: this.eth_getTransactionByHash.bind(this), eth_getTransactionByHash: this.eth_getTransactionByHash.bind(this),
eth_getTransactionByBlockHashAndIndex: this.eth_getTransactionByBlockHashAndIndex.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 +38,30 @@ export class Transactions {
if (payload.params && payload.params.length > 0 && payload.params[0].from) { if (payload.params && payload.params.length > 0 && payload.params[0].from) {
payload.params[0].from = toChecksumAddress(payload.params[0].from) payload.params[0].from = toChecksumAddress(payload.params[0].from)
} }
processTx(this.executionContext, this.accounts, payload, false, cb) processTx(this.vmContext, this.accounts, 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) { 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) { if (error) {
return cb(error) return cb(error)
} }
const txBlock = this.executionContext.txs[receipt.hash] const txBlock = this.vmContext.txs[receipt.hash]
const r: Record <string, unknown> = { const r: Record <string, unknown> = {
transactionHash: receipt.hash, transactionHash: receipt.hash,
@ -72,7 +91,7 @@ export class Transactions {
eth_getCode (payload, cb) { eth_getCode (payload, cb) {
const address = payload.params[0] const address = payload.params[0]
this.executionContext.web3().eth.getCode(address, (error, result) => { this.vmContext.web3().eth.getCode(address, (error, result) => {
if (error) { if (error) {
console.dir('error getting code') console.dir('error getting code')
console.dir(error) console.dir(error)
@ -92,13 +111,31 @@ export class Transactions {
payload.params[0].value = undefined payload.params[0].value = undefined
processTx(this.executionContext, this.accounts, payload, true, cb) const tag = payload.params[0].timestamp // e2e reference
processTx(this.vmContext, this.accounts, 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) { eth_getTransactionCount (payload, cb) {
const address = payload.params[0] 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) const nonce = new BN(account.nonce).toString(10)
cb(null, nonce) cb(null, nonce)
}).catch((error) => { }).catch((error) => {
@ -109,12 +146,12 @@ export class Transactions {
eth_getTransactionByHash (payload, cb) { eth_getTransactionByHash (payload, cb) {
const address = payload.params[0] const address = payload.params[0]
this.executionContext.web3().eth.getTransactionReceipt(address, (error, receipt) => { this.vmContext.web3().eth.getTransactionReceipt(address, (error, receipt) => {
if (error) { if (error) {
return cb(error) return cb(error)
} }
const txBlock = this.executionContext.txs[receipt.transactionHash] const txBlock = this.vmContext.txs[receipt.transactionHash]
// TODO: params to add later // TODO: params to add later
const r: Record<string, unknown> = { const r: Record<string, unknown> = {
@ -154,10 +191,10 @@ export class Transactions {
eth_getTransactionByBlockHashAndIndex (payload, cb) { eth_getTransactionByBlockHashAndIndex (payload, cb) {
const txIndex = payload.params[1] 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') 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) { if (error) {
return cb(error) return cb(error)
} }
@ -196,10 +233,10 @@ export class Transactions {
eth_getTransactionByBlockNumberAndIndex (payload, cb) { eth_getTransactionByBlockNumberAndIndex (payload, cb) {
const txIndex = payload.params[1] 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') 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) { if (error) {
return cb(error) return cb(error)
} }

@ -1,15 +1,15 @@
import { execution } from '@remix-project/remix-lib' import { execution } from '@remix-project/remix-lib'
const TxExecution = execution.txExecution const TxExecution = execution.txExecution
const TxRunner = execution.txRunner const TxRunnerVM = execution.TxRunnerVM
const TxRunner = execution.TxRunner
function runCall (payload, from, to, data, value, gasLimit, txRunner, callbacks, callback) { function runCall (payload, from, to, data, value, gasLimit, txRunner, callbacks, callback) {
const finalCallback = function (err, result) { const finalCallback = function (err, result) {
if (err) { if (err) {
return callback(err) return callback(err)
} }
const returnValue = result.result.execResult.returnValue.toString('hex') return callback(null, result)
const toReturn = `0x${returnValue || '0'}`
return callback(null, toReturn)
} }
TxExecution.callFunction(from, to, data, value, gasLimit, { constant: true }, txRunner, callbacks, finalCallback) TxExecution.callFunction(from, to, data, value, gasLimit, { constant: true }, txRunner, callbacks, finalCallback)
@ -20,7 +20,7 @@ function runTx (payload, from, to, data, value, gasLimit, txRunner, callbacks, c
if (err) { if (err) {
return callback(err) return callback(err)
} }
callback(null, result.transactionHash) callback(null, result)
} }
TxExecution.callFunction(from, to, data, value, gasLimit, { constant: false }, txRunner, callbacks, finalCallback) TxExecution.callFunction(from, to, data, value, gasLimit, { constant: false }, txRunner, callbacks, finalCallback)
@ -31,15 +31,16 @@ function createContract (payload, from, data, value, gasLimit, txRunner, callbac
if (err) { if (err) {
return callback(err) return callback(err)
} }
callback(null, result.transactionHash) callback(null, result)
} }
TxExecution.createContract(from, data, value, gasLimit, txRunner, callbacks, finalCallback) TxExecution.createContract(from, data, value, gasLimit, txRunner, callbacks, finalCallback)
} }
let txRunnerVMInstance
let txRunnerInstance let txRunnerInstance
export function processTx (executionContext, accounts, payload, isCall, callback) { export function processTx (vmContext, accounts, payload, isCall, callback) {
const api = { const api = {
logMessage: (msg) => { logMessage: (msg) => {
}, },
@ -61,11 +62,11 @@ export function processTx (executionContext, accounts, payload, isCall, callback
} }
} }
executionContext.init(api.config) if (!txRunnerVMInstance) {
txRunnerVMInstance = new TxRunnerVM(accounts, api, _ => vmContext.vm())
// let txRunner = new TxRunner(accounts, api) }
if (!txRunnerInstance) { if (!txRunnerInstance) {
txRunnerInstance = new TxRunner(accounts, api, executionContext) txRunnerInstance = new TxRunner(txRunnerVMInstance, { runAsync: false })
} }
txRunnerInstance.vmaccounts = accounts txRunnerInstance.vmaccounts = accounts
let { from, to, data, value, gas } = payload.params[0] let { from, to, data, value, gas } = payload.params[0]

@ -1,5 +1,4 @@
import { Blocks } from './methods/blocks' import { Blocks } from './methods/blocks'
import { execution } from '@remix-project/remix-lib'
import { info } from './utils/logs' import { info } from './utils/logs'
import merge from 'merge' import merge from 'merge'
@ -11,11 +10,11 @@ import { methods as netMethods } from './methods/net'
import { Transactions } from './methods/transactions' import { Transactions } from './methods/transactions'
import { Debug } from './methods/debug' import { Debug } from './methods/debug'
import { generateBlock } from './genesis' import { generateBlock } from './genesis'
const { executionContext } = execution import { VMContext } from './vm-context'
export class Provider { export class Provider {
options: Record<string, unknown> options: Record<string, unknown>
executionContext vmContext
Accounts Accounts
Transactions Transactions
methods methods
@ -26,23 +25,23 @@ export class Provider {
this.options = options this.options = options
this.host = host this.host = host
this.connected = true this.connected = true
// TODO: init executionContext here this.vmContext = new VMContext()
this.executionContext = executionContext
this.Accounts = new Accounts(this.executionContext) this.Accounts = new Accounts(this.vmContext)
this.Transactions = new Transactions(this.executionContext) this.Transactions = new Transactions(this.vmContext)
this.methods = {} this.methods = {}
this.methods = merge(this.methods, this.Accounts.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, 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, netMethods())
this.methods = merge(this.methods, this.Transactions.methods()) 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 () { async init () {
await generateBlock(this.executionContext) await generateBlock(this.vmContext)
await this.Accounts.resetAccounts() await this.Accounts.resetAccounts()
this.Transactions.init(this.Accounts.accounts) this.Transactions.init(this.Accounts.accounts)
} }
@ -87,6 +86,40 @@ export class Provider {
}; };
on (type, cb) { 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: []
})
}
}

@ -0,0 +1,147 @@
/* global ethereum */
'use strict'
import Web3 from 'web3'
import { rlp, keccak, bufferToHex } from 'ethereumjs-util'
import { vm, execution } from '@remix-project/remix-lib'
const EthJSVM = require('ethereumjs-vm').default
const StateManager = require('ethereumjs-vm/dist/state/stateManager').default
/*
extend vm state manager and instanciate VM
*/
class StateManagerCommonStorageDump extends StateManager {
constructor (arg) {
super(arg)
this.keyHashes = {}
}
putContractStorage (address, key, value, cb) {
this.keyHashes[keccak(key).toString('hex')] = bufferToHex(key)
super.putContractStorage(address, key, value, cb)
}
dumpStorage (address, cb) {
this._getStorageTrie(address, (err, trie) => {
if (err) {
return cb(err)
}
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 () {
cb(storage)
})
})
}
getStateRoot (cb) {
const checkpoint = this._checkpointCount
this._checkpointCount = 0
super.getStateRoot((err, stateRoot) => {
this._checkpointCount = checkpoint
cb(err, stateRoot)
})
}
setStateRoot (stateRoot, cb) {
const checkpoint = this._checkpointCount
this._checkpointCount = 0
super.setStateRoot(stateRoot, (err) => {
this._checkpointCount = checkpoint
cb(err)
})
}
}
/*
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 = 'muirGlacier'
this.vms = {
/*
byzantium: createVm('byzantium'),
constantinople: createVm('constantinople'),
petersburg: createVm('petersburg'),
istanbul: createVm('istanbul'),
*/
muirGlacier: this.createVm('muirGlacier')
}
this.blocks = {}
this.latestBlockNumber = 0
this.txs = {}
this.exeResults = {}
this.logsManager = new execution.LogsManager()
}
createVm (hardfork) {
const stateManager = new StateManagerCommonStorageDump({})
stateManager.checkpoint(() => {})
const ethvm = new EthJSVM({
activatePrecompiles: true,
blockchain: stateManager.blockchain,
stateManager: stateManager,
hardfork: hardfork
})
ethvm.blockchain.validate = false
this.web3vm = new vm.Web3VMProvider()
this.web3vm.setVM(ethvm)
return { vm: ethvm, web3vm: this.web3vm, stateManager }
}
web3 () {
return this.web3vm
}
blankWeb3 () {
return new Web3()
}
vm () {
return this.vms[this.currentFork].vm
}
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.web3vm)
}
trackTx (tx, block) {
this.txs[tx] = block
}
trackExecResult (tx, execReult) {
this.exeResults[tx] = execReult
}
}

@ -1,7 +1,7 @@
module.exports = { module.exports = {
name: 'remix-tests', name: 'remix-tests',
preset: '../../jest.config.js', preset: '../../jest.config.js',
verbose: true, verbose: false,
silent: false, // Silent console messages, specially the 'remix-simulator' ones silent: false, // Silent console messages, specially the 'remix-simulator' ones
transform: { transform: {
'^.+\\.[tj]sx?$': 'ts-jest', '^.+\\.[tj]sx?$': 'ts-jest',
@ -18,6 +18,6 @@ module.exports = {
"!src/types.ts", "!src/types.ts",
"!src/logger.ts" "!src/logger.ts"
], ],
coverageDirectory: '../../coverage/libs/remix-tests', coverageDirectory: '../../coverage/libs/remix-tests'
}; };

@ -79,7 +79,7 @@ export function deployAll (compileResult: compilationInterface, web3: Web3, with
contracts[contractName] = contractObject contracts[contractName] = contractObject
contracts[contractName].filename = filename 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) { }).on('error', function (err) {
console.error(err) console.error(err)
callback(err) callback(err)

@ -3,17 +3,23 @@ import { resolve } from 'path'
describe('testRunner: remix-tests CLI', () => { describe('testRunner: remix-tests CLI', () => {
// remix-tests binary, after build, is used as executable // remix-tests binary, after build, is used as executable
const executablePath = resolve(__dirname + '/../../../dist/libs/remix-tests/bin/remix-tests') const executablePath = resolve(__dirname + '/../../../dist/libs/remix-tests/bin/remix-tests')
const result = spawnSync('ls', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') }) const result = spawnSync('ls', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') })
if(result) { if(result) {
const dirContent = result.stdout.toString() const dirContent = result.stdout.toString()
// Install dependencies if 'node_modules' is not already present // Install dependencies if 'node_modules' is not already present
if(!dirContent.includes('node_modules')) execSync('npm install', { cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') }) if(!dirContent.includes('node_modules')) execSync(
'ln -s ' + __dirname + '/../../../node_modules node_modules',
{ cwd: resolve(__dirname + '/../../../dist/libs/remix-tests') })
} }
describe('test various CLI options', () => { describe('test various CLI options', () => {
test('remix-tests version', () => { test('remix-tests version', () => {
const res = spawnSync(executablePath, ['-V']) const res = spawnSync(executablePath, ['-V'])
console.log(res.stdout.toString())
expect(res.stdout.toString().trim()).toBe(require('../package.json').version) expect(res.stdout.toString().trim()).toBe(require('../package.json').version)
}) })

@ -3,9 +3,9 @@
"compilerOptions": { "compilerOptions": {
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "commonjs", "module": "commonjs",
"esModuleInterop": true,
"allowJs": true, "allowJs": true,
"rootDir": "./", "rootDir": "./",
"esModuleInterop": true
}, },
"include": ["**/*.ts"] "include": ["**/*.ts"]
} }

@ -9,8 +9,7 @@
}, },
"exclude": [ "exclude": [
"**/*.spec.ts", "**/*.spec.ts",
"tests/" "test/"
], ],
"include": ["**/*.ts"] "include": ["**/*.ts"]
} }

@ -13,4 +13,3 @@
"**/*.d.ts" "**/*.d.ts"
] ]
} }
Loading…
Cancel
Save