remix-project mirror
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
remix-project/libs/remix-debug/src/solidity-decoder/decodeInfo.js

384 lines
13 KiB

'use strict'
const AddressType = require('./types/Address')
const ArrayType = require('./types/ArrayType')
const BoolType = require('./types/Bool')
const BytesType = require('./types/DynamicByteArray')
const BytesXType = require('./types/FixedByteArray')
const EnumType = require('./types/Enum')
const StringType = require('./types/StringType')
const StructType = require('./types/Struct')
const IntType = require('./types/Int')
const UintType = require('./types/Uint')
const MappingType = require('./types/Mapping')
const util = require('./types/util')
/**
* mapping decode the given @arg type
*
* @param {String} type - type given by the AST
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function mapping (type, stateDefinitions, contractName) {
const match = type.match(/mapping\((.*?)=>(.*)\)$/)
const keyTypeName = match[1].trim()
const valueTypeName = match[2].trim()
const keyType = parseType(keyTypeName, stateDefinitions, contractName, 'storage')
const valueType = parseType(valueTypeName, stateDefinitions, contractName, 'storage')
var underlyingTypes = {
'keyType': keyType,
'valueType': valueType
}
return new MappingType(underlyingTypes, 'location', util.removeLocation(type))
}
/**
* Uint decode the given @arg type
*
* @param {String} type - type given by the AST (e.g uint256, uint32)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function uint (type) {
type === 'uint' ? 'uint256' : type
const storageBytes = parseInt(type.replace('uint', '')) / 8
return new UintType(storageBytes)
}
/**
* Int decode the given @arg type
*
* @param {String} type - type given by the AST (e.g int256, int32)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function int (type) {
type === 'int' ? 'int256' : type
const storageBytes = parseInt(type.replace('int', '')) / 8
return new IntType(storageBytes)
}
/**
* Address decode the given @arg type
*
* @param {String} type - type given by the AST (e.g address)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function address (type) {
return new AddressType()
}
/**
* Bool decode the given @arg type
*
* @param {String} type - type given by the AST (e.g bool)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function bool (type) {
return new BoolType()
}
/**
* DynamicByteArray decode the given @arg type
*
* @param {String} type - type given by the AST (e.g bytes storage ref)
* @param {null} stateDefinitions - all state definitions given by the AST (including struct and enum type declaration) for all contracts
* @param {null} contractName - contract the @args typeName belongs to
* @param {String} location - location of the data (storage ref| storage pointer| memory| calldata)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function dynamicByteArray (type, stateDefinitions, contractName, location) {
if (!location) {
location = util.extractLocation(type)
}
if (location) {
return new BytesType(location)
} else {
return null
}
}
/**
* FixedByteArray decode the given @arg type
*
* @param {String} type - type given by the AST (e.g bytes16)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function fixedByteArray (type) {
const storageBytes = parseInt(type.replace('bytes', ''))
return new BytesXType(storageBytes)
}
/**
* StringType decode the given @arg type
*
* @param {String} type - type given by the AST (e.g string storage ref)
* @param {null} stateDefinitions - all state definitions given by the AST (including struct and enum type declaration) for all contracts
* @param {null} contractName - contract the @args typeName belongs to
* @param {String} location - location of the data (storage ref| storage pointer| memory| calldata)
* @return {Object} returns decoded info about the current type: { storageBytes, typeName}
*/
function stringType (type, stateDefinitions, contractName, location) {
if (!location) {
location = util.extractLocation(type)
}
if (location) {
return new StringType(location)
} else {
return null
}
}
/**
* ArrayType decode the given @arg type
*
* @param {String} type - type given by the AST (e.g int256[] storage ref, int256[] storage ref[] storage ref)
* @param {Object} stateDefinitions - all state definitions 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 {Object} returns decoded info about the current type: { storageBytes, typeName, arraySize, subArray}
*/
function array (type, stateDefinitions, contractName, location) {
let arraySize
const match = type.match(/(.*)\[(.*?)\]( storage ref| storage pointer| memory| calldata)?$/)
if (!match) {
console.log('unable to parse type ' + type)
return null
}
if (!location) {
location = match[3].trim()
}
arraySize = match[2] === '' ? 'dynamic' : parseInt(match[2])
const underlyingType = parseType(match[1], stateDefinitions, contractName, location)
if (underlyingType === null) {
console.log('unable to parse type ' + type)
return null
}
return new ArrayType(underlyingType, arraySize, location)
}
/**
* Enum decode the given @arg type
*
* @param {String} type - type given by the AST (e.g enum enumDef)
* @param {Object} stateDefinitions - all state definitions given by the AST (including struct and enum type declaration) for all contracts
* @param {String} contractName - contract the @args typeName belongs to
* @return {Object} returns decoded info about the current type: { storageBytes, typeName, enum}
*/
function enumType (type, stateDefinitions, contractName) {
const match = type.match(/enum (.*)/)
const enumDef = getEnum(match[1], stateDefinitions, contractName)
if (enumDef === null) {
console.log('unable to retrieve decode info of ' + type)
return null
}
return new EnumType(enumDef)
}
/**
* Struct decode the given @arg type
*
* @param {String} type - type given by the AST (e.g struct structDef storage ref)
* @param {Object} stateDefinitions - all state definitions 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 {Object} returns decoded info about the current type: { storageBytes, typeName, members}
*/
function struct (type, stateDefinitions, contractName, location) {
const match = type.match(/struct (\S*?)( storage ref| storage pointer| memory| calldata)?$/)
if (match) {
if (!location) {
location = match[2].trim()
}
const 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 {
return null
}
}
/**
* retrieve enum declaration of the given @arg type
*
* @param {String} type - type given by the AST (e.g enum enumDef)
* @param {Object} stateDefinitions - all state declarations given by the AST (including struct and enum type declaration) for all contracts
* @param {String} contractName - contract the @args typeName belongs to
* @return {Array} - containing all value declaration of the current enum type
*/
function getEnum (type, stateDefinitions, contractName) {
const split = type.split('.')
if (!split.length) {
type = contractName + '.' + type
} else {
contractName = split[0]
}
const state = stateDefinitions[contractName]
if (state) {
for (let dec of state.stateDefinitions) {
if (dec && dec.name && type === contractName + '.' + dec.name) {
return dec
}
}
}
return null
}
/**
* retrieve memebers declared in the given @arg tye
*
* @param {String} typeName - name of the struct type (e.g struct <name>)
* @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) {
const split = type.split('.')
if (!split.length) {
type = contractName + '.' + type
} else {
contractName = split[0]
}
const state = stateDefinitions[contractName]
if (state) {
for (let dec of state.stateDefinitions) {
if (dec.nodeType === 'StructDefinition' && type === contractName + '.' + dec.name) {
const offsets = computeOffsets(dec.members, stateDefinitions, contractName, location)
if (!offsets) {
return null
}
return {
members: offsets.typesOffsets,
storageSlots: offsets.endLocation.slot
}
}
}
}
return null
}
/**
* parse the full type
*
* @param {String} fullType - type given by the AST (ex: uint[2] storage ref[2])
* @return {String} returns the token type (used to instanciate the right decoder) (uint[2] storage ref[2] will return 'array', uint256 will return uintX)
*/
function typeClass (fullType) {
fullType = util.removeLocation(fullType)
if (fullType.lastIndexOf(']') === fullType.length - 1) {
return 'array'
}
if (fullType.indexOf('mapping') === 0) {
return 'mapping'
}
if (fullType.indexOf(' ') !== -1) {
fullType = fullType.split(' ')[0]
}
const char = fullType.indexOf('bytes') === 0 ? 'X' : ''
return fullType.replace(/[0-9]+/g, char)
}
/**
* parse the type and return an object representing the type
*
* @param {Object} type - type name given by the ast node
* @param {Object} stateDefinitions - all state stateDefinitions 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 {Object} - return the corresponding decoder or null on error
*/
function parseType (type, stateDefinitions, contractName, location) {
const decodeInfos = {
'contract': address,
'address': address,
'array': array,
'bool': bool,
'bytes': dynamicByteArray,
'bytesX': fixedByteArray,
'enum': enumType,
'string': stringType,
'struct': struct,
'int': int,
'uint': uint,
'mapping': mapping
}
const currentType = typeClass(type)
if (currentType === null) {
console.log('unable to retrieve decode info of ' + type)
return null
}
if (decodeInfos[currentType]) {
return decodeInfos[currentType](type, stateDefinitions, contractName, location)
} else {
return null
}
}
/**
* compute offset (slot offset and byte offset of the @arg list of types)
*
* @param {Array} types - list of types
* @param {Object} stateDefinitions - all state definitions 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} - return an array of types item: {name, type, location}. location defines the byte offset and slot offset
*/
function computeOffsets (types, stateDefinitions, contractName, location) {
const ret = []
const storagelocation = {
offset: 0,
slot: 0
}
for (var i in types) {
var variable = types[i]
var type = parseType(variable.typeDescriptions.typeString, stateDefinitions, contractName, location)
if (!type) {
console.log('unable to retrieve decode info of ' + variable.typeDescriptions.typeString)
return null
}
if (!variable.constant && storagelocation.offset + type.storageBytes > 32) {
storagelocation.slot++
storagelocation.offset = 0
}
ret.push({
name: variable.name,
type: type,
constant: variable.constant,
storagelocation: {
offset: variable.constant ? 0 : storagelocation.offset,
slot: variable.constant ? 0 : storagelocation.slot
}
})
if (!variable.constant) {
if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) {
storagelocation.offset += type.storageBytes
} else {
storagelocation.slot += type.storageSlots
storagelocation.offset = 0
}
}
}
if (storagelocation.offset > 0) {
storagelocation.slot++
}
return {
typesOffsets: ret,
endLocation: storagelocation
}
}
module.exports = {
parseType,
computeOffsets,
Uint: uint,
Address: address,
Bool: bool,
DynamicByteArray: dynamicByteArray,
FixedByteArray: fixedByteArray,
Int: int,
String: stringType,
Array: array,
Enum: enumType,
Struct: struct
}