const util = require('../util') const uiutil = require('../helpers/uiHelper') const ethutil = require('ethereumjs-util') const Web3 = require('web3') function web3VmProvider () { this.web3 = new Web3() this.vm = null this.vmTraces = {} this.txs = {} this.txsReceipt = {} this.processingHash = null this.processingAddress = null this.processingIndex = null this.previousDepth = 0 this.incr = 0 this.eth = {} this.debug = {} this.eth.getCode = (...args) => this.getCode(...args) this.eth.getTransaction = (...args) => this.getTransaction(...args) this.eth.getTransactionReceipt = (...args) => this.getTransactionReceipt(...args) this.eth.getTransactionFromBlock = (...args) => this.getTransactionFromBlock(...args) this.eth.getBlockNumber = (...args) => this.getBlockNumber(...args) this.debug.traceTransaction = (...args) => this.traceTransaction(...args) this.debug.storageRangeAt = (...args) => this.storageRangeAt(...args) this.debug.preimage = (...args) => this.preimage(...args) this.providers = { 'HttpProvider': function (url) {} } this.currentProvider = { 'host': 'vm provider' } this.storageCache = {} this.lastProcessedStorageTxHash = {} this.sha3Preimages = {} // util this.sha3 = (...args) => this.web3.utils.sha3(...args) this.toHex = (...args) => this.web3.utils.toHex(...args) this.toAscii = (...args) => this.web3.utils.hexToAscii(...args) this.fromAscii = (...args) => this.web3.utils.asciiToHex(...args) this.fromDecimal = (...args) => this.web3.utils.numberToHex(...args) this.fromWei = (...args) => this.web3.utils.fromWei(...args) this.toWei = (...args) => this.web3.utils.toWei(...args) this.toBigNumber = (...args) => this.web3.utils.toBN(...args) this.isAddress = (...args) => this.web3.utils.isAddress(...args) this.utils = Web3.utils || [] } web3VmProvider.prototype.setVM = function (vm) { if (this.vm === vm) return this.vm = vm this.vm.on('step', (data) => { this.pushTrace(this, data) }) this.vm.on('afterTx', (data) => { this.txProcessed(this, data) }) this.vm.on('beforeTx', (data) => { this.txWillProcess(this, data) }) } web3VmProvider.prototype.releaseCurrentHash = function () { const ret = this.processingHash this.processingHash = undefined return ret } web3VmProvider.prototype.txWillProcess = function (self, data) { self.incr++ self.processingHash = util.hexConvert(data.hash()) self.vmTraces[self.processingHash] = { gas: '0x0', return: '0x0', structLogs: [] } let tx = {} tx.hash = self.processingHash tx.from = ethutil.toChecksumAddress(util.hexConvert(data.getSenderAddress())) if (data.to && data.to.length) { tx.to = ethutil.toChecksumAddress(util.hexConvert(data.to)) } this.processingAddress = tx.to tx.data = util.hexConvert(data.data) tx.input = util.hexConvert(data.input) tx.gas = (new ethutil.BN(util.hexConvert(data.gas).replace('0x', ''), 16)).toString(10) if (data.value) { tx.value = util.hexConvert(data.value) } self.txs[self.processingHash] = tx self.txsReceipt[self.processingHash] = tx self.storageCache[self.processingHash] = {} if (tx.to) { const account = ethutil.toBuffer(tx.to) self.vm.stateManager.dumpStorage(account, (storage) => { self.storageCache[self.processingHash][tx.to] = storage self.lastProcessedStorageTxHash[tx.to] = self.processingHash }) } this.processingIndex = 0 } web3VmProvider.prototype.txProcessed = function (self, data) { const lastOp = self.vmTraces[self.processingHash].structLogs[self.processingIndex - 1] if (lastOp) { lastOp.error = lastOp.op !== 'RETURN' && lastOp.op !== 'STOP' && lastOp.op !== 'SELFDESTRUCT' } self.vmTraces[self.processingHash].gas = '0x' + data.gasUsed.toString(16) const logs = [] for (let l in data.execResult.logs) { const log = data.execResult.logs[l] const topics = [] if (log[1].length > 0) { for (var k in log[1]) { topics.push('0x' + log[1][k].toString('hex')) } } else { topics.push('0x') } logs.push({ address: '0x' + log[0].toString('hex'), data: '0x' + log[2].toString('hex'), topics: topics, rawVMResponse: log }) } self.txsReceipt[self.processingHash].logs = logs self.txsReceipt[self.processingHash].transactionHash = self.processingHash const status = data.execResult.exceptionError ? 0 : 1 self.txsReceipt[self.processingHash].status = `0x${status}` if (data.createdAddress) { const address = util.hexConvert(data.createdAddress) self.vmTraces[self.processingHash].return = ethutil.toChecksumAddress(address) self.txsReceipt[self.processingHash].contractAddress = ethutil.toChecksumAddress(address) } else if (data.execResult.returnValue) { self.vmTraces[self.processingHash].return = util.hexConvert(data.execResult.returnValue) } else { self.vmTraces[self.processingHash].return = '0x' } this.processingIndex = null this.processingAddress = null this.previousDepth = 0 } web3VmProvider.prototype.pushTrace = function (self, data) { const depth = data.depth + 1 // geth starts the depth from 1 if (!self.processingHash) { console.log('no tx processing') return } let previousopcode if (self.vmTraces[self.processingHash] && self.vmTraces[self.processingHash].structLogs[this.processingIndex - 1]) { previousopcode = self.vmTraces[self.processingHash].structLogs[this.processingIndex - 1] } if (this.previousDepth > depth && previousopcode) { // returning from context, set error it is not STOP, RETURN previousopcode.invalidDepthChange = previousopcode.op !== 'RETURN' && previousopcode.op !== 'STOP' } const step = { stack: util.hexListFromBNs(data.stack), memory: util.formatMemory(data.memory), storage: data.storage, op: data.opcode.name, pc: data.pc, gasCost: data.opcode.fee.toString(), gas: data.gasLeft.toString(), depth: depth, error: data.error === false ? undefined : data.error } self.vmTraces[self.processingHash].structLogs.push(step) if (step.op === 'CREATE' || step.op === 'CALL') { if (step.op === 'CREATE') { this.processingAddress = '(Contract Creation - Step ' + this.processingIndex + ')' this.storageCache[this.processingHash][this.processingAddress] = {} this.lastProcessedStorageTxHash[this.processingAddress] = this.processingHash } else { this.processingAddress = uiutil.normalizeHexAddress(step.stack[step.stack.length - 2]) this.processingAddress = ethutil.toChecksumAddress(this.processingAddress) if (!self.storageCache[self.processingHash][this.processingAddress]) { const account = ethutil.toBuffer(this.processingAddress) self.vm.stateManager.dumpStorage(account, function (storage) { self.storageCache[self.processingHash][self.processingAddress] = storage self.lastProcessedStorageTxHash[self.processingAddress] = self.processingHash }) } } } if (previousopcode && previousopcode.op === 'SHA3') { const preimage = getSha3Input(previousopcode.stack, previousopcode.memory) const imageHash = step.stack[step.stack.length - 1].replace('0x', '') self.sha3Preimages[imageHash] = { 'preimage': preimage } } this.processingIndex++ this.previousDepth = depth } web3VmProvider.prototype.getCode = function (address, cb) { address = ethutil.toChecksumAddress(address) const account = ethutil.toBuffer(address) this.vm.stateManager.getContractCode(account, (error, result) => { cb(error, util.hexConvert(result)) }) } web3VmProvider.prototype.setProvider = function (provider) {} web3VmProvider.prototype.traceTransaction = function (txHash, options, cb) { if (this.vmTraces[txHash]) { if (cb) { cb(null, this.vmTraces[txHash]) } return this.vmTraces[txHash] } if (cb) { cb('unable to retrieve traces ' + txHash, null) } } web3VmProvider.prototype.storageRangeAt = function (blockNumber, txIndex, address, start, maxLength, cb) { // txIndex is the hash in the case of the VM // we don't use the range params here address = ethutil.toChecksumAddress(address) if (txIndex === 'latest') { txIndex = this.lastProcessedStorageTxHash[address] } if (this.storageCache[txIndex] && this.storageCache[txIndex][address]) { const storage = this.storageCache[txIndex][address] return cb(null, { storage: JSON.parse(JSON.stringify(storage)), nextKey: null }) } cb('unable to retrieve storage ' + txIndex + ' ' + address) } web3VmProvider.prototype.getBlockNumber = function (cb) { cb(null, 'vm provider') } web3VmProvider.prototype.getTransaction = function (txHash, cb) { if (this.txs[txHash]) { if (cb) { cb(null, this.txs[txHash]) } return this.txs[txHash] } if (cb) { cb('unable to retrieve tx ' + txHash, null) } } web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) { // same as getTransaction but return the created address also if (this.txsReceipt[txHash]) { if (cb) { cb(null, this.txsReceipt[txHash]) } return this.txsReceipt[txHash] } if (cb) { cb('unable to retrieve txReceipt ' + txHash, null) } } web3VmProvider.prototype.getTransactionFromBlock = function (blockNumber, txIndex, cb) { const mes = 'not supposed to be needed by remix in vmmode' console.log(mes) if (cb) { cb(mes, null) } } web3VmProvider.prototype.preimage = function (hashedKey, cb) { hashedKey = hashedKey.replace('0x', '') cb(null, this.sha3Preimages[hashedKey] !== undefined ? this.sha3Preimages[hashedKey].preimage : null) } function getSha3Input (stack, memory) { let memoryStart = stack[stack.length - 1] let memoryLength = stack[stack.length - 2] const memStartDec = (new ethutil.BN(memoryStart.replace('0x', ''), 16)).toString(10) memoryStart = parseInt(memStartDec) * 2 const memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) memoryLength = parseInt(memLengthDec) * 2 let i = Math.floor(memoryStart / 32) const maxIndex = Math.floor(memoryLength / 32) + i if (!memory[i]) { return emptyFill(memoryLength) } let sha3Input = memory[i].slice(memoryStart - 32 * i) i++ while (i < maxIndex) { sha3Input += memory[i] ? memory[i] : emptyFill(32) i++ } if (sha3Input.length < memoryLength) { const leftSize = memoryLength - sha3Input.length sha3Input += memory[i] ? memory[i].slice(0, leftSize) : emptyFill(leftSize) } return sha3Input } function emptyFill (size) { return (new Array(size)).join('0') } module.exports = web3VmProvider