From 8b76861318b402923c77b2f55f64be54734a8cc9 Mon Sep 17 00:00:00 2001 From: cdetrio Date: Tue, 2 May 2017 12:18:08 -0400 Subject: [PATCH 01/11] initial support for mapping types --- src/helpers/traceHelper.js | 4 ++ src/solidity/decodeInfo.js | 19 +++++++-- src/solidity/types/Mapping.js | 52 ++++++++++++++++++++++-- src/solidity/types/StringType.js | 2 +- src/trace/traceAnalyser.js | 16 ++++++++ src/trace/traceCache.js | 14 +++++++ src/ui/SolidityState.js | 68 ++++++++++++++++++++++++++++++-- src/ui/SolidityTypeFormatter.js | 9 ++++- 8 files changed, 170 insertions(+), 14 deletions(-) diff --git a/src/helpers/traceHelper.js b/src/helpers/traceHelper.js index 8d2a560d3..6e66c6cbd 100644 --- a/src/helpers/traceHelper.js +++ b/src/helpers/traceHelper.js @@ -37,6 +37,10 @@ module.exports = { return step.op === 'SSTORE' }, + isSHA3Instruction: function (step) { + return step.op === 'SHA3' + }, + newContextStorage: function (step) { return step.op === 'CREATE' || step.op === 'CALL' }, diff --git a/src/solidity/decodeInfo.js b/src/solidity/decodeInfo.js index aaee05cd7..4dbd1d341 100644 --- a/src/solidity/decodeInfo.js +++ b/src/solidity/decodeInfo.js @@ -19,8 +19,19 @@ var util = require('./types/util') * @param {String} type - type given by the AST * @return {Object} returns decoded info about the current type: { storageBytes, typeName} */ -function mapping (type) { - return new MappingType() +function mapping (type, stateDefinitions, contractName) { + var match = type.match(/mapping\((.*?)( =>)? (.*)\)$/) + var keyTypeName = match[1] + var valueTypeName = match[3] + + var keyType = parseType(keyTypeName, stateDefinitions, contractName, 'storage') + var valueType = parseType(valueTypeName, stateDefinitions, contractName, 'storage') + + var underlyingTypes = { + 'keyType': keyType, + 'valueType': valueType + } + return new MappingType(underlyingTypes, 'location', util.removeLocation(type)) } /** @@ -179,7 +190,7 @@ function struct (type, stateDefinitions, contractName, location) { if (!location) { location = match[2].trim() } - var memberDetails = getStructMembers(match[1], stateDefinitions, contractName, location) // type is used to extract the ast struct definition + var memberDetails = getStructMembers(match[1], stateDefinitions, contractName) // type is used to extract the ast struct definition if (!memberDetails) return null return new StructType(memberDetails, location, match[1]) } else { @@ -222,7 +233,7 @@ function getEnum (type, stateDefinitions, contractName) { * @param {String} location - location of the data (storage ref| storage pointer| memory| calldata) * @return {Array} containing all members of the current struct type */ -function getStructMembers (type, stateDefinitions, contractName, location) { +function getStructMembers (type, stateDefinitions, contractName) { var split = type.split('.') if (!split.length) { type = contractName + '.' + type diff --git a/src/solidity/types/Mapping.js b/src/solidity/types/Mapping.js index e61df4720..0e62012af 100644 --- a/src/solidity/types/Mapping.js +++ b/src/solidity/types/Mapping.js @@ -1,20 +1,41 @@ 'use strict' var RefType = require('./RefType') +var ethutil = require('ethereumjs-util') class Mapping extends RefType { - constructor () { - super(1, 32, 'mapping', 'storage') + constructor (underlyingTypes, location, fullType) { + super(1, 32, fullType, 'storage') + this.keyType = underlyingTypes.keyType + this.valueType = underlyingTypes.valueType + } + + setMappingElements (mappingKeyPreimages) { + this.preimages = mappingKeyPreimages } async decodeFromStorage (location, storageResolver) { + // location.offset should always be 0 for a mapping (?? double check) + + var ret = {} + for (var i in this.preimages) { + var preimage = this.preimages[i] + var mapLocation = getMappingLocation(preimage, location.slot) + var globalLocation = { + offset: location.offset, + slot: mapLocation + } + ret[preimage] = await this.valueType.decodeFromStorage(globalLocation, storageResolver) + } + return { - value: '', - length: '0x', + value: ret, type: this.typeName } } decodeFromMemoryInternal (offset, memory) { + // mappings can only exist in storage and not in memory + // so this should never be called return { value: '', length: '0x', @@ -23,4 +44,27 @@ class Mapping extends RefType { } } +function getMappingLocation (key, position) { + // mapping storage location decribed at http://solidity.readthedocs.io/en/develop/miscellaneous.html#layout-of-state-variables-in-storage + // > the value corresponding to a mapping key k is located at keccak256(k . p) where . is concatenation. + + // key should be a hex string, and position an int + var mappingK = ethutil.toBuffer('0x' + key) + mappingK = ethutil.setLengthLeft(mappingK, 32) + var mappingP = ethutil.intToBuffer(position) + mappingP = ethutil.setLengthLeft(mappingP, 32) + var mappingKeyBuf = concatTypedArrays(mappingK, mappingP) + var mappingKeyPreimage = '0x' + mappingKeyBuf.toString('hex') + var mappingStorageLocation = ethutil.sha3(mappingKeyPreimage) + mappingStorageLocation = new ethutil.BN(mappingStorageLocation, 16) + return mappingStorageLocation +} + +function concatTypedArrays (a, b) { // a, b TypedArray of same type + let c = new (a.constructor)(a.length + b.length) + c.set(a, 0) + c.set(b, a.length) + return c +} + module.exports = Mapping diff --git a/src/solidity/types/StringType.js b/src/solidity/types/StringType.js index f21eac4a5..a74b6745a 100644 --- a/src/solidity/types/StringType.js +++ b/src/solidity/types/StringType.js @@ -40,7 +40,7 @@ function format (decoded) { var value = decoded.value value = value.replace('0x', '').replace(/(..)/g, '%$1') var ret = { - length: decoded.length, + // length: decoded.length, // unneeded, only dynamicBytes uses length raw: decoded.value, type: 'string' } diff --git a/src/trace/traceAnalyser.js b/src/trace/traceAnalyser.js index 66aefa2c5..2dd60fbad 100644 --- a/src/trace/traceAnalyser.js +++ b/src/trace/traceAnalyser.js @@ -1,5 +1,6 @@ 'use strict' var traceHelper = require('../helpers/traceHelper') +var ethutil = require('ethereumjs-util') function TraceAnalyser (_cache) { this.traceCache = _cache @@ -71,6 +72,18 @@ TraceAnalyser.prototype.buildMemory = function (index, step) { } } +function getSha3Input (stack, memory) { + var memoryStart = stack[stack.length - 1] + var memoryLength = stack[stack.length - 2] + var memStartDec = (new ethutil.BN(memoryStart.replace('0x', ''), 16)).toString(10) + memoryStart = parseInt(memStartDec) * 2 + var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) + memoryLength = parseInt(memLengthDec) * 2 + var memoryHex = memory.join('') + var sha3Input = memoryHex.substr(memoryStart, memoryLength) + return sha3Input +} + TraceAnalyser.prototype.buildStorage = function (index, step, context) { if (traceHelper.newContextStorage(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) { var calledAddress = traceHelper.resolveCalledAddress(index, this.trace) @@ -80,6 +93,9 @@ TraceAnalyser.prototype.buildStorage = function (index, step, context) { console.log('unable to build storage changes. ' + index + ' does not match with a CALL. storage changes will be corrupted') } this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1]) + } else if (traceHelper.isSHA3Instruction(step)) { + var sha3Input = getSha3Input(step.stack, step.memory) + this.traceCache.pushSha3Preimage(sha3Input, context.storageContext[context.storageContext.length - 1]) } else if (traceHelper.isSSTOREInstruction(step)) { this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1], step.stack[step.stack.length - 1], step.stack[step.stack.length - 2]) } else if (traceHelper.isReturnInstruction(step)) { diff --git a/src/trace/traceCache.js b/src/trace/traceCache.js index 71f14be8e..edff2aa98 100644 --- a/src/trace/traceCache.js +++ b/src/trace/traceCache.js @@ -1,5 +1,6 @@ 'use strict' var helper = require('../helpers/util') +var ethutil = require('ethereumjs-util') function TraceCache () { this.init() @@ -20,6 +21,9 @@ TraceCache.prototype.init = function () { this.memoryChanges = [] this.storageChanges = [] this.sstore = {} // all sstore occurence in the trace + if (!this.sha3Preimages) { // need to accumulate the preimages over multiple tx's, so dont clear + this.sha3Preimages = {} + } } TraceCache.prototype.pushSteps = function (index, currentCallIndex) { @@ -92,6 +96,16 @@ TraceCache.prototype.pushStoreChanges = function (index, address, key, value) { this.storageChanges.push(index) } +TraceCache.prototype.pushSha3Preimage = function (sha3Input, address) { + console.log('pushSha3Preimage sha3Input:', sha3Input) + var preimage = sha3Input + var imageHash = ethutil.sha3('0x' + sha3Input).toString('hex') + this.sha3Preimages[imageHash] = { + 'address': address, + 'preimage': preimage + } +} + TraceCache.prototype.accumulateStorageChanges = function (index, address, storage) { var ret = Object.assign({}, storage) for (var k in this.storageChanges) { diff --git a/src/ui/SolidityState.js b/src/ui/SolidityState.js index ed684f1b0..691e91131 100644 --- a/src/ui/SolidityState.js +++ b/src/ui/SolidityState.js @@ -3,6 +3,8 @@ var DropdownPanel = require('./DropdownPanel') var stateDecoder = require('../solidity/stateDecoder') var solidityTypeFormatter = require('./SolidityTypeFormatter') var StorageViewer = require('../storage/storageViewer') +var util = require('../solidity/types/util') +var ethutil = require('ethereumjs-util') var yo = require('yo-yo') function SolidityState (_parent, _traceManager, _codeManager, _solidityProxy) { @@ -64,9 +66,28 @@ SolidityState.prototype.init = function () { tx: self.parent.tx, address: address }, self.storageResolver, self.traceManager) - stateDecoder.decodeState(stateVars, storageViewer).then((result) => { - if (!result.error) { - self.basicPanel.update(result) + + var storageJSON = {} + storageViewer.storageRange(function (error, result) { + if (!error) { + storageJSON = result + var sha3Preimages = self.traceManager.traceCache.sha3Preimages + var mappingPreimages = getMappingPreimages(stateVars, storageJSON, sha3Preimages) + + for (var k in stateVars) { + var stateVar = stateVars[k] + if (stateVar.type.typeName.indexOf('mapping') === 0) { + var mapSlot = util.toBN(stateVar.storagelocation.slot).toString(16) + mapSlot = ethutil.setLengthLeft('0x' + mapSlot, 32).toString('hex') + stateVar.type.setMappingElements(mappingPreimages[mapSlot]) + } + } + + stateDecoder.decodeState(stateVars, storageViewer).then((result) => { + if (!result.error) { + self.basicPanel.update(result) + } + }) } }) } @@ -76,4 +97,45 @@ SolidityState.prototype.init = function () { }) } +function getMappingPreimages (stateVars, storage, preimages) { + // loop over stateVars and get the locations of all the mappings + // then on each mapping, pass its specific preimage keys + + // first filter out all non-mapping storage slots + + var ignoreSlots = [] + for (var k in stateVars) { + var stateVar = stateVars[k] + if (stateVar.type.typeName.indexOf('mapping') !== 0) { + ignoreSlots.push(stateVar.storagelocation.slot.toString()) + } + } + + var possibleMappings = [] + for (var hashedLoc in storage) { + var slotNum = util.toBN(storage[hashedLoc].key).toString(10) + if (ignoreSlots.indexOf(slotNum) === -1) { + possibleMappings.push(storage[hashedLoc].key) + } + } + + var storageMappings = {} + for (var pk in possibleMappings) { + var possMapKey = possibleMappings[pk].replace('0x', '') + if (preimages[possMapKey]) { + // got preimage! + var preimage = preimages[possMapKey].preimage + // get mapping position (i.e. storage slot), its the last 32 bytes + var slotByteOffset = preimage.length - 64 + var mappingSlot = preimage.substr(slotByteOffset) + var mappingKey = preimage.substr(0, slotByteOffset) + if (!storageMappings[mappingSlot]) { + storageMappings[mappingSlot] = [] + } + storageMappings[mappingSlot].push(mappingKey) + } + } + return storageMappings +} + module.exports = SolidityState diff --git a/src/ui/SolidityTypeFormatter.js b/src/ui/SolidityTypeFormatter.js index cfb03d99d..1d4b60af0 100644 --- a/src/ui/SolidityTypeFormatter.js +++ b/src/ui/SolidityTypeFormatter.js @@ -41,6 +41,12 @@ function extractData (item, parent, key) { }) ret.self = item.type ret.isStruct = true + } else if (item.type.indexOf('mapping') === 0) { + ret.children = Object.keys((item.value || {})).map(function (key) { + return {key: key, value: item.value[key]} + }) + ret.isMapping = true + ret.self = item.type } else { ret.children = [] ret.self = item.value @@ -51,7 +57,7 @@ function extractData (item, parent, key) { function fontColor (data) { var color = '#124B46' - if (data.isArray || data.isStruct) { + if (data.isArray || data.isStruct || data.isMapping) { color = '#847979' } else if (data.type.indexOf('uint') === 0 || data.type.indexOf('int') === 0 || @@ -63,4 +69,3 @@ function fontColor (data) { } return 'color:' + color } - From aef34c6dd334f2d67ca11741a0e5cd4bb8961c52 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 18 May 2017 16:46:20 +0200 Subject: [PATCH 02/11] move sha3 mapping from traceChache to web3VMProvider - add we3.debug.preimage - move mapping resolver from soliditystate to storageViewer --- src/solidity/decodeInfo.js | 4 +- src/solidity/types/Mapping.js | 25 ++++++----- src/solidity/types/StringType.js | 2 +- src/storage/mappingPreimages.js | 58 +++++++++++++++++++++++++ src/storage/storageViewer.js | 12 ++++++ src/trace/traceAnalyser.js | 16 ------- src/trace/traceCache.js | 14 ------ src/ui/SolidityState.js | 68 ++---------------------------- src/util/web3Admin.js | 9 ++++ src/web3Provider/web3VmProvider.js | 32 ++++++++++++++ 10 files changed, 132 insertions(+), 108 deletions(-) create mode 100644 src/storage/mappingPreimages.js diff --git a/src/solidity/decodeInfo.js b/src/solidity/decodeInfo.js index 4dbd1d341..a31e4e175 100644 --- a/src/solidity/decodeInfo.js +++ b/src/solidity/decodeInfo.js @@ -190,7 +190,7 @@ function struct (type, stateDefinitions, contractName, location) { if (!location) { location = match[2].trim() } - var memberDetails = getStructMembers(match[1], stateDefinitions, contractName) // type is used to extract the ast struct definition + var memberDetails = getStructMembers(match[1], stateDefinitions, contractName, location) // type is used to extract the ast struct definition if (!memberDetails) return null return new StructType(memberDetails, location, match[1]) } else { @@ -233,7 +233,7 @@ function getEnum (type, stateDefinitions, contractName) { * @param {String} location - location of the data (storage ref| storage pointer| memory| calldata) * @return {Array} containing all members of the current struct type */ -function getStructMembers (type, stateDefinitions, contractName) { +function getStructMembers (type, stateDefinitions, contractName, location) { var split = type.split('.') if (!split.length) { type = contractName + '.' + type diff --git a/src/solidity/types/Mapping.js b/src/solidity/types/Mapping.js index 0e62012af..ae21c774c 100644 --- a/src/solidity/types/Mapping.js +++ b/src/solidity/types/Mapping.js @@ -1,5 +1,6 @@ 'use strict' var RefType = require('./RefType') +var util = require('./util') var ethutil = require('ethereumjs-util') class Mapping extends RefType { @@ -9,22 +10,26 @@ class Mapping extends RefType { this.valueType = underlyingTypes.valueType } - setMappingElements (mappingKeyPreimages) { - this.preimages = mappingKeyPreimages - } - async decodeFromStorage (location, storageResolver) { - // location.offset should always be 0 for a mapping (?? double check) - + try { + var mappingsPreimages = await storageResolver.mappingPreimages() + } catch (e) { + return { + value: ' ' + e.message, + type: this.type + } + } + var mapSlot = util.toBN(location.slot).toString(16) + mapSlot = ethutil.setLengthLeft('0x' + mapSlot, 32).toString('hex') + var mappingPreimages = mappingsPreimages[mapSlot] var ret = {} - for (var i in this.preimages) { - var preimage = this.preimages[i] - var mapLocation = getMappingLocation(preimage, location.slot) + for (var i in mappingPreimages) { + var mapLocation = getMappingLocation(i, location.slot) var globalLocation = { offset: location.offset, slot: mapLocation } - ret[preimage] = await this.valueType.decodeFromStorage(globalLocation, storageResolver) + ret[i] = await this.valueType.decodeFromStorage(globalLocation, storageResolver) } return { diff --git a/src/solidity/types/StringType.js b/src/solidity/types/StringType.js index a74b6745a..f21eac4a5 100644 --- a/src/solidity/types/StringType.js +++ b/src/solidity/types/StringType.js @@ -40,7 +40,7 @@ function format (decoded) { var value = decoded.value value = value.replace('0x', '').replace(/(..)/g, '%$1') var ret = { - // length: decoded.length, // unneeded, only dynamicBytes uses length + length: decoded.length, raw: decoded.value, type: 'string' } diff --git a/src/storage/mappingPreimages.js b/src/storage/mappingPreimages.js new file mode 100644 index 000000000..b85901e8e --- /dev/null +++ b/src/storage/mappingPreimages.js @@ -0,0 +1,58 @@ +var global = require('../helpers/global') + +module.exports = { + extractMappingPreimages: extractMappingPreimages +} + +async function extractMappingPreimages (storageViewer) { + return new Promise((resolve, reject) => { + storageViewer.storageRange(function (error, storage) { + if (!error) { + decodeMappingsKeys(storage, (error, mappings) => { + if (error) { + reject(error) + } else { + resolve(mappings) + } + }) + } else { + reject(error) + } + }) + }) +} + +async function decodeMappingsKeys (storage, callback) { + var ret = {} + for (var hashedLoc in storage) { + var preimage + try { + preimage = await getPreimage(storage[hashedLoc].key) + } catch (e) { + } + if (preimage) { + // got preimage! + // get mapping position (i.e. storage slot), its the last 32 bytes + var slotByteOffset = preimage.length - 64 + var mappingSlot = preimage.substr(slotByteOffset) + var mappingKey = preimage.substr(0, slotByteOffset) + if (!ret[mappingSlot]) { + ret[mappingSlot] = {} + } + ret[mappingSlot][mappingKey] = preimage + } + } + callback(null, ret) +} + +function getPreimage (key) { + return new Promise((resolve, reject) => { + global.web3.debug.preimage(key, function (error, preimage) { + if (error) { + reject(error) + } else { + resolve(preimage) + } + }) + }) +} diff --git a/src/storage/storageViewer.js b/src/storage/storageViewer.js index ca117f5b6..289805e89 100644 --- a/src/storage/storageViewer.js +++ b/src/storage/storageViewer.js @@ -1,10 +1,15 @@ 'use strict' var helper = require('../helpers/util') +var mappingPreimagesExtractor = require('./mappingPreimages') class StorageViewer { constructor (_context, _storageResolver, _traceManager) { this.context = _context this.storageResolver = _storageResolver + // contains [mappingSlot][mappingkey] = preimage + // this map is renewed for each execution step + // this map is shared among all the mapping types + this.mappingsPreimages = null _traceManager.accumulateStorageChanges(this.context.stepIndex, this.context.address, {}, (error, storageChanges) => { if (!error) { this.storageChanges = storageChanges @@ -58,6 +63,13 @@ class StorageViewer { isComplete (address) { return this.storageResolver.isComplete(address) } + + async mappingPreimages () { + if (!this.mappingsPreimages) { + this.mappingsPreimages = await mappingPreimagesExtractor.extractMappingPreimages(this) + } + return this.mappingsPreimages + } } module.exports = StorageViewer diff --git a/src/trace/traceAnalyser.js b/src/trace/traceAnalyser.js index 2dd60fbad..66aefa2c5 100644 --- a/src/trace/traceAnalyser.js +++ b/src/trace/traceAnalyser.js @@ -1,6 +1,5 @@ 'use strict' var traceHelper = require('../helpers/traceHelper') -var ethutil = require('ethereumjs-util') function TraceAnalyser (_cache) { this.traceCache = _cache @@ -72,18 +71,6 @@ TraceAnalyser.prototype.buildMemory = function (index, step) { } } -function getSha3Input (stack, memory) { - var memoryStart = stack[stack.length - 1] - var memoryLength = stack[stack.length - 2] - var memStartDec = (new ethutil.BN(memoryStart.replace('0x', ''), 16)).toString(10) - memoryStart = parseInt(memStartDec) * 2 - var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) - memoryLength = parseInt(memLengthDec) * 2 - var memoryHex = memory.join('') - var sha3Input = memoryHex.substr(memoryStart, memoryLength) - return sha3Input -} - TraceAnalyser.prototype.buildStorage = function (index, step, context) { if (traceHelper.newContextStorage(step) && !traceHelper.isCallToPrecompiledContract(index, this.trace)) { var calledAddress = traceHelper.resolveCalledAddress(index, this.trace) @@ -93,9 +80,6 @@ TraceAnalyser.prototype.buildStorage = function (index, step, context) { console.log('unable to build storage changes. ' + index + ' does not match with a CALL. storage changes will be corrupted') } this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1]) - } else if (traceHelper.isSHA3Instruction(step)) { - var sha3Input = getSha3Input(step.stack, step.memory) - this.traceCache.pushSha3Preimage(sha3Input, context.storageContext[context.storageContext.length - 1]) } else if (traceHelper.isSSTOREInstruction(step)) { this.traceCache.pushStoreChanges(index + 1, context.storageContext[context.storageContext.length - 1], step.stack[step.stack.length - 1], step.stack[step.stack.length - 2]) } else if (traceHelper.isReturnInstruction(step)) { diff --git a/src/trace/traceCache.js b/src/trace/traceCache.js index edff2aa98..71f14be8e 100644 --- a/src/trace/traceCache.js +++ b/src/trace/traceCache.js @@ -1,6 +1,5 @@ 'use strict' var helper = require('../helpers/util') -var ethutil = require('ethereumjs-util') function TraceCache () { this.init() @@ -21,9 +20,6 @@ TraceCache.prototype.init = function () { this.memoryChanges = [] this.storageChanges = [] this.sstore = {} // all sstore occurence in the trace - if (!this.sha3Preimages) { // need to accumulate the preimages over multiple tx's, so dont clear - this.sha3Preimages = {} - } } TraceCache.prototype.pushSteps = function (index, currentCallIndex) { @@ -96,16 +92,6 @@ TraceCache.prototype.pushStoreChanges = function (index, address, key, value) { this.storageChanges.push(index) } -TraceCache.prototype.pushSha3Preimage = function (sha3Input, address) { - console.log('pushSha3Preimage sha3Input:', sha3Input) - var preimage = sha3Input - var imageHash = ethutil.sha3('0x' + sha3Input).toString('hex') - this.sha3Preimages[imageHash] = { - 'address': address, - 'preimage': preimage - } -} - TraceCache.prototype.accumulateStorageChanges = function (index, address, storage) { var ret = Object.assign({}, storage) for (var k in this.storageChanges) { diff --git a/src/ui/SolidityState.js b/src/ui/SolidityState.js index 691e91131..ed684f1b0 100644 --- a/src/ui/SolidityState.js +++ b/src/ui/SolidityState.js @@ -3,8 +3,6 @@ var DropdownPanel = require('./DropdownPanel') var stateDecoder = require('../solidity/stateDecoder') var solidityTypeFormatter = require('./SolidityTypeFormatter') var StorageViewer = require('../storage/storageViewer') -var util = require('../solidity/types/util') -var ethutil = require('ethereumjs-util') var yo = require('yo-yo') function SolidityState (_parent, _traceManager, _codeManager, _solidityProxy) { @@ -66,28 +64,9 @@ SolidityState.prototype.init = function () { tx: self.parent.tx, address: address }, self.storageResolver, self.traceManager) - - var storageJSON = {} - storageViewer.storageRange(function (error, result) { - if (!error) { - storageJSON = result - var sha3Preimages = self.traceManager.traceCache.sha3Preimages - var mappingPreimages = getMappingPreimages(stateVars, storageJSON, sha3Preimages) - - for (var k in stateVars) { - var stateVar = stateVars[k] - if (stateVar.type.typeName.indexOf('mapping') === 0) { - var mapSlot = util.toBN(stateVar.storagelocation.slot).toString(16) - mapSlot = ethutil.setLengthLeft('0x' + mapSlot, 32).toString('hex') - stateVar.type.setMappingElements(mappingPreimages[mapSlot]) - } - } - - stateDecoder.decodeState(stateVars, storageViewer).then((result) => { - if (!result.error) { - self.basicPanel.update(result) - } - }) + stateDecoder.decodeState(stateVars, storageViewer).then((result) => { + if (!result.error) { + self.basicPanel.update(result) } }) } @@ -97,45 +76,4 @@ SolidityState.prototype.init = function () { }) } -function getMappingPreimages (stateVars, storage, preimages) { - // loop over stateVars and get the locations of all the mappings - // then on each mapping, pass its specific preimage keys - - // first filter out all non-mapping storage slots - - var ignoreSlots = [] - for (var k in stateVars) { - var stateVar = stateVars[k] - if (stateVar.type.typeName.indexOf('mapping') !== 0) { - ignoreSlots.push(stateVar.storagelocation.slot.toString()) - } - } - - var possibleMappings = [] - for (var hashedLoc in storage) { - var slotNum = util.toBN(storage[hashedLoc].key).toString(10) - if (ignoreSlots.indexOf(slotNum) === -1) { - possibleMappings.push(storage[hashedLoc].key) - } - } - - var storageMappings = {} - for (var pk in possibleMappings) { - var possMapKey = possibleMappings[pk].replace('0x', '') - if (preimages[possMapKey]) { - // got preimage! - var preimage = preimages[possMapKey].preimage - // get mapping position (i.e. storage slot), its the last 32 bytes - var slotByteOffset = preimage.length - 64 - var mappingSlot = preimage.substr(slotByteOffset) - var mappingKey = preimage.substr(0, slotByteOffset) - if (!storageMappings[mappingSlot]) { - storageMappings[mappingSlot] = [] - } - storageMappings[mappingSlot].push(mappingKey) - } - } - return storageMappings -} - module.exports = SolidityState diff --git a/src/util/web3Admin.js b/src/util/web3Admin.js index fa9350f6a..4ebb9c910 100644 --- a/src/util/web3Admin.js +++ b/src/util/web3Admin.js @@ -6,6 +6,15 @@ module.exports = { } // DEBUG var methods = [] + if (!(web3.debug && web3.debug.preimage)) { + methods.push(new web3._extend.Method({ + name: 'preimage', + call: 'debug_preimage', + inputFormatter: [null], + params: 1 + })) + } + if (!(web3.debug && web3.debug.traceTransaction)) { methods.push(new web3._extend.Method({ name: 'traceTransaction', diff --git a/src/web3Provider/web3VmProvider.js b/src/web3Provider/web3VmProvider.js index e53d6a0b5..741022f20 100644 --- a/src/web3Provider/web3VmProvider.js +++ b/src/web3Provider/web3VmProvider.js @@ -1,6 +1,7 @@ var util = require('../helpers/util') var uiutil = require('../helpers/ui') var traceHelper = require('../helpers/traceHelper') +var ethutil = require('ethereumjs-util') var Web3 = require('web3') function web3VmProvider () { @@ -22,9 +23,11 @@ function web3VmProvider () { this.eth.getBlockNumber = function (cb) { return self.getBlockNumber(cb) } this.debug.traceTransaction = function (hash, options, cb) { return self.traceTransaction(hash, options, cb) } this.debug.storageRangeAt = function (blockNumber, txIndex, address, start, end, maxLength, cb) { return self.storageRangeAt(blockNumber, txIndex, address, start, end, maxLength, cb) } + this.debug.preimage = function (hashedKey, cb) { return self.preimage(hashedKey, cb) } this.providers = { 'HttpProvider': function (url) {} } this.currentProvider = {'host': 'vm provider'} this.storageCache = {} + this.sha3Preimages = {} } web3VmProvider.prototype.setVM = function (vm) { @@ -128,6 +131,10 @@ web3VmProvider.prototype.pushTrace = function (self, data) { } } } + if (traceHelper.isSHA3Instruction(step)) { + var sha3Input = getSha3Input(step.stack, step.memory) + pushSha3Preimage(this, sha3Input) + } this.processingIndex++ this.previousDepth = depth } @@ -189,4 +196,29 @@ web3VmProvider.prototype.getTransactionFromBlock = function (blockNumber, txInde } } +web3VmProvider.prototype.preimage = function (hashedKey, cb) { + hashedKey = hashedKey.replace('0x', '') + cb(null, this.sha3Preimages[hashedKey] !== undefined ? this.sha3Preimages[hashedKey].preimage : null) +} + +function pushSha3Preimage (self, sha3Input) { + var preimage = sha3Input + var imageHash = ethutil.sha3('0x' + sha3Input).toString('hex') + self.sha3Preimages[imageHash] = { + 'preimage': preimage + } +} + +function getSha3Input (stack, memory) { + var memoryStart = stack[stack.length - 1] + var memoryLength = stack[stack.length - 2] + var memStartDec = (new ethutil.BN(memoryStart.replace('0x', ''), 16)).toString(10) + memoryStart = parseInt(memStartDec) * 2 + var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) + memoryLength = parseInt(memLengthDec) * 2 + var memoryHex = memory.join('') + var sha3Input = memoryHex.substr(memoryStart, memoryLength) + return sha3Input +} + module.exports = web3VmProvider From 3ff34f1f3aa3410f398393166c673c987bc04b91 Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 19 May 2017 13:51:59 +0200 Subject: [PATCH 03/11] remove location from getStructMembers --- src/solidity/decodeInfo.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/solidity/decodeInfo.js b/src/solidity/decodeInfo.js index a31e4e175..c0d9238a4 100644 --- a/src/solidity/decodeInfo.js +++ b/src/solidity/decodeInfo.js @@ -190,7 +190,7 @@ function struct (type, stateDefinitions, contractName, location) { if (!location) { location = match[2].trim() } - var memberDetails = getStructMembers(match[1], stateDefinitions, contractName, location) // type is used to extract the ast struct definition + var memberDetails = getStructMembers(match[1], stateDefinitions, contractName) // type is used to extract the ast struct definition if (!memberDetails) return null return new StructType(memberDetails, location, match[1]) } else { @@ -230,10 +230,9 @@ function getEnum (type, stateDefinitions, contractName) { * @param {String} typeName - name of the struct type (e.g struct ) * @param {Object} stateDefinitions - all state definition given by the AST (including struct and enum type declaration) for all contracts * @param {String} contractName - contract the @args typeName belongs to - * @param {String} location - location of the data (storage ref| storage pointer| memory| calldata) * @return {Array} containing all members of the current struct type */ -function getStructMembers (type, stateDefinitions, contractName, location) { +function getStructMembers (type, stateDefinitions, contractName) { var split = type.split('.') if (!split.length) { type = contractName + '.' + type From ce16924682542c5b410dc1baee49e61bca436465 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 22 May 2017 15:31:09 +0200 Subject: [PATCH 04/11] use helper --- src/solidity/types/Mapping.js | 4 ++-- src/solidity/types/util.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/solidity/types/Mapping.js b/src/solidity/types/Mapping.js index ae21c774c..e540a6c6a 100644 --- a/src/solidity/types/Mapping.js +++ b/src/solidity/types/Mapping.js @@ -19,8 +19,8 @@ class Mapping extends RefType { type: this.type } } - var mapSlot = util.toBN(location.slot).toString(16) - mapSlot = ethutil.setLengthLeft('0x' + mapSlot, 32).toString('hex') + var mapSlot = util.normalizeHex(ethutil.bufferToHex(location.slot)) + console.log(mapSlot, mappingsPreimages) var mappingPreimages = mappingsPreimages[mapSlot] var ret = {} for (var i in mappingPreimages) { diff --git a/src/solidity/types/util.js b/src/solidity/types/util.js index 3bf63f03d..35918aedb 100644 --- a/src/solidity/types/util.js +++ b/src/solidity/types/util.js @@ -10,7 +10,8 @@ module.exports = { toBN: toBN, add: add, extractLocation: extractLocation, - removeLocation: removeLocation + removeLocation: removeLocation, + normalizeHex: normalizeHex } function decodeIntFromHex (value, byteLength, signed) { From 0958b7eb290ff0696a69df8041d19a95e00c1c12 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 22 May 2017 16:00:48 +0200 Subject: [PATCH 05/11] add doc --- src/storage/mappingPreimages.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/storage/mappingPreimages.js b/src/storage/mappingPreimages.js index b85901e8e..5156f0191 100644 --- a/src/storage/mappingPreimages.js +++ b/src/storage/mappingPreimages.js @@ -4,6 +4,13 @@ module.exports = { extractMappingPreimages: extractMappingPreimages } +/** + * Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location. + * like { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... } + * + * @param {Object} address - storageViewer + * @return {Map} - solidity mapping location + */ async function extractMappingPreimages (storageViewer) { return new Promise((resolve, reject) => { storageViewer.storageRange(function (error, storage) { @@ -22,6 +29,14 @@ async function extractMappingPreimages (storageViewer) { }) } +/** + * Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location. + * like { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... } + * + * @param {Object} storage - storage given by storage Viewer (basically a mapping hashedkey : {key, value}) + * @param {Function} callback - calback + * @return {Map} - solidity mapping location (e.g { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... }) + */ async function decodeMappingsKeys (storage, callback) { var ret = {} for (var hashedLoc in storage) { @@ -45,6 +60,12 @@ async function decodeMappingsKeys (storage, callback) { callback(null, ret) } +/** + * Uses web3 to return preimage of a key + * + * @param {String} key - key to retrieve the preimage of + * @return {String} - preimage of the given key + */ function getPreimage (key) { return new Promise((resolve, reject) => { global.web3.debug.preimage(key, function (error, preimage) { From 72e1b875ab377643091547679a3837e99fd5a2da Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 22 May 2017 16:06:48 +0200 Subject: [PATCH 06/11] remove uneeded function --- src/web3Provider/web3VmProvider.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/web3Provider/web3VmProvider.js b/src/web3Provider/web3VmProvider.js index 741022f20..0aef043d6 100644 --- a/src/web3Provider/web3VmProvider.js +++ b/src/web3Provider/web3VmProvider.js @@ -133,7 +133,11 @@ web3VmProvider.prototype.pushTrace = function (self, data) { } if (traceHelper.isSHA3Instruction(step)) { var sha3Input = getSha3Input(step.stack, step.memory) - pushSha3Preimage(this, sha3Input) + var preimage = sha3Input + var imageHash = ethutil.sha3('0x' + sha3Input).toString('hex') + self.sha3Preimages[imageHash] = { + 'preimage': preimage + } } this.processingIndex++ this.previousDepth = depth @@ -201,14 +205,6 @@ web3VmProvider.prototype.preimage = function (hashedKey, cb) { cb(null, this.sha3Preimages[hashedKey] !== undefined ? this.sha3Preimages[hashedKey].preimage : null) } -function pushSha3Preimage (self, sha3Input) { - var preimage = sha3Input - var imageHash = ethutil.sha3('0x' + sha3Input).toString('hex') - self.sha3Preimages[imageHash] = { - 'preimage': preimage - } -} - function getSha3Input (stack, memory) { var memoryStart = stack[stack.length - 1] var memoryLength = stack[stack.length - 2] From e59e5644d3248d282cb27a13499fbd20c167aa35 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 23 May 2017 09:45:02 +0200 Subject: [PATCH 07/11] extract sha3 input --- src/web3Provider/web3VmProvider.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/web3Provider/web3VmProvider.js b/src/web3Provider/web3VmProvider.js index 0aef043d6..e1f23d4a8 100644 --- a/src/web3Provider/web3VmProvider.js +++ b/src/web3Provider/web3VmProvider.js @@ -212,8 +212,13 @@ function getSha3Input (stack, memory) { memoryStart = parseInt(memStartDec) * 2 var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) memoryLength = parseInt(memLengthDec) * 2 - var memoryHex = memory.join('') - var sha3Input = memoryHex.substr(memoryStart, memoryLength) + + var subMemoryIndex = Math.floor(memoryStart / 32) + var sha3Input = '' + while (sha3Input.length < memoryLength) { + sha3Input += memory[subMemoryIndex] + subMemoryIndex++ + } return sha3Input } From ce3caa69916931f1291d85dff0043db8cd6683cf Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 23 May 2017 12:57:40 +0200 Subject: [PATCH 08/11] cache the preimages for a debug session (using the initial state) --- src/solidity/types/Mapping.js | 7 +-- src/storage/mappingPreimages.js | 51 ++++++---------------- src/storage/storageResolver.js | 43 ++++++++++++++++--- src/storage/storageViewer.js | 75 +++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 60 deletions(-) diff --git a/src/solidity/types/Mapping.js b/src/solidity/types/Mapping.js index e540a6c6a..17ddd3373 100644 --- a/src/solidity/types/Mapping.js +++ b/src/solidity/types/Mapping.js @@ -11,12 +11,13 @@ class Mapping extends RefType { } async decodeFromStorage (location, storageResolver) { + var mappingsPreimages try { - var mappingsPreimages = await storageResolver.mappingPreimages() + mappingsPreimages = await storageResolver.mappingsLocation() } catch (e) { return { - value: ' ' + e.message, - type: this.type + value: e.message, + type: this.typeName } } var mapSlot = util.normalizeHex(ethutil.bufferToHex(location.slot)) diff --git a/src/storage/mappingPreimages.js b/src/storage/mappingPreimages.js index 5156f0191..5a70a92ec 100644 --- a/src/storage/mappingPreimages.js +++ b/src/storage/mappingPreimages.js @@ -1,42 +1,17 @@ var global = require('../helpers/global') module.exports = { - extractMappingPreimages: extractMappingPreimages + decodeMappingsKeys: decodeMappingsKeys } /** - * Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location. - * like { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... } - * - * @param {Object} address - storageViewer - * @return {Map} - solidity mapping location - */ -async function extractMappingPreimages (storageViewer) { - return new Promise((resolve, reject) => { - storageViewer.storageRange(function (error, storage) { - if (!error) { - decodeMappingsKeys(storage, (error, mappings) => { - if (error) { - reject(error) - } else { - resolve(mappings) - } - }) - } else { - reject(error) - } - }) - }) -} - -/** - * Uses the storageViewer to retrieve the storage and returns a mapping containing possible solidity mappping type location. - * like { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... } - * - * @param {Object} storage - storage given by storage Viewer (basically a mapping hashedkey : {key, value}) - * @param {Function} callback - calback - * @return {Map} - solidity mapping location (e.g { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... }) - */ + * extract the mappings location from the storage + * like { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... } + * + * @param {Object} storage - storage given by storage Viewer (basically a mapping hashedkey : {key, value}) + * @param {Function} callback - calback + * @return {Map} - solidity mapping location (e.g { "" : { "": preimageOf1 }, { "": preimageOf2 }, ... }) + */ async function decodeMappingsKeys (storage, callback) { var ret = {} for (var hashedLoc in storage) { @@ -61,11 +36,11 @@ async function decodeMappingsKeys (storage, callback) { } /** - * Uses web3 to return preimage of a key - * - * @param {String} key - key to retrieve the preimage of - * @return {String} - preimage of the given key - */ + * Uses web3 to return preimage of a key + * + * @param {String} key - key to retrieve the preimage of + * @return {String} - preimage of the given key + */ function getPreimage (key) { return new Promise((resolve, reject) => { global.web3.debug.preimage(key, function (error, preimage) { diff --git a/src/storage/storageResolver.js b/src/storage/storageResolver.js index 416538864..3ad236c0a 100644 --- a/src/storage/storageResolver.js +++ b/src/storage/storageResolver.js @@ -1,10 +1,16 @@ 'use strict' var traceHelper = require('../helpers/traceHelper') var util = require('../helpers/global') +var mappingPreimages = require('./mappingPreimages') +/** + * Basically one instance is created for one debugging session. + * (TODO: one instance need to be shared over all the components) + */ class StorageResolver { constructor () { this.storageByAddress = {} + this.preimagesMappingByAddress = {} this.maxSize = 100 } @@ -21,6 +27,33 @@ class StorageResolver { storageRangeInternal(this, zeroSlot, tx, stepIndex, address, callback) } + /** + * compute the mappgings type locations for the current address (cached for a debugging session) + * note: that only retrieve the first 100 items. + * + * @param {String} address - contract address + * @param {Object} address - storage + * @return {Function} - callback + */ + initialPreimagesMappings (tx, stepIndex, address, callback) { + if (this.preimagesMappingByAddress[address]) { + return callback(null, this.preimagesMappingByAddress[address]) + } + this.storageRange(tx, stepIndex, address, (error, storage) => { + if (error) { + return callback(error) + } + mappingPreimages.decodeMappingsKeys(storage, (error, mappings) => { + if (error) { + callback(error) + } else { + this.preimagesMappingByAddress[address] = mappings + callback(null, mappings) + } + }) + }) + } + /** * return a slot value for the given context (address and vm trace index) * @@ -96,11 +129,11 @@ function fromCache (self, address) { } /** - * store the result of `storageRangeAtInternal` - * - * @param {String} address - contract address - * @param {Object} storage - result of `storageRangeAtInternal`, contains {key, hashedKey, value} - */ + * store the result of `storageRangeAtInternal` + * + * @param {String} address - contract address + * @param {Object} storage - result of `storageRangeAtInternal`, contains {key, hashedKey, value} + */ function toCache (self, address, storage) { if (!self.storageByAddress[address]) { self.storageByAddress[address] = {} diff --git a/src/storage/storageViewer.js b/src/storage/storageViewer.js index 289805e89..fde0d56e1 100644 --- a/src/storage/storageViewer.js +++ b/src/storage/storageViewer.js @@ -1,15 +1,16 @@ 'use strict' var helper = require('../helpers/util') -var mappingPreimagesExtractor = require('./mappingPreimages') +var mappingPreimages = require('./mappingPreimages') + /** + * easier access to the storage resolver + * Basically one instance is created foreach execution step and foreach component that need it. + * (TODO: one instance need to be shared over all the components) + */ class StorageViewer { constructor (_context, _storageResolver, _traceManager) { this.context = _context this.storageResolver = _storageResolver - // contains [mappingSlot][mappingkey] = preimage - // this map is renewed for each execution step - // this map is shared among all the mapping types - this.mappingsPreimages = null _traceManager.accumulateStorageChanges(this.context.stepIndex, this.context.address, {}, (error, storageChanges) => { if (!error) { this.storageChanges = storageChanges @@ -20,11 +21,11 @@ class StorageViewer { } /** - * return the storage for the current context (address and vm trace index) - * by default now returns the range 0 => 1000 - * - * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value} - */ + * return the storage for the current context (address and vm trace index) + * by default now returns the range 0 => 1000 + * + * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value} + */ storageRange (callback) { this.storageResolver.storageRange(this.context.tx, this.context.stepIndex, this.context.address, (error, storage) => { if (error) { @@ -64,12 +65,58 @@ class StorageViewer { return this.storageResolver.isComplete(address) } - async mappingPreimages () { - if (!this.mappingsPreimages) { - this.mappingsPreimages = await mappingPreimagesExtractor.extractMappingPreimages(this) + /** + * return all the possible mappings locations for the current context (cached) + * + * @param {Function} callback + */ + async mappingsLocation () { + return new Promise((resolve, reject) => { + if (this.completeMappingsLocation) { + return resolve(this.completeMappingsLocation) + } + this.storageResolver.initialPreimagesMappings(this.context.tx, this.context.stepIndex, this.context.address, (error, initialMappingsLocation) => { + if (error) { + reject(error) + } else { + this.extractMappingsLocationChanges(this.storageChanges, (error, mappingsLocationChanges) => { + if (error) { + return reject(error) + } + this.completeMappingsLocation = Object.assign({}, initialMappingsLocation) + for (var key in mappingsLocationChanges) { + if (!initialMappingsLocation[key]) { + initialMappingsLocation[key] = {} + } + this.completeMappingsLocation[key] = Object.assign({}, initialMappingsLocation[key], mappingsLocationChanges[key]) + } + resolve(this.completeMappingsLocation) + }) + } + }) + }) + } + + /** + * retrieve mapping location changes from the storage changes. + * + * @param {Function} callback + */ + extractMappingsLocationChanges (storageChanges, callback) { + if (this.mappingsLocationChanges) { + return callback(null, this.mappingsLocationChanges) } - return this.mappingsPreimages + mappingPreimages.decodeMappingsKeys(storageChanges, (error, mappings) => { + if (!error) { + this.mappingsLocationChanges = mappings + return callback(null, this.mappingsLocationChanges) + } else { + callback(error) + } + }) } } + + module.exports = StorageViewer From 075fa4ee5987d0754df4cd48bf5402681b441aab Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 23 May 2017 15:00:51 +0200 Subject: [PATCH 09/11] fix sha3 input extract --- src/web3Provider/web3VmProvider.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/web3Provider/web3VmProvider.js b/src/web3Provider/web3VmProvider.js index e1f23d4a8..079db3b62 100644 --- a/src/web3Provider/web3VmProvider.js +++ b/src/web3Provider/web3VmProvider.js @@ -213,11 +213,16 @@ function getSha3Input (stack, memory) { var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) memoryLength = parseInt(memLengthDec) * 2 - var subMemoryIndex = Math.floor(memoryStart / 32) - var sha3Input = '' - while (sha3Input.length < memoryLength) { - sha3Input += memory[subMemoryIndex] - subMemoryIndex++ + var min = Math.floor(memoryStart / 32) + var fillLength = Math.floor(memoryLength / 32) + var sha3Input = memory[min].slice(memoryStart - 32 * min) + min++ + while (min < fillLength) { + sha3Input += memory[min] + min++ + } + if (sha3Input.length < memoryLength) { + sha3Input += memory[min].slice(0, memoryLength - sha3Input.length) } return sha3Input } From 4013d3528d7eaa489607e3fd0db75c69b8570b00 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 24 May 2017 12:48:56 +0200 Subject: [PATCH 10/11] return a promise if mappings alrady requested --- src/storage/storageViewer.js | 48 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/storage/storageViewer.js b/src/storage/storageViewer.js index fde0d56e1..5aeeece5f 100644 --- a/src/storage/storageViewer.js +++ b/src/storage/storageViewer.js @@ -11,6 +11,7 @@ class StorageViewer { constructor (_context, _storageResolver, _traceManager) { this.context = _context this.storageResolver = _storageResolver + this.completeMapingsLocationPromise = null _traceManager.accumulateStorageChanges(this.context.stepIndex, this.context.address, {}, (error, storageChanges) => { if (!error) { this.storageChanges = storageChanges @@ -71,30 +72,33 @@ class StorageViewer { * @param {Function} callback */ async mappingsLocation () { - return new Promise((resolve, reject) => { - if (this.completeMappingsLocation) { - return resolve(this.completeMappingsLocation) - } - this.storageResolver.initialPreimagesMappings(this.context.tx, this.context.stepIndex, this.context.address, (error, initialMappingsLocation) => { - if (error) { - reject(error) - } else { - this.extractMappingsLocationChanges(this.storageChanges, (error, mappingsLocationChanges) => { - if (error) { - return reject(error) - } - this.completeMappingsLocation = Object.assign({}, initialMappingsLocation) - for (var key in mappingsLocationChanges) { - if (!initialMappingsLocation[key]) { - initialMappingsLocation[key] = {} - } - this.completeMappingsLocation[key] = Object.assign({}, initialMappingsLocation[key], mappingsLocationChanges[key]) - } - resolve(this.completeMappingsLocation) - }) + if (!this.completeMapingsLocationPromise) { + this.completeMapingsLocationPromise = new Promise((resolve, reject) => { + if (this.completeMappingsLocation) { + return this.completeMappingsLocation } + this.storageResolver.initialPreimagesMappings(this.context.tx, this.context.stepIndex, this.context.address, (error, initialMappingsLocation) => { + if (error) { + reject(error) + } else { + this.extractMappingsLocationChanges(this.storageChanges, (error, mappingsLocationChanges) => { + if (error) { + return reject(error) + } + this.completeMappingsLocation = Object.assign({}, initialMappingsLocation) + for (var key in mappingsLocationChanges) { + if (!initialMappingsLocation[key]) { + initialMappingsLocation[key] = {} + } + this.completeMappingsLocation[key] = Object.assign({}, initialMappingsLocation[key], mappingsLocationChanges[key]) + } + resolve(this.completeMappingsLocation) + }) + } + }) }) - }) + } + return this.completeMapingsLocationPromise } /** From cbd30b86eca8d41eeeee7c166e995d237af9e2e7 Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 24 May 2017 12:59:13 +0200 Subject: [PATCH 11/11] typo + emptyFill if memory not yet filled --- src/web3Provider/web3VmProvider.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/web3Provider/web3VmProvider.js b/src/web3Provider/web3VmProvider.js index 079db3b62..f3b68f79c 100644 --- a/src/web3Provider/web3VmProvider.js +++ b/src/web3Provider/web3VmProvider.js @@ -213,18 +213,26 @@ function getSha3Input (stack, memory) { var memLengthDec = (new ethutil.BN(memoryLength.replace('0x', ''), 16).toString(10)) memoryLength = parseInt(memLengthDec) * 2 - var min = Math.floor(memoryStart / 32) - var fillLength = Math.floor(memoryLength / 32) - var sha3Input = memory[min].slice(memoryStart - 32 * min) - min++ - while (min < fillLength) { - sha3Input += memory[min] - min++ + var i = Math.floor(memoryStart / 32) + var maxIndex = Math.floor(memoryLength / 32) + if (!memory[i]) { + return emptyFill(memoryLength) + } + var sha3Input = memory[i].slice(memoryStart - 32 * i) + i++ + while (i < maxIndex) { + sha3Input += memory[i] ? memory[i] : emptyFill(32) + i++ } if (sha3Input.length < memoryLength) { - sha3Input += memory[min].slice(0, memoryLength - sha3Input.length) + var 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