Skip to content
This repository has been archived by the owner on Mar 3, 2021. It is now read-only.

Commit

Permalink
initial support for mapping types
Browse files Browse the repository at this point in the history
  • Loading branch information
cdetrio authored and yann300 committed May 18, 2017
1 parent 92942db commit a382227
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/helpers/traceHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
Expand Down
19 changes: 15 additions & 4 deletions src/solidity/decodeInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

/**
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
52 changes: 48 additions & 4 deletions src/solidity/types/Mapping.js
Original file line number Diff line number Diff line change
@@ -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: '<not implemented>',
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: '<not implemented>',
length: '0x',
Expand All @@ -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
2 changes: 1 addition & 1 deletion src/solidity/types/StringType.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down
16 changes: 16 additions & 0 deletions src/trace/traceAnalyser.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'
var traceHelper = require('../helpers/traceHelper')
var ethutil = require('ethereumjs-util')

function TraceAnalyser (_cache) {
this.traceCache = _cache
Expand Down Expand Up @@ -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)
Expand All @@ -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)) {
Expand Down
14 changes: 14 additions & 0 deletions src/trace/traceCache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'
var helper = require('../helpers/util')
var ethutil = require('ethereumjs-util')

function TraceCache () {
this.init()
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
68 changes: 65 additions & 3 deletions src/ui/SolidityState.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
})
}
})
}
Expand All @@ -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
9 changes: 7 additions & 2 deletions src/ui/SolidityTypeFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ||
Expand All @@ -63,4 +69,3 @@ function fontColor (data) {
}
return 'color:' + color
}

0 comments on commit a382227

Please sign in to comment.