Merge pull request #2583 from ethereum/refactor_logic6_2

refactor blockchain logic into their own providers - 2
pull/1/head
yann300 5 years ago committed by GitHub
commit dc62bc9f36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 414
      package-lock.json
  2. 13
      src/app/tabs/runTab/contractDropdown.js
  3. 8
      src/app/tabs/runTab/model/recorder.js
  4. 2
      src/app/tabs/runTab/recorder.js
  5. 2
      src/app/tabs/runTab/settings.js
  6. 11
      src/app/ui/modaldialog.js
  7. 19
      src/app/ui/universal-dapp-ui.js
  8. 503
      src/blockchain/blockchain.js
  9. 53
      src/blockchain/providers/injected.js
  10. 62
      src/blockchain/providers/node.js
  11. 96
      src/blockchain/providers/vm.js

414
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -193,7 +193,7 @@ class ContractDropdownUI {
{ {
label: 'Force Send', label: 'Force Send',
fn: () => { fn: () => {
this.blockchain.deployContract(selectedContract, args, contractMetadata, compilerContracts, {continueCb, promptCb, statusCb, finalCb}, confirmationCb) this.deployContract(selectedContract, args, contractMetadata, compilerContracts, {continueCb, promptCb, statusCb, finalCb}, confirmationCb)
}}, { }}, {
label: 'Cancel', label: 'Cancel',
fn: () => { fn: () => {
@ -201,7 +201,16 @@ class ContractDropdownUI {
} }
}) })
} }
this.blockchain.deployContract(selectedContract, args, contractMetadata, compilerContracts, {continueCb, promptCb, statusCb, finalCb}, confirmationCb) this.deployContract(selectedContract, args, contractMetadata, compilerContracts, {continueCb, promptCb, statusCb, finalCb}, confirmationCb)
}
deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
const { statusCb } = callbacks
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
return this.blockchain.deployContractAndLibraries(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
}
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
this.blockchain.deployContractWithLibrary(selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb)
} }
getConfirmationCb (modalDialog, confirmDialog) { getConfirmationCb (modalDialog, confirmDialog) {

@ -67,11 +67,10 @@ class Recorder {
} }
}) })
this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => { this.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload, rawAddress) => {
if (error) return console.log(error) if (error) return console.log(error)
if (call) return if (call) return
const rawAddress = this.blockchain.getAddressFromTransactionResult(txResult)
if (!rawAddress) return // not a contract creation if (!rawAddress) return // not a contract creation
const stringAddress = this.addressToString(rawAddress) const stringAddress = this.addressToString(rawAddress)
const address = ethutil.toChecksumAddress(stringAddress) const address = ethutil.toChecksumAddress(stringAddress)
@ -249,13 +248,12 @@ class Recorder {
logCallBack(`(${index}) data: ${data.data}`) logCallBack(`(${index}) data: ${data.data}`)
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName, timestamp: tx.timestamp } record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName, timestamp: tx.timestamp }
self.blockchain.runTransaction(record, continueCb, promptCb, confirmationCb, self.blockchain.runTx(record, confirmationCb, continueCb, promptCb,
function (err, txResult) { function (err, txResult, rawAddress) {
if (err) { if (err) {
console.error(err) console.error(err)
return logCallBack(err + '. Execution failed at ' + index) return logCallBack(err + '. Execution failed at ' + index)
} }
const rawAddress = self.blockchain.getAddressFromTransactionResult(txResult)
if (rawAddress) { if (rawAddress) {
const stringAddress = self.addressToString(rawAddress) const stringAddress = self.addressToString(rawAddress)
const address = ethutil.toChecksumAddress(stringAddress) const address = ethutil.toChecksumAddress(stringAddress)

@ -89,7 +89,7 @@ class RecorderUI {
modalDialog('Confirm transaction', content, modalDialog('Confirm transaction', content,
{ label: 'Confirm', { label: 'Confirm',
fn: () => { fn: () => {
this.blockchain.udapp.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked) this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
// TODO: check if this is check is still valid given the refactor // TODO: check if this is check is still valid given the refactor
if (!content.gasPriceStatus) { if (!content.gasPriceStatus) {
cancelCb('Given gas price is not correct') cancelCb('Given gas price is not correct')

@ -44,7 +44,7 @@ class SettingsUI {
if (!this.el) return if (!this.el) return
var accounts = $(this.el.querySelector('#txorigin')).children('option') var accounts = $(this.el.querySelector('#txorigin')).children('option')
accounts.each((index, account) => { accounts.each((index, account) => {
this.blockchain.getAccountBalanceForAddress(account.value, (err, balance) => { this.blockchain.getBalanceInEther(account.value, (err, balance) => {
if (err) return if (err) return
account.innerText = helper.shortenAddress(account.value, balance) account.innerText = helper.shortenAddress(account.value, balance)
}) })

@ -1,6 +1,7 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var css = require('./styles/modaldialog-styles') var css = require('./styles/modaldialog-styles')
let incomingModal = false // in case modals are queued, ensure we are not hiding the last one.
module.exports = (title, content, ok, cancel, focusSelector, opts) => { module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true let agreed = true
let footerIsActive = false let footerIsActive = false
@ -9,7 +10,8 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
if (!container) { if (!container) {
document.querySelector('body').appendChild(html(opts)) document.querySelector('body').appendChild(html(opts))
container = document.querySelector(`.modal`) container = document.querySelector(`.modal`)
} incomingModal = false
} else incomingModal = true
var closeDiv = document.getElementById('modal-close') var closeDiv = document.getElementById('modal-close')
if (opts.hideClose) closeDiv.style.display = 'none' if (opts.hideClose) closeDiv.style.display = 'none'
@ -50,13 +52,15 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
function okListener () { function okListener () {
removeEventListener() removeEventListener()
if (ok && ok.fn && agreed) ok.fn() if (ok && ok.fn && agreed) ok.fn()
hide() if (!incomingModal) hide()
incomingModal = false
} }
function cancelListener () { function cancelListener () {
removeEventListener() removeEventListener()
if (cancel && cancel.fn) cancel.fn() if (cancel && cancel.fn) cancel.fn()
hide() if (!incomingModal) hide()
incomingModal = false
} }
function modalKeyEvent (e) { function modalKeyEvent (e) {
@ -81,6 +85,7 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
container.style.display = 'none' container.style.display = 'none'
if (container.parentElement) container.parentElement.removeChild(container) if (container.parentElement) container.parentElement.removeChild(container)
container = null container = null
incomingModal = false
} }
function show () { function show () {

@ -159,8 +159,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
} }
setLLIError('') setLLIError('')
const fallback = self.blockchain.getFallbackInterface(contractABI) const fallback = txHelper.getFallbackInterface(contractABI)
const receive = self.blockchain.getReceiveInterface(contractABI) const receive = txHelper.getReceiveInterface(contractABI)
const args = { const args = {
funABI: fallback || receive, funABI: fallback || receive,
address: address, address: address,
@ -243,7 +243,20 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i
} }
} }
const params = args.funABI.type !== 'fallback' ? inputsValues : '' const params = args.funABI.type !== 'fallback' ? inputsValues : ''
this.blockchain.runOrCallContractMethod(args.contractName, args.contractAbi, args.funABI, inputsValues, args.address, params, lookupOnly, logMsg, this.logCallback, outputCb, callbacksInContext) this.blockchain.runOrCallContractMethod(
args.contractName,
args.contractAbi,
args.funABI,
inputsValues,
args.address,
params,
lookupOnly,
logMsg,
this.logCallback,
outputCb,
callbacksInContext.confirmationCb.bind(callbacksInContext),
callbacksInContext.continueCb.bind(callbacksInContext),
callbacksInContext.promptCb.bind(callbacksInContext))
} }
module.exports = UniversalDAppUI module.exports = UniversalDAppUI

@ -2,22 +2,22 @@ const remixLib = require('remix-lib')
const txFormat = remixLib.execution.txFormat 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 TxRunner = remixLib.execution.txRunner
const Txlistener = remixLib.execution.txListener const Txlistener = remixLib.execution.txListener
const TxRunner = remixLib.execution.txRunner
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
const ethJSUtil = require('ethereumjs-util')
const Personal = require('web3-eth-personal')
const Web3 = require('web3') const Web3 = require('web3')
const async = require('async') const async = require('async')
const { BN, privateToAddress, isValidPrivate, stripHexPrefix, toChecksumAddress } = require('ethereumjs-util')
const crypto = require('crypto')
const { EventEmitter } = require('events') const { EventEmitter } = require('events')
const { resultToRemixTx } = require('./txResultHelper') const { resultToRemixTx } = require('./txResultHelper')
const VMProvider = require('./providers/vm.js')
const InjectedProvider = require('./providers/injected.js')
const NodeProvider = require('./providers/node.js')
class Blockchain { class Blockchain {
// NOTE: the config object will need to be refactored out in remix-lib // NOTE: the config object will need to be refactored out in remix-lib
@ -34,14 +34,14 @@ class Blockchain {
this.executionContext.detectNetwork(cb) this.executionContext.detectNetwork(cb)
}, },
personalMode: () => { personalMode: () => {
return this.executionContext.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
} }
}, this.executionContext) }, this.executionContext)
this.accounts = {}
this.executionContext.event.register('contextChanged', this.resetEnvironment.bind(this)) this.executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
this.networkcallid = 0 this.networkcallid = 0
this.setupEvents() this.setupEvents()
this.setupProviders()
} }
setupEvents () { setupEvents () {
@ -56,34 +56,71 @@ class Blockchain {
this.executionContext.event.register('removeProvider', (name) => { this.executionContext.event.register('removeProvider', (name) => {
this.event.trigger('removeProvider', [name]) this.event.trigger('removeProvider', [name])
}) })
// this.udapp.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
// this.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
// })
// this.udapp.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
// this.event.trigger('transactionExecuted', [error, from, to, data, call, txResult, timestamp])
// })
// this.udapp.event.register('transactionBroadcasted', (txhash, networkName) => {
// this.event.trigger('transactionBroadcasted', [txhash, networkName])
// })
} }
async deployContract (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) { setupProviders () {
const { continueCb, promptCb, statusCb, finalCb } = callbacks this.providers = {}
this.providers.vm = new VMProvider(this.executionContext)
this.providers.injected = new InjectedProvider(this.executionContext)
this.providers.web3 = new NodeProvider(this.executionContext, this.config)
}
const constructor = selectedContract.getConstructorInterface() getCurrentProvider () {
if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) { const provider = this.getProvider()
return txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => { return this.providers[provider]
if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error) }
statusCb(`creation of ${selectedContract.name} pending...`) /** Return the list of accounts */
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) // note: the dual promise/callback is kept for now as it was before
}, statusCb, (data, runTxCallback) => { getAccounts (cb) {
// called for libraries deployment return new Promise((resolve, reject) => {
this.runTransaction(data, continueCb, promptCb, confirmationCb, runTxCallback) this.getCurrentProvider().getAccounts((error, accounts) => {
if (cb) {
return cb(error, accounts)
}
if (error) {
reject(error)
}
resolve(accounts)
}) })
} })
if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`) }
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => {
deployContractAndLibraries (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.buildData(selectedContract.name, selectedContract.object, compilerContracts, true, constructor, args, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error) if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
statusCb(`creation of ${selectedContract.name} pending...`) statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
}, statusCb, (data, runTxCallback) => {
// called for libraries deployment
this.runTx(data, confirmationCb, continueCb, promptCb, runTxCallback)
}) })
} }
runTransaction (data, continueCb, promptCb, confirmationCb, finalCb) { deployContractWithLibrary (selectedContract, args, contractMetadata, compilerContracts, callbacks, confirmationCb) {
this.runTx(data, confirmationCb, continueCb, promptCb, finalCb) const { continueCb, promptCb, statusCb, finalCb } = callbacks
const constructor = selectedContract.getConstructorInterface()
txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => {
if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
statusCb(`creation of ${selectedContract.name} pending...`)
this.createContract(selectedContract, data, continueCb, promptCb, confirmationCb, finalCb)
})
} }
createContract (selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) { createContract (selectedContract, data, continueCb, promptCb, confirmationCb, finalCb) {
@ -93,29 +130,21 @@ class Blockchain {
data.contractABI = selectedContract.abi data.contractABI = selectedContract.abi
} }
this._createContract(data, confirmationCb, continueCb, promptCb, this.runTx({ data: data, useCall: false }, confirmationCb, continueCb, promptCb,
(error, txResult) => { (error, txResult, address) => {
if (error) { if (error) {
return finalCb(`creation of ${selectedContract.name} errored: ${error}`) return finalCb(`creation of ${selectedContract.name} errored: ${error}`)
} }
const isVM = this.executionContext.isVM() if (txResult.result.status && txResult.result.status === '0x0') {
if (isVM) { return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
const vmError = txExecution.checkVMError(txResult) }
if (vmError.error) { finalCb(null, selectedContract, address)
return finalCb(vmError.message) }
} )
}
if (txResult.result.status === false || txResult.result.status === '0x0') {
return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
}
const address = isVM ? txResult.result.createdAddress : txResult.result.contractAddress
finalCb(null, selectedContract, address)
}
)
} }
determineGasPrice (cb) { determineGasPrice (cb) {
this.getGasPrice((error, gasPrice) => { this.getCurrentProvider().getGasPrice((error, gasPrice) => {
const warnMessage = ' Please fix this issue before sending any transaction. ' const warnMessage = ' Please fix this issue before sending any transaction. '
if (error) { if (error) {
return cb('Unable to retrieve the current network gas price.' + warnMessage + error) return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
@ -129,20 +158,10 @@ class Blockchain {
}) })
} }
getGasPrice (cb) {
return this.executionContext.web3().eth.getGasPrice(cb)
}
getFallbackInterface (contractABI) {
return txHelper.getFallbackInterface(contractABI)
}
getReceiveInterface (contractABI) {
return txHelper.getReceiveInterface(contractABI)
}
getInputs (funABI) { getInputs (funABI) {
if (!funABI.inputs) return '' if (!funABI.inputs) {
return ''
}
return txHelper.inputParametersDeclarationToString(funABI.inputs) return txHelper.inputParametersDeclarationToString(funABI.inputs)
} }
@ -180,10 +199,6 @@ class Blockchain {
return determineGasFeesCb return determineGasFeesCb
} }
getAddressFromTransactionResult (txResult) {
return this.executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
}
changeExecutionContext (context, confirmCb, infoCb, cb) { changeExecutionContext (context, confirmCb, infoCb, cb) {
return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb) return this.executionContext.executionContextChange(context, null, confirmCb, infoCb, cb)
} }
@ -192,14 +207,6 @@ class Blockchain {
return this.executionContext.setProviderFromEndpoint(target, context, cb) return this.executionContext.setProviderFromEndpoint(target, context, cb)
} }
getProvider () {
return this.executionContext.getProvider()
}
getAccountBalanceForAddress (address, cb) {
return this.getBalanceInEther(address, cb)
}
updateNetwork (cb) { updateNetwork (cb) {
this.networkcallid++ this.networkcallid++
((callid) => { ((callid) => {
@ -218,53 +225,22 @@ class Blockchain {
return this.executionContext.detectNetwork(cb) return this.executionContext.detectNetwork(cb)
} }
getProvider () {
return this.executionContext.getProvider()
}
isWeb3Provider () { isWeb3Provider () {
const isVM = this.executionContext.isVM() const isVM = this.getProvider() === 'vm'
const isInjected = this.executionContext.getProvider() === 'injected' const isInjected = this.getProvider() === 'injected'
return (!isVM && !isInjected) return (!isVM && !isInjected)
} }
isInjectedWeb3 () { isInjectedWeb3 () {
return this.executionContext.getProvider() === 'injected' return this.getProvider() === 'injected'
} }
signMessage (message, account, passphrase, cb) { signMessage (message, account, passphrase, cb) {
const isVM = this.executionContext.isVM() this.getCurrentProvider().signMessage(message, account, passphrase, cb)
const isInjected = this.executionContext.getProvider() === 'injected'
if (isVM) {
const personalMsg = ethJSUtil.hashPersonalMessage(Buffer.from(message))
const privKey = this.accounts[account].privateKey
try {
const rsv = ethJSUtil.ecsign(personalMsg, privKey)
const signedData = ethJSUtil.toRpcSig(rsv.v, rsv.r, rsv.s)
cb(null, '0x' + personalMsg.toString('hex'), signedData)
} catch (e) {
cb(e.message)
}
return
}
if (isInjected) {
const hashedMsg = Web3.utils.sha3(message)
try {
this.executionContext.web3().eth.sign(account, hashedMsg, (error, signedData) => {
cb(error.message, hashedMsg, signedData)
})
} catch (e) {
cb(e.message)
}
return
}
const hashedMsg = Web3.utils.sha3(message)
try {
const personal = new Personal(this.executionContext.web3().currentProvider)
personal.sign(hashedMsg, account, passphrase, (error, signedData) => {
cb(error.message, hashedMsg, signedData)
})
} catch (e) {
cb(e.message)
}
} }
web3 () { web3 () {
@ -273,55 +249,53 @@ class Blockchain {
getTxListener (opts) { getTxListener (opts) {
opts.event = { opts.event = {
// udapp: this.udapp.event
udapp: this.event udapp: this.event
} }
const txlistener = new Txlistener(opts, this.executionContext) const txlistener = new Txlistener(opts, this.executionContext)
return txlistener return txlistener
} }
runOrCallContractMethod (contractName, contractABI, funABI, value, address, params, lookupOnly, logMsg, logCallback, outputCb, callbacksInContext) { runOrCallContractMethod (contractName, contractAbi, funABI, value, address, callType, lookupOnly, logMsg, logCallback, outputCb, confirmationCb, continueCb, promptCb) {
// contractsDetails is used to resolve libraries // contractsDetails is used to resolve libraries
txFormat.buildData(contractName, contractABI, {}, false, funABI, params, (error, data) => { txFormat.buildData(contractName, contractAbi, {}, false, funABI, callType, (error, data) => {
if (!error) { if (error) {
if (!lookupOnly) { return logCallback(`${logMsg} errored: ${error} `)
logCallback(`${logMsg} pending ... `) }
} else { if (!lookupOnly) {
logCallback(`${logMsg}`) logCallback(`${logMsg} pending ... `)
}
if (funABI.type === 'fallback') data.dataHex = value
this.callFunction(address, data, funABI, callbacksInContext.confirmationCb.bind(callbacksInContext), callbacksInContext.continueCb.bind(callbacksInContext), callbacksInContext.promptCb.bind(callbacksInContext), (error, txResult) => {
if (!error) {
const isVM = this.executionContext.isVM()
if (isVM) {
const vmError = txExecution.checkVMError(txResult)
if (vmError.error) {
logCallback(`${logMsg} errored: ${vmError.message} `)
return
}
}
if (lookupOnly) {
const returnValue = (this.executionContext.isVM() ? txResult.result.execResult.returnValue : ethJSUtil.toBuffer(txResult.result))
outputCb(returnValue)
}
} else {
logCallback(`${logMsg} errored: ${error} `)
}
})
} else { } else {
logCallback(`${logMsg} errored: ${error} `) logCallback(`${logMsg}`)
} }
}, (msg) => { if (funABI.type === 'fallback') data.dataHex = value
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({to: address, data, useCall}, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) {
return logCallback(`${logMsg} errored: ${error} `)
}
if (lookupOnly) {
outputCb(returnValue)
}
})
},
(msg) => {
logCallback(msg) logCallback(msg)
}, (data, runTxCallback) => { },
(data, runTxCallback) => {
// called for libraries deployment // called for libraries deployment
this.runTx(data, callbacksInContext.confirmationCb.bind(callbacksInContext), runTxCallback) this.runTx(data, confirmationCb, runTxCallback, promptCb, () => {})
}) })
} }
context () {
return (this.executionContext.isVM() ? 'memory' : 'blockchain')
}
// NOTE: the config is only needed because exectuionContext.init does // NOTE: the config is only needed because exectuionContext.init does
// if config.get('settings/always-use-vm'), we can simplify this later // if config.get('settings/always-use-vm'), we can simplify this later
resetAndInit (config, transactionContext) { resetAndInit (config, transactionContextAPI) {
this.resetAPI(transactionContext) this.transactionContextAPI = transactionContextAPI
this.executionContext.init(config) this.executionContext.init(config)
this.executionContext.stopListenOnLastBlock() this.executionContext.stopListenOnLastBlock()
this.executionContext.listenOnLastBlock() this.executionContext.listenOnLastBlock()
@ -345,16 +319,9 @@ class Blockchain {
} }
resetEnvironment () { resetEnvironment () {
this.accounts = {} this.getCurrentProvider().resetEnvironment()
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 // TODO: most params here can be refactored away in txRunner
this.txRunner = new TxRunner(this.accounts, { this.txRunner = new TxRunner(this.providers.vm.accounts, {
// TODO: only used to check value of doNotShowTransactionConfirmationAgain property // TODO: only used to check value of doNotShowTransactionConfirmationAgain property
config: this.config, config: this.config,
// TODO: to refactor, TxRunner already has access to executionContext // TODO: to refactor, TxRunner already has access to executionContext
@ -362,7 +329,7 @@ class Blockchain {
this.executionContext.detectNetwork(cb) this.executionContext.detectNetwork(cb)
}, },
personalMode: () => { personalMode: () => {
return this.executionContext.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false return this.getProvider() === 'web3' ? this.config.get('settings/personal-mode') : false
} }
}, this.executionContext) }, this.executionContext)
this.txRunner.event.register('transactionBroadcasted', (txhash) => { this.txRunner.event.register('transactionBroadcasted', (txhash) => {
@ -373,184 +340,30 @@ class Blockchain {
}) })
} }
resetAPI (transactionContextAPI) {
this.transactionContextAPI = transactionContextAPI
}
/** /**
* Create a VM Account * Create a VM Account
* @param {{privateKey: string, balance: string}} newAccount The new account to create * @param {{privateKey: string, balance: string}} newAccount The new account to create
*/ */
createVMAccount (newAccount) { createVMAccount (newAccount) {
const { privateKey, balance } = newAccount if (this.getProvider() !== 'vm') {
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') throw new Error('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
} }
this._addAccount(privateKey, balance) return this.providers.vm.createVMAccount(newAccount)
const privKey = Buffer.from(privateKey, 'hex')
return '0x' + privateToAddress(privKey).toString('hex')
} }
newAccount (_password, passwordPromptCb, cb) { newAccount (_password, passwordPromptCb, cb) {
if (!this.executionContext.isVM()) { return this.getCurrentProvider().newAccount(passwordPromptCb, cb)
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
this.executionContext.web3().personal.newAccount(passphrase, cb)
})
} else {
let privateKey
do {
privateKey = crypto.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) {
privateKey = Buffer.from(privateKey, 'hex')
const address = privateToAddress(privateKey)
// FIXME: we don't care about the callback, but we should still make this proper
let stateManager = this.executionContext.vm().stateManager
stateManager.getAccount(address, (error, account) => {
if (error) return console.log(error)
account.balance = balance || '0xf00000000000000001'
stateManager.putAccount(address, account, (error) => {
if (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('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) {
address = stripHexPrefix(address)
if (!this.executionContext.isVM()) {
this.executionContext.web3().eth.getBalance(address, (err, res) => {
if (err) {
cb(err)
} else {
cb(null, res.toString(10))
}
})
} else {
if (!this.accounts) {
return cb('No accounts?')
}
this.executionContext.vm().stateManager.getAccount(Buffer.from(address, 'hex'), (err, res) => {
if (err) {
cb('Account not found')
} else {
cb(null, new BN(res.balance).toString(10))
}
})
}
} }
/** Get the balance of an address, and convert wei to ether */ /** Get the balance of an address, and convert wei to ether */
getBalanceInEther (address, callback) { getBalanceInEther (address, cb) {
this.getBalance(address, (error, balance) => { this.getCurrentProvider().getBalanceInEther(address, cb)
if (error) {
callback(error)
} else {
// callback(null, this.executionContext.web3().fromWei(balance, 'ether'))
callback(null, Web3.utils.fromWei(balance.toString(10), 'ether'))
}
})
} }
pendingTransactionsCount () { pendingTransactionsCount () {
return Object.keys(this.txRunner.pendingTxs).length 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, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
/**
* 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, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
}
context () {
return (this.executionContext.isVM() ? 'memory' : 'blockchain')
}
/** /**
* This function send a tx only to javascript VM or testnet, will return an error for the mainnet * This function send a tx only to javascript VM or testnet, will return an error for the mainnet
* SHOULD BE TAKEN CAREFULLY! * SHOULD BE TAKEN CAREFULLY!
@ -564,35 +377,25 @@ class Blockchain {
if (network.name === 'Main' && network.id === '1') { if (network.name === 'Main' && network.id === '1') {
return reject(new Error('It is not allowed to make this action against mainnet')) return reject(new Error('It is not allowed to make this action against mainnet'))
} }
this.silentRunTx(tx, (error, result) => {
if (error) return reject(error) this.txRunner.rawRun(
try { tx,
resolve(resultToRemixTx(result)) (network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
} catch (e) { (error, continueTxExecution, cancelCb) => { if (error) { reject(error) } else { continueTxExecution() } },
reject(e) (okCb, cancelCb) => { okCb() },
(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) { runTx (args, confirmationCb, continueCb, promptCb, cb) {
const self = this const self = this
async.waterfall([ async.waterfall([
@ -627,7 +430,7 @@ class Blockchain {
if (err) return next(err) if (err) return next(err)
if (!address) return next('No accounts available') if (!address) return next('No accounts available')
if (self.executionContext.isVM() && !self.accounts[address]) { if (self.executionContext.isVM() && !self.providers.vm.accounts[address]) {
return next('Invalid account selected') return next('Invalid account selected')
} }
next(null, address, value, gasLimit) next(null, address, value, gasLimit)
@ -644,8 +447,11 @@ class Blockchain {
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) { function (error, result) {
if (error) return next(error)
const rawAddress = self.executionContext.isVM() ? result.result.createdAddress : result.result.contractAddress
let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted') let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad]) self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad, rawAddress])
if (error && (typeof (error) !== 'string')) { if (error && (typeof (error) !== 'string')) {
if (error.message) error = error.message if (error.message) error = error.message
@ -657,7 +463,30 @@ class Blockchain {
} }
) )
} }
], cb) ],
(error, txResult) => {
if (error) {
return cb(error)
}
const isVM = this.executionContext.isVM()
if (isVM) {
const vmError = txExecution.checkVMError(txResult)
if (vmError.error) {
return cb(vmError.message)
}
}
let address = null
let returnValue = null
if (txResult && txResult.result) {
address = isVM ? txResult.result.createdAddress : 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 : ''
}
cb(error, txResult, address, returnValue)
})
} }
} }

@ -0,0 +1,53 @@
const Web3 = require('web3')
const { stripHexPrefix } = require('ethereumjs-util')
class InjectedProvider {
constructor (executionContext) {
this.executionContext = executionContext
}
getAccounts (cb) {
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
passwordPromptCb((passphrase) => {
this.executionContext.web3().personal.newAccount(passphrase, cb)
})
}
resetEnvironment () {
}
getBalanceInEther (address, cb) {
address = stripHexPrefix(address)
this.executionContext.web3().eth.getBalance(address, (err, res) => {
if (err) {
return cb(err)
}
cb(null, Web3.utils.fromWei(res.toString(10), 'ether'))
})
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, _passphrase, cb) {
const hashedMsg = Web3.utils.sha3(message)
try {
this.executionContext.web3().eth.sign(hashedMsg, account, (error, signedData) => {
cb(error, hashedMsg, signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return 'injected'
}
}
module.exports = InjectedProvider

@ -0,0 +1,62 @@
const Web3 = require('web3')
const { stripHexPrefix } = require('ethereumjs-util')
const Personal = require('web3-eth-personal')
class NodeProvider {
constructor (executionContext, config) {
this.executionContext = executionContext
this.config = config
}
getAccounts (cb) {
if (this.config.get('settings/personal-mode')) {
return this.executionContext.web3().personal.getListAccounts(cb)
}
return this.executionContext.web3().eth.getAccounts(cb)
}
newAccount (passwordPromptCb, cb) {
if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode')
}
passwordPromptCb((passphrase) => {
this.executionContext.web3().personal.newAccount(passphrase, cb)
})
}
resetEnvironment () {
}
getBalanceInEther (address, cb) {
address = stripHexPrefix(address)
this.executionContext.web3().eth.getBalance(address, (err, res) => {
if (err) {
return cb(err)
}
cb(null, Web3.utils.fromWei(res.toString(10), 'ether'))
})
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, passphrase, cb) {
const hashedMsg = Web3.utils.sha3(message)
try {
const personal = new Personal(this.executionContext.web3().currentProvider)
personal.sign(hashedMsg, account, passphrase, (error, signedData) => {
cb(error, hashedMsg, signedData)
})
} catch (e) {
cb(e.message)
}
}
getProvider () {
return this.executionContext.getProvider()
}
}
module.exports = NodeProvider

@ -0,0 +1,96 @@
const Web3 = require('web3')
const { BN, privateToAddress, toChecksumAddress, isValidPrivate, stripHexPrefix } = require('ethereumjs-util')
const crypto = require('crypto')
const ethJSUtil = require('ethereumjs-util')
class VMProvider {
constructor (executionContext) {
this.executionContext = executionContext
this.accounts = {}
}
getAccounts (cb) {
if (!this.accounts) {
cb('No accounts?')
return cb('No accounts?')
}
return cb(null, Object.keys(this.accounts))
}
resetEnvironment () {
this.accounts = {}
this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511', '0x56BC75E2D63100000')
this._addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c', '0x56BC75E2D63100000')
this._addAccount('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', '0x56BC75E2D63100000')
this._addAccount('d74aa6d18aa79a05f3473dd030a97d3305737cbc8337d940344345c1f6b72eea', '0x56BC75E2D63100000')
this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
}
/** Add an account to the list of account (only for Javascript VM) */
_addAccount (privateKey, balance) {
privateKey = Buffer.from(privateKey, 'hex')
const address = privateToAddress(privateKey)
// FIXME: we don't care about the callback, but we should still make this proper
let stateManager = this.executionContext.vm().stateManager
stateManager.getAccount(address, (error, account) => {
if (error) return console.log(error)
account.balance = balance || '0xf00000000000000001'
stateManager.putAccount(address, account, (error) => {
if (error) console.log(error)
})
})
this.accounts[toChecksumAddress('0x' + address.toString('hex'))] = { privateKey, nonce: 0 }
}
createVMAccount (newAccount) {
const { privateKey, balance } = newAccount
this._addAccount(privateKey, balance)
const privKey = Buffer.from(privateKey, 'hex')
return '0x' + privateToAddress(privKey).toString('hex')
}
newAccount (_passwordPromptCb, cb) {
let privateKey
do {
privateKey = crypto.randomBytes(32)
} while (!isValidPrivate(privateKey))
this._addAccount(privateKey, '0x56BC75E2D63100000')
return cb(null, '0x' + privateToAddress(privateKey).toString('hex'))
}
getBalanceInEther (address, cb) {
address = stripHexPrefix(address)
this.executionContext.vm().stateManager.getAccount(Buffer.from(address, 'hex'), (err, res) => {
if (err) {
return cb('Account not found')
}
cb(null, Web3.utils.fromWei(new BN(res.balance).toString(10), 'ether'))
})
}
getGasPrice (cb) {
this.executionContext.web3().eth.getGasPrice(cb)
}
signMessage (message, account, _passphrase, cb) {
const personalMsg = ethJSUtil.hashPersonalMessage(Buffer.from(message))
const privKey = this.accounts[account].privateKey
try {
const rsv = ethJSUtil.ecsign(personalMsg, privKey)
const signedData = ethJSUtil.toRpcSig(rsv.v, rsv.r, rsv.s)
cb(null, '0x' + personalMsg.toString('hex'), signedData)
} catch (e) {
cb(e.message)
}
}
getProvider () {
return 'vm'
}
}
module.exports = VMProvider
Loading…
Cancel
Save